The introduction
- Why restart and stop gracefully
- What’s the difference between restarting and stopping, and what’s elegant
- What is a semaphore and what are there
- How to achieve graceful restart and stop
- Code making
1, according to?
In the development phase, when we modify the code or configuration, we just CTRL + C and start the service. What’s the problem if we do the same in production?
- The request is missing
- User behavior is interrupted
- You could lose your job
To prevent this from happening, we want existing applications that are processing existing connections not to be interrupted when an application is updated or released, but to finish processing the existing connections before exiting. Newly released applications start receiving new requests and processing them after they are deployed, thus avoiding the problem of connections being interrupted
2. What’s the difference between restarting and stopping?
- A restart is when an update is applied without the connection being processed being broken, and a new process takes up the new application and accepts new requests
- Stop means that when the application process is closed, it can finish processing existing connections before closing and stop accepting new connections
- The bottom line is not to lose the connection, the connection should be processed
3. Definition of semaphore
Signaling is a limited means of interprocess communication in Unix, UniX-like, and other POSIX-compatible operating systems
- Common semaphore
The command | signal | describe |
---|---|---|
ctrl + c | SIGINT | Forcing a Process to End |
ctrl + z | SIGTSTP | Task interrupted, process suspended |
ctrl + \ | SIGQUIT | Process end and dump core |
- Total semaphore
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX- 14 51) SIGRTMAX- 13 52) SIGRTMAX- 12
53) SIGRTMAX- 11 54) SIGRTMAX- 10 55) SIGRTMAX9 - 56) SIGRTMAX- 8 - 57) SIGRTMAX7 -
58) SIGRTMAX- 6 59) SIGRTMAX- 5 60) SIGRTMAX4 - 61) SIGRTMAX- 3 62) SIGRTMAX2 -
63) SIGRTMAX- 1 64) SIGRTMAX
Copy the code
4. How
To achieve purpose
- Do not close an existing connection (a running program).
- The new process starts and replaces the old process.
- The new process takes over the new connection.
- The connection should always respond to the user’s request. The connection should be maintained while the user is still requesting the old process, and the new user should request the new process without having to reject the request.
Realize the principle of
- Monitor SIGHUP signal;
- After receiving the signal, pass the file descriptor that the service listens to to the new child process;
- The new and old processes receive requests at the same time.
- The parent process stops receiving new requests and waits for the old request to complete (or time out);
- The parent process exits.
By the third party components “github.com/fvbock/endless”
import ("github.com/fvbock/endless") r := InitRouter() s := &http.Server{ Addr: fmt.Sprintf("%s:%d", config.ServerSetting.HttpAddress, config.ServerSetting.HttpPort), Handler: r, ReadTimeout: The config. ServerSetting. ReadTimeout, / request/response time WriteTimeout supermarket: Config. ServerSetting. WriteTimeout, / / returns the response timeout / / MaxHeaderBytes: 1 < < 20, / / the default of 1 MB} endless. ListenAndServe ()Copy the code
5. Restart the component after self-heating
The core processes
func serve(a) {
ListenAndServe() // Listen and startThe core run function getNetListener()// 1. Obtain the listener
Serve() // 2. Use the listener to start the server service
handleSignals() // 3. Listen for external signals to control whether the program is forked or shutdown
}
Copy the code
The code address
package hotstart
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
)
const (
LISTENER_FD = 3
DEFAULT_READ_TIMEOUT = 60 * time.Second
DEFAULT_WRITE_TIMEOUT = DEFAULT_READ_TIMEOUT
)
var (
runMutex = sync.RWMutex{}
)
// HTTP server that supported hotstart shutdown or restart
type HotServer struct {
*http.Server
listener net.Listener
isChild bool
signalChan chan os.Signal
shutdownChan chan bool
BeforeBegin func(addr string)
}
func ListenAndServer(server *http.Server) error {
return NewHotServer(server).ListenAndServe()
}
func ListenAndServe(addr string, handler http.Handler) error {
return NewServer(addr, handler, DEFAULT_READ_TIMEOUT, DEFAULT_WRITE_TIMEOUT).ListenAndServe()
}
/*
new HotServer
*/
func NewHotServer(server *http.Server) (srv *HotServer) {
runMutex.Lock()
defer runMutex.Unlock()
isChild := os.Getenv("HOT_CONTINUE") != ""
srv = &HotServer{
Server: server,
isChild: isChild,
signalChan: make(chan os.Signal),
shutdownChan: make(chan bool),}// The service starts before the hook, the command line output PID
srv.BeforeBegin = func(addr string) {
srv.logf(addr)
}
return
}
/*
new HotServer
*/
func NewServer(addr string, handler http.Handler, readTimeout, writeTimeout time.Duration) *HotServer {
Server := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
}
return NewHotServer(Server)
}
/*
Listen http server
*/
func (srv *HotServer) ListenAndServe(a) error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := srv.getNetListener(addr)
iferr ! =nil {
return err
}
srv.listener = ln
if srv.isChild {
// Notify the parent that the request is not accepted
syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
}
srv.BeforeBegin(srv.Addr)
return srv.Serve()
}
/* Listen to HTTPS server */
func (srv *HotServer) ListenAndServeTLS(certFile, keyFile string) error {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
config := &tls.Config{}
ifsrv.TLSConfig ! =nil {
*config = *srv.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"HTTP / 1.1"}}var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
iferr ! =nil {
return err
}
ln, err := srv.getNetListener(addr)
iferr ! =nil {
return err
}
srv.listener = tls.NewListener(ln, config)
if srv.isChild {
syscall.Kill(syscall.Getppid(), syscall.SIGTERM)
}
srv.BeforeBegin(srv.Addr)
return srv.Serve()
}
/* Service start */
func (srv *HotServer) Serve(a) error {
// Listen for signals
go srv.handleSignals()
err := srv.Server.Serve(srv.listener)
srv.logf("waiting for connections closed.")
// Block until closed
<-srv.shutdownChan
srv.logf("all connections closed.")
return err
}
/*
get lister
*/
func (srv *HotServer) getNetListener(addr string) (ln net.Listener, err error) {
if srv.isChild {
file := os.NewFile(LISTENER_FD, "")
ln, err = net.FileListener(file)
iferr ! =nil {
err = fmt.Errorf("net.FileListener error: %v", err)
return nil, err
}
} else {
ln, err = net.Listen("tcp", addr)
iferr ! =nil {
err = fmt.Errorf("net.Listen error: %v", err)
return nil, err
}
}
return ln, nil
}
/* Monitor signal */
func (srv *HotServer) handleSignals(a) {
var sig os.Signal
signal.Notify(
srv.signalChan,
syscall.SIGTERM,
syscall.SIGUSR2,
)
for {
sig = <-srv.signalChan
switch sig {
case syscall.SIGTERM:
srv.logf("received SIGTERM, hotstart shutting down HTTP server.")
srv.shutdown()
case syscall.SIGUSR2:
srv.logf("received SIGUSR2, hotstart restarting HTTP server.")
iferr := srv.fork(); err ! =nil {
log.Println("Fork err:", err)
}
default:}}}/* Gracefully close the background */
func (srv *HotServer) shutdown(a) {
iferr := srv.Shutdown(context.Background()); err ! =nil {
srv.logf("HTTP server shutdown error: %v", err)
} else {
srv.logf("HTTP server shutdown success.")
srv.shutdownChan <- true}}// start new process to handle HTTP Connection
func (srv *HotServer) fork(a) (err error) {
listener, err := srv.getTCPListenerFile()
iferr ! =nil {
return fmt.Errorf("failed to get socket file descriptor: %v", err)
}
// set hotstart restart env flag
env := append(
os.Environ(),
"HOT_CONTINUE=1",
)
execSpec := &syscall.ProcAttr{
Env: env,
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), listener.Fd()},
}
_, err = syscall.ForkExec(os.Args[0], os.Args, execSpec)
iferr ! =nil {
return fmt.Errorf("Restart: Failed to launch, error: %v", err)
}
return
}
/* Get the TCP listener file */
func (srv *HotServer) getTCPListenerFile(a) (*os.File, error) {
file, err := srv.listener.(*net.TCPListener).File()
iferr ! =nil {
return file, err
}
return file, nil
}
/* Format output Log */
func (srv *HotServer) logf(format string, args ...interface{}) {
pids := strconv.Itoa(os.Getpid())
format = "[pid " + pids + "]" + format
log.Printf(format, args...)
}
Copy the code
The demo address
func hello(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Second)
w.Write([]byte("hello world233333!!!!"))}func test(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test 22222"))}func main(a) {
http.HandleFunc("/hello", hello)
http.HandleFunc("/test", test)
pid := os.Getpid()
address := ": 9999"
s := &http.Server{
Addr: address,
Handler: nil,
}
err := Hot.ListenAndServer(s)
log.Printf("process with pid %d stoped, error: %s.\n", pid, err)
}
Copy the code
6, scripts,
Restart (according to the project startup script)
ps aux | grep "gintest" | grep -v grep | awk '{print $2}' | xargs -i kill -SIGUSR2 {}
Copy the code
stop
ps aux | grep "gintest" | grep -v grep | awk '{print $2}' | xargs -i kill -SIGTERM {}
Copy the code
Port restart
ps aux | lsof -i:8080 | grep -v grep | awk '{print $1}' | xargs -i kill -1 {}
Copy the code
Stop port
ps aux | lsof -i:8080 | grep -v grep | awk '{print $1}' | xargs -i kill {}
Copy the code
7,
Elegant reboots (hot updates) are an important part of everyday HTTP services, and Golang has a number of options that we can choose from
8. Recommend cases
My own another framework has added hotStart use, support for multiple environment compilation, address
9. Series of articles
- Serial set up a Golang environment
- Serial 2 Install Gin
- Serial three defines the directory structure
- Serial four build case API1
- Serial five build case API2
- Serialized six access Swagger interface documents
- Serialize seven log components
- Serial eight graceful restart and stop
- Serialize the external Makefile build
- Serialize other Cron scheduled tasks
- Serialized content to create command-line tools
- 3 days to build exclusive Cache(First day)
- 3 days to build exclusive Cache(Second day)
- Third Day: Creating a dedicated Cache