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