У меня есть такое чувство, что в комментах люди бывает фантазируют, о чем угодно. Возможно кто-то думает, что и я брешу и нет балансировщика на Go. Поэтому, я решил выложить код балансировщика сразу. Этот код был написан в “особой ситуации” за 4 часа, и потом работал примерно в такой форме 2 недели без перегрузки так, как “все” были в Греции. Код не красив и даже содержит ошибки, но так как он работал и балансировал, то уже чего то стоит.
Под катом почти оринальный скорописный балансировщик. Я убрал оригинальные константы и код декодирования кук.
package main // (c) http://habrahabr.ru/users/pyra/ BSD license import ( // "encoding/json" "fmt" // "io" "io/ioutil" "log" "time" "net/http" "net/url" // "os" // "sort" "strconv" "strings" // "time" "errors" ) func main() { //http.HandleFunc("/r", handle_redir) //http.Handle("/extrahtml/", http.FileServer(http.Dir("./extrahtml/"))) http.HandleFunc("/googleXXXXXXXXXXXX.html", handle_google) http.HandleFunc("/", handle_def) https1 := &http.Server{ Addr: ":8443", Handler: nil, ReadTimeout: 20 * time.Second, WriteTimeout: 20 * time.Second, MaxHeaderBytes: 1 << 15, } go func() { log.Fatal(https1.ListenAndServeTLS("device.crt", "device.key")) }() http1 := &http.Server{ Addr: ":8080", Handler: nil, ReadTimeout: 20 * time.Second, WriteTimeout: 20 * time.Second, MaxHeaderBytes: 1 << 15, } http1.ListenAndServe() } var reqcntr int var opencntr int func redirectPolicyFunc(req *http.Request, via []*http.Request) error { e := errors.New("redirect") return e } func handle_google(w http.ResponseWriter, r *http.Request) { fmt.Println("google") b, _ := ioutil.ReadFile("googleXXXXXXXXXXXXXXXX.html") fmt.Println(len(b)) w.Write(b) } func handle_def(w http.ResponseWriter, r *http.Request) { // считаем количество открытых одновременно соединений opencntr++ defer func() { opencntr-- }() client := &http.Client{ CheckRedirect: redirectPolicyFunc, } ip := strings.Split(r.RemoteAddr, ":")[0] // считаем примерно запросы (без мютексов) reqcntr++ q := r.URL.RawQuery //fmt.Println("def ", r.Method, reqcntr, q) //r.Form, _ = url.ParseQuery(r.URL.RawQuery) //io.WriteString(w, r.URL.Path) path := r.URL.Path // ID пользователя будем хранить тут var cid int64 // пометим если запрос к ПХП странице breporting := false // ПХП будем обрабатывать особым образом if strings.HasSuffix(path, ".php") { // fmt.Println("breporting = true") breporting = true } // через прокси будем запрашивать другую ПХП страницу if path == "/ajax/main.php" { path = "/ajax/main_hide1777.php" } cid = -2 if strings.HasPrefix(path, "/ajax/") || strings.HasPrefix(path, "/im/") { // файлы в след папках содержат ID пользователя m, err := url.ParseQuery(q) if err == nil { id := m.Get("xid") if id == "" { id = m.Get("aid") if id == "" { id = m.Get("cid") if id == "" { id = m.Get("bid") } } } cid, err = strconv.ParseInt(id, 10, 64) if err != nil { cid = -1 } } } else if strings.HasPrefix(path, "/avatar/") { // аватарка тоже содержит ИД /avatar/1234.gif // ID - 1234 cid = -1 pos1 := strings.Index(path[8:], ".") if pos1 != -1 { id := path[6 : pos1+6] var err error cid, err = strconv.ParseInt(id, 10, 64) if err != nil { cid = -1 } } } // тут был ещё один else где хитро декриптились куки для определения целевого сервера // переводим ID пользователя в домен сервера обрабатающего его запросы //host := "test000.cloud" host := "login.yahoo.com" // для интереса можно посматреть чем заголовок балансировшика отличается от оригинала if cid > 1000 && cid < 5000 { host = "prod002.cloud" } else if cid >= 5000 && cid < 7000 { host = "prod003.cloud" } else if cid >= 7000 && cid < 15000 { host = "prod005.cloud" } else if cid >= 15000 && cid < 16000 { host = "prod006.cloud" } else if cid >= 25000 && cid < 34000 { host = "prod011.cloud" } url := "" // передаем реальный IP в урле так как PHP 5.3 FastCGI не умеет хедеры читать if breporting { url = "https://" + host + path + "?" + q + "&HEHE_IP="+ip+"&HEHE_SECRET=B87BVf5" }else{ url = "https://" + host + path + "?" + q } fmt.Println(url) // проксируем только GET запросы if r.Method == "GET" { req1, err := http.NewRequest("GET", url, nil) if err != nil { fmt.Println("Error1: ", err) // тут чего то не хватает } req1.Header = r.Header req1.Header.Add("XHEHE_REMOTE_IP", ip) resp, _ := client.Do(req1) StatusCode := resp.StatusCode defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("Error2: ", err) // и тут )))) // это обычно выстреливает когда редирект } fmt.Println("def ", r.Method, reqcntr, opencntr, url, len(body), StatusCode) for k1, v1 := range resp.Header { for _, v2 := range v1 { w.Header().Add(k1, v2) } } w.WriteHeader(StatusCode) w.Write(body) } } // 190 строк с комментами
После этот код был переписан. В нем были реализованы идеи из моей предыдущей стаьи. Отдельный порт для админки, который работает всегда, благодаря контролю того сколько к серверу соединений, статистику и отчеты, API для обновления таблиц маршрутизации (это позволяет мигрировать пользователей). И вот в разрабатываем задержку запросов на время обновления инстансов или миграции пользователей, так же в бете есть кеширование статики, кеширование редиректов.
Что такое миграция пользователя. Основной профайл пользователя без фоток это ~10КБ. Сервер А сериализирует пользователя в файл, файл сжимается, файл копируется на сервер Б, балансировщику или балансировшикам говорится, что теперь запросы от этого пользователя следует направлять серверу Б.
Мне было бы интересно сравнить Go и ngnix. Понятно ngnix быстрей, но если ngnix на одном ядре балансирует 300 или 500 мбит, а Go только 50 мбит, то учитывая скорость типичного виртуального порта у облачного сервера за 5 баксов у того же DigitalOcean, интересно как это выражается в деньгах? А вдруг Go может балансировать только 1мбит? Времени нет сравнить и написать.
Если кто-то решит сделать бенчмарк, то важно учитывать не только, то что размер ответа разный, но и то что запрос может обрабатываться с задержкой. Поэтому, для симуляции инстансов отлично подойдет Go. Вместо http.NewRequest поставить time.Sleep. Инстансов должно быть на порядок больше, чем балансировшиков.
ссылка на оригинал статьи http://habrahabr.ru/post/197570/
Добавить комментарий