Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
A background
After the deployment of the Web project written by us, we often restart the service for configuration change or function iteration. The simple kill -9 PID method will force the process to shut down, which will lead to the failure of the server currently processing requests. Is there a more elegant way to shut down or restart the service?
To read this article, you need to understand some of the concepts of signals in UNIX systems.
Ii. Implementation Scheme
The 2.1 Linux signal
2.1.1 Signal name and number
Each signal has a name and a number that starts with “SIG”, such as “SIGIO”, “SIGCHLD”, and so on. The signals are defined in the signal.h header file, and the signal names are all defined as positive integers. You can run the kill -l command to check the name and serial number of the signal. The signal is numbered from 1 and no signal 0 exists. Kill has a special application for signal 0.
root@1204nStrive:~# 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) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAXCopy the code
2.1.2 Signal processing
Signal processing has three methods, respectively: ignore, capture and default action
- Ignore signals. Most signals can be processed in this way, but there are two types of signals that cannot be ignored (respectively
SIGKILL
andSIGSTOP
). Because they provide the kernel and the superuser with a reliable way to terminate and stop a process, if ignored, the process becomes an unmanageable process, which the kernel designers clearly do not want to see - To capture a signal, you need to tell the kernel how the user wants to process a particular signal. In other words, you need to write a signal processing function and then tell the kernel that function. When the signal is generated, the kernel calls a user-defined function to realize some signal processing.
- System default action, for each signal, the system corresponds to the default processing action, when the signal occurs, the system will automatically execute. However, most of the solutions to the system are crude: kill the process. Specific signal default actions can be used
man 7 signal
To see the specific definition of the system. Here, I will not expand in detail, need to view, you can view. Also refer to the P251 — P256 section of Advanced Programming for UNIX Environments (Part 3) for detailed instructions on each signal.
2.2 Graceful Shutdown
2.2.1 What is an elegant shutdown
Graceful shutdown means that the server does not shut down immediately after the shutdown command is issued, but exits the program after all the requests that are still being processed are finished. It is a kind of client-friendly shutdown mode. Pressing Ctrl+C to shut down the server will force the process to end, causing problems with ongoing requests.
2.2.2 Elegant shutdown
After Go 1.8, the Shutdown() method built into HTTP.server supports elegant Shutdown, as follows
Three practical
3.1 Http.server built-in Shutdown() method
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
// @title Docker monitoring service
/ / @ version 1.0
// @description gin shutdown
// @contact.name API Support
// @contact.url http://www.swagger.io/support
/ / @ license. Name the Apache 2.0
/ / @ license. The url http://www.apache.org/licenses/LICENSE-2.0.html
/ / @ host 127.0.0.1:9009
// @BasePath
func main(a) {
r := gin.Default()
r.GET("/".func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "gin %s"."ok")
})
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
server := http.Server{
Addr: ": 8080",
Handler: r,
}
go func(a) {
iferr := server.ListenAndServe(); err ! =nil&& err ! = http.ErrServerClosed { log.Fatal("server listen err:%s", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// block here
<-quit
ctx, channel := context.WithTimeout(context.Background(), 5*time.Second)
defer channel()
iferr := server.Shutdown(ctx); err ! =nil {
log.Fatal("server shutdown error")
}
log.Println("server exiting...")}Copy the code
How to verify the effect of an elegant shutdown?
The above code will run to start a Web service on port 8080, which registers only one route /, and the back-end service will sleep for 5 seconds before returning a response.
When we press Ctrl+C, we send syscall.sigint to tell the program to gracefully shut down, as follows:
- Open the terminal, compile and execute the code above
- Open a browser and visit
127.0.0.1:8080 /
“, the browser displays a blank screen and waits for a response from the server. - In the endquicklyperform
Ctrl+C
Commands are sent to the programsyscall.SIGINT
signal - Instead of exiting immediately, the program will wait for our step 2 response to return and then exit, thus gracefully shutting down.
3.2 Graceful Restart
Elegant shutdown is implemented, so how to implement elegant restart?
We can do this by using fvbock/ Endless to replace the default ListenAndServe startup service with the following example code:
package main
import (
"log"
"net/http"
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func main(a) {
router := gin.Default()
router.GET("/".func(c *gin.Context) {
time.Sleep(5 * time.Second)
c.String(http.StatusOK, "hello gin!")})// By default, the Endless server listens for the following signals:
SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGTERM and syscall.SIGTSTP
// SIGHUP signal will trigger 'fork/restart' for an elegant reboot (kill -1 PID will send SIGHUP signal)
// Receiving syscall.SIGINT or syscall.SIGTERM triggers a graceful shutdown
// Receiving SIGUSR2 triggers HammerTime
// SIGUSR1 and SIGTSTP are used to trigger some user-defined hook functions
if err := endless.ListenAndServe(": 8080", router); err! =nil{
log.Fatalf("listen: %s\n", err)
}
log.Println("Server exiting")}Copy the code
How do you verify an elegant reboot?
We send syscall.SIGINT by executing the kill -1 pid command to tell the program to restart gracefully as follows:
- Open the terminal,
go build -o graceful_restart
Compile and execute./graceful_restart
, the terminal outputs the current PID (assume 43682) - Returns the function that handles the request in the code
hello gin!
Modified tohello q1mi!
, compile againgo build -o graceful_restart
- Open a browser and visit
127.0.0.1:8080 /
“, the browser displays a blank screen and waits for a response from the server. - In the endquicklyperform
kill -1 43682
Commands are sent to the programsyscall.SIGHUP
signal - Wait for step 3 for the browser to receive the response message
hello gin!
Later visit127.0.0.1:8080 /
Will receivehello q1mi!
The response. - The program code is replaced without affecting the current pending request, enabling an elegant restart.
However, it should be noted that the PID of the application changes, because Endless restarts gracefully by forking new requests and exiting after the original process finishes processing the current request. This is not the case when your project uses software like Supervisor to manage processes.
Pay attention to the point
- To start the GO coroutine, an error other than HTTP.ErrServerClosed is required
iferr := rsv.ListenAndServe(); err ! =nil&& err ! = http.ErrServerClosed {Copy the code
- Define signal, block
// Kill sends the syscall.sigterm signal by default
// kill -2 send syscall.SIGINT signal
// kill -9 sends the syscall.SIGKILL signal, but it cannot be captured, so there is no need to add it
// signal.Notify Forwards the received syscall.SIGINT or syscall.SIGTERM signal to quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // There is no blocking here
<-quit // Block here, and proceed only when the above two signals are received
log.Println("Shutdown Server ...")
Copy the code
conclusion
Both graceful shutdown and graceful restart are ultimately by listening to specific system signals, and then performing certain logical processing to ensure that the current system is processing requests properly before shutting down the current process. Using graceful shutdown or graceful restart, and how to achieve this, depends on the actual situation of the project.