📜 ⬆️ ⬇️

200 line Go balancer

I mentioned that I developed a balancer on Go, although there is an opinion that the front end should be nginx.

I have a feeling that in the comments people happen to fantasize about anything. Perhaps someone thinks that I am also wrong and there is no balancer on Go. Therefore, I decided to post the balancer code immediately. This code was written in a “special situation” for 4 hours, and then worked approximately in such a form for 2 weeks without overloading as “everyone” was in Greece. The code is not beautiful and even contains errors, but since it worked and balanced, it is already worth something.

Under the cut is almost original cursive balancer. I removed the original constants and the decoding code of the cookies.

')
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    

After this code has been rewritten. It was implemented ideas from my previous pack. A separate port for the admin, which always works, thanks to controlling how many connections to the server, statistics and reports, the API for updating the routing tables (this allows users to migrate). And now we are developing delay requests for updating instances or user migrations, as well there is static caching, redirects caching in beta.

What is user migration? The main user profile without photos is ~ 10KB. Server A serializes the user to a file, the file is compressed, the file is copied to server B, the balancer or balancer says that now requests from this user should be sent to server B.

I would be interested to compare Go and nginx. It is clear that nginx is faster, but if nginx on one core balances 300 or 500 Mbps, and Go is only 50 Mbps, then considering the speed of a typical virtual port on a cloud server for 5 bucks from the same DigitalOcean, I wonder how this is expressed in money? What if Go can only balance 1 Mbps? There is no time to compare and write.

If someone decides to make a benchmark, it is important to consider not only that the size of the response is different, but also that the request can be processed with a delay. Therefore, Go is perfect for instantiation simulation. Instead of http.NewRequest put time.Sleep. There should be an order of magnitude more than balancers.

PS For those who are not familiar with Go. Go turns this seemingly synchronous code into asynchronous, which can run on one thread or several threads using asynchronous calls. Just like nginx does. The compilation results in an executable file that is independent of Go and can be executed on a machine without Go.

Source: https://habr.com/ru/post/197570/


All Articles