Server-side code often needs to be upgraded. The commonly used method for upgrading online systems is to ensure that at least one service is available during the upgrade through front-end load balancing (such as nginx) and upgrade in sequence (grayscale).
Another more convenient method is to do a hot restart on the application, directly upgrade the application without stopping service.
principle
The principle of hot restart is very simple, but it involves many details such as some system calls and the transfer of file handles between parent and child processes.
The processing process is divided into the following steps:
- Monitoring signal (USR2)
- When a signal is received, the fork child process (using the same startup command) passes the socket file descriptor that the service is listening to to the child process
- The child process listens to the parent process's socket. At this time, both the parent and the child process can receive requests.
- After the child process is successfully started, the parent process stops receiving the new connection and waits for the old connection processing to complete (or timeout)
- The parent process exits and the upgrade is completed
detail
- The parent process passes the socket file descriptor to the child process through the command line, or environment variables, etc.
- The child process uses the same command line as the parent process when it starts. For golang, it overwrites the old program with an updated executable program.
- ()The elegant closing method is a new feature of go1.8
- (l) The method returns immediately when Shutdown, and the Shutdown method blocks until the context completes, so the Shutdown method must be written in the main goroutine
Code
package main import ( "context" "errors" "flag" "log" "net" "net/http" "os" "os/exec" "os/signal" "syscall" "time" ) var ( server * listener graceful = ("graceful", false, "listen on fd open 3 (internal use only)") ) func handler(w , r *) { (20 * ) ([]byte("hello world233333!!!!")) } func main() { () ("/hello", handler) server = &{Addr: ":9999"} var err error if *graceful { ("main: Listening to existing file descriptor 3.") // : If non-nil, entry i becomes file descriptor 3+i. // when we put socket FD at the first entry, it will always be 3(0+3) f := (3, "") listener, err = (f) } else { ("main: Listening on a new file descriptor.") listener, err = ("tcp", ) } if err != nil { ("listener error: %v", err) } go func() { // () stops Serve() immediately, thus () should not be in main goroutine err = (listener) (" err: %v\n", err) }() signalHandler() ("signal end") } func reload() error { tl, ok := listener.(*) if !ok { return ("listener is not tcp listener") } f, err := () if err != nil { return err } args := []string{"-graceful"} cmd := ([0], args...) = = // put socket FD at the first entry = []*{f} return () } func signalHandler() { ch := make(chan , 1) (ch, , , syscall.SIGUSR2) for { sig := <-ch ("signal: %v", sig) // timeout context for shutdown ctx, _ := ((), 20*) switch sig { case , : // stop ("stop") (ch) (ctx) ("graceful shutdown") return case syscall.SIGUSR2: // reload ("reload") err := reload() if err != nil { ("graceful restart error: %v", err) } (ctx) ("graceful reload") return } } }
references
Graceful Restart in Golang
facebookgo/grace
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.