Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize smooth restart in Golang

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

Editor to share with you how to achieve a smooth restart in Golang, I believe most people do not know much about it, so share this article for your reference, I hope you can learn a lot after reading this article, let's learn about it!

Like the reload configuration, we also need to notify server to restart by signal, but the key is to restart smoothly. If it is a simple restart, you only need to drop kill and then pull it up. A smooth restart means that there is no need to stop business when server is upgraded.

Let's take a look at whether there are any libraries on Github to solve this problem, and then find the following three libraries:

Facebookgo/grace-Graceful restart & zero downtime deploy for Go servers.

Fvbock/endless-Zero downtime restarts for go servers (Drop in replacement for http.ListenAndServe)

Jpillora/overseer-Monitorable, gracefully restarting, self-upgrading binaries in Go (golang)

Let's learn separately. Let's just talk about the restart of http server.

Mode of use

Let's use each of these three libraries to do a smooth restart, and then compare their advantages and disadvantages.

The officials of these three libraries have given corresponding examples, as follows:

But the official examples of the three libraries are not quite the same, so let's unify them:

Grace example https://github.com/facebookgo/grace/blob/master/gracedemo/demo.go

Endless example https://github.com/fvbock/endless/tree/master/examples

Overseer example https://github.com/jpillora/overseer/tree/master/example

Let's refer to the official examples to write down the examples for comparison:

Grace

Package mainimport ("time"net/http"github.com/facebookgo/grace/gracehttp") func main () {gracehttp.Serve (& http.Server {Addr: ": 5001", Handler: newGraceHandler ()}, & http.Server {Addr: ": 5002", Handler: newGraceHandler ()},)} func newGraceHandler () http.Handler {mux: = http.NewServeMux () mux.HandleFunc ("/ sleep", func (w http.ResponseWriter, r * http.Request) {duration Err: = time.ParseDuration (r.FormValue ("duration")) if err! = nil {http.Error (w, err.Error (), 400) return} time.Sleep (duration) w.Write ([] byte ("Hello World")}) return mux}

Endless

Package mainimport ("log"net/http"os"sync"time"github.com/fvbock/endless"github.com/gorilla/mux") func handler (w http.ResponseWriter, r * http.Request) {duration, err: = time.ParseDuration (r.FormValue ("duration") if err! = nil {http.Error (w, err.Error ()) Return} time.Sleep (duration) w.Write ([] byte ("Hello World"))} func main () {mux1: = mux.NewRouter () mux1.HandleFunc ("/ sleep", handler) w: = sync.WaitGroup {} w.Add (2) go func () {err: = endless.ListenAndServe (": 5003") Mux1) if err! = nil {log.Println (err)} log.Println ("Server on 5003 stopped") w.Done ()} () go func () {err: = endless.ListenAndServe (": 5004" mux1) if err! = nil {log.Println (err)} log.Println ("Server on 5004 stopped") w.Done ()} () w.Wait () log.Println ("All servers stopped. Exiting. ") Os.Exit (0)}

Overseer

Package mainimport ("fmt"net/http"time"github.com/jpillora/overseer") / / see example.sh for the use-case// BuildID is compile-time variablevar BuildID = "0" / / convert your 'main ()' into a 'prog (state)' / / 'prog ()' is run in a child processfunc prog (state overseer.State) {fmt.Printf ("app#%s (% s) listening...\ n", BuildID, state.ID) http.Handle ("/") Http.HandlerFunc (func (w http.ResponseWriter, r * http.Request) {duration, err: = time.ParseDuration (r.FormValue ("duration")) if err! = nil {http.Error (w, err.Error (), 400) return} time.Sleep (duration) w.Write ([] byte ("Hello World") fmt.Fprintf (w, "app#%s (% s) says hello\ n", BuildID State.ID)) http.Serve (state.Listener, nil) fmt.Printf ("app#%s (% s) exiting...\ n", BuildID, state.ID)} / / then create another 'main' which runs the upgrades//'main ()' is run in the initial processfunc main () {overseer.Run (overseer.Config {Program: prog, Addresses: [] string {": 5005", ": 5006"} / Fetcher: & fetcher.File {Path: "my_app_next"}, Debug: false, / / display log of overseer actions})}

Contrast

Compare the operation steps of the example

Build the above example separately and record the pid

Call API, and when it is not returned, modify the content (Hello World-> Hello Harry) and rebuild. Check whether the old API returns the old content

Call the new API to see if the returned content is new

Check whether the currently running pid is the same as before

The following operation commands are given.

# when go build grace.go# runs the project for the first time, you can make content changes. / grace & # request the project, and return to curl "http://127.0.0.1:5001/sleep?duration=60s" & # again after 60s. Here is the restart of the new content go build grace.go#. 2096 request curl "http://127.0.0.1:5001/sleep?duration=1s"# to build the project go build endless.go# to run the project for the first time for pidkill-USR2 209 new API, then you can make content changes. / endless & # request the project, return to curl" http://127.0.0.1:5003/sleep?duration=60s" & # again after 60s. Here is the restart of the new content go build endless.go#. 22072 request curl "http://127.0.0.1:5003/sleep?duration=1s"# to build the project for the first time go build-ldflags'- X main.BuildID=1' overseer.go# run the project for pidkill-12207 new API. / overseer & # request the project, return to curl" http://127.0.0.1:5005/sleep?duration=60s" & # to build the project again after 60s. Here is the new content. Note that the version number is different go build-ldflags'- X main.BuildID=2' overseer.go# restart, 28300 main process pidkill-USR2 2830 new API request curl http://127.0.0.1:5005/sleep?duration=1s

Contrast result

Example old API return value new API return value old pid new pid conclusion graceHello worldHello Harry20963100 old API will not be broken, the original logic will be executed, pid will change endlessHello worldHello Harry2207222365 old API will not break, original logic will be executed, pid will change overseerHello worldHello Harry2830028300 old API will not be broken, original logic will be executed, and the main process pid will not change

Principle analysis

You can see that grace and endless are quite similar.

Monitoring signal

When the signal is received, the socket child process (using the same startup command) passes the service listening socket file descriptor to the child process

The child process listens to the socket of the parent process, and both the parent process and the child process can receive requests

After the child process starts successfully, the parent process stops receiving the new connection and waits for the old connection processing to complete (or timeout).

Parent process exited, upgrade completed

Overseer is different, mainly because overseer adds a main process management smooth restart, child processes handle links, can keep the main process pid unchanged.

The picture below is very vivid.

Realize it by yourself

Let's try to implement the first process ourselves. The code is as follows, which comes from "Hot restart golang Server":

Package mainimport ("context"errors"flag"log"net"net/http"os"os/exec"os/signal"syscall"time") var (server * http.Server listener net.Listener graceful = flag.Bool ("graceful", false, "listen on fd open 3 (internal use only)") func sleep (w http.ResponseWriter, r * http.Request) {duration Err: = time.ParseDuration (r.FormValue ("duration") if err! = nil {http.Error (w, err.Error (), 400) return} time.Sleep (duration) w.Write ([] byte ("Hello World"))} func main () {flag.Parse () http.HandleFunc ("/ sleep") Sleep) server = & http.Server {Addr: ": 5007"} var err error if * graceful {log.Print ("main: Listening to existing file descriptor 3.") / / cmd.ExtraFiles: If non-nil, entry i becomes file descriptor 3i. / / when we put socket FD at the first entry, it will always be 3 (0,3) f: = os.NewFile (3, ") listener Err = net.FileListener (f)} else {log.Print ("main: Listening on a new file descriptor.") Listener, err = net.Listen ("tcp", server.Addr)} if err! = nil {log.Fatalf ("listener error:% v", err)} go func () {/ / server.Shutdown () stops Serve () immediately, thus server.Serve () should not be in main goroutine err = server.Serve (listener) log.Printf ("server.Serve err:% v\ n" Err)} () signalHandler () log.Printf ("signal end")} func reload () error {tl, ok: = listener. (* net.TCPListener) if! ok {return errors.New ("listener is not tcp listener")} f, err: = tl.File () if err! = nil {return err} args: = [] string {"- graceful"} cmd: = exec.Command (os.Args [0], args...) Cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr / / put socket FD at the first entry cmd.ExtraFiles = [] * os.File {f} return cmd.Start ()} func signalHandler () {ch: = make (chan os.Signal, 1) signal.Notify (ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2) for {sig: =

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report