# Golang Program graceful shutdown and reboot # What is elegance when the online code has an update, we want to shut down the service first, and then start the service, if the traffic is large, when the service is closed, the current server is likely to have a lot of connections, then if the service directly shut down at this time, these connections will be all broken off, The impact on the user experience is definitely not elegant so we have to think of a way to smooth off or restart the program is called Elegance. # # idea 1. When the server starts, it opens a process to listen for the shutdown Signal 2. When the process receives a shutdown signal, it refuses to receive a new connection and handles all current connections and disconnects 3. Start a new server-side process to take over the new Connection 4. Close the current process # # Implementation takes the [Siluser/bingo] (Https://github.com/silsuer/bingo) framework as an example > a series of articles about this framework:-[Use Go to write a simple MVC web Framework] (https:/ /studygolang.com/articles/12818)-[use go to encapsulate a convenient ORM] (https://studygolang.com/articles/12825)-[retrofit Httprouter to enable middleware support ] (Transforming Httprouter to support middleware)-[modeled Laravel-artisan for easy Go Development Scaffolding] (https://studygolang.com/articles/14148) I used [tim1020/ Godaemon] (Https://github.com/tim1020/godaemon) This package to achieve smooth restart function (for most projects, direct use can meet most requirements, without modification) desired effect: Enter ' bingo run in console daemon [start|restart|stop] ' can cause the server to start | restart | stop ' 1. First look at how to open a server (' Bingo Run dev ') the implementation of the ' bingo ' command can be seen in my previous blog: [Modeled Laravel-artisan for easy Go Development scaffolding] (https://studygolang.com/ articles/14148) because it's a development environment, the general idea is that the ' bingo run ' command translates to ' shell ' command ' go run start.go ' so ' bingo run dev ' equals ' go run start.go de ' V ' ' go//handles HTTP. Server, enabling supportGraceful Stop/restartfunc graceful (s http. Server) Error {//setting an environment variable OS. Setenv ("__graceful", "true")//Create a custom Serversrv = &server{cm:newconnectionmanager (), server:s,}// Sets the state of the server SRV. Connstate = FUNC (conn net. Conn, state http. connstate) {switch state {case HTTP. StateNew:srv.cm.add (1) Case http. StateActive:srv.cm.rmIdleConns (Conn. Localaddr (). String ()) case HTTP. StateIdle:srv.cm.addIdleConns (Conn. Localaddr (). String (), conn) case http. statehijacked, http. StateClosed:srv.cm.done ()}}l, err: = Srv.getlistener () if Err = = Nil {err = srv. Server.serve (L)} else {fmt. PRINTLN (ERR)}return err} "" So that you can start a server and listen to 2 when the connection state changes. Start the server with the daemon when you use ' Bingo run daemon ' or ' Bingo run daemon start ', the ' daemoninit () ' function is triggered, as follows: ' ' Gofunc daemoninit () {//Get hold P The path to the id file dir, _: = OS. GETWD () pidfile = dir + "/" + Env.get ("Pid_file") if OS. Getenv ("__daemon")! = "true" {//mastercmd: = "Start"//default = startif L: = Len (OS). Args); L > 2 {cmd = OS. Args[l-1]}switch cmd {case "start": if IsRunning () {fmt. Printf ("\ n%c[0;48;34m%s%c[0m ", 0x1B," ["+strconv. Itoa (Pidval) + "] Bingo is running", 0x1B)} else {//fork daemon process if err: = Forkdaemon (); Err! = nil {fmt. PRINTLN (ERR)}}case "Restart"://restart: if!isrunning () {fmt. Printf ("\ n%c[0;48;31m%s%c[0m", 0x1B, "[Warning]bingo not Running", 0x1B) restart (pidval)} else {fmt. Printf ("\ n%c[0;48;34m%s%c[0m", 0x1B, "[" +strconv. Itoa (Pidval) + "] Bingo restart Now", 0x1B) restart (pidval)}case "Stop"://Stop if!isrunning () {fmt. Printf ("\ n%c[0;48;31m%s%c[0m", 0x1B, "[Warning]bingo not Running", 0x1B)} else {Syscall. Kill (Pidval, Syscall. SIGTERM)//kill}case "-H": FMT. Println ("Usage:" + appName + "Start|restart|stop") Default://Other unrecognized parameters return//returned to the caller}//the main process to exit the OS. Exit (0)}go handlesignals ()} "the first thing to get ' pidfile ' This file is to store the process ' PID ' when the program runs, why should I persist ' pid '? is to allow multiple programs to run the process, determine whether there is the same program startup and other operations to obtain the corresponding operation (START|RESTART|STOP), a said > Case ' start ': First Use the ' isrunning () ' method to determine whether the current program is running , how to judge? is to remove the process number from the ' pidfile ' mentioned above and then determine if the current system is running to make this process, if so, prove that it is running, return ' true ', and return ' false ' if not running, call ' Forkdaemon () ' function to start the program, This function is the core of the whole function '' Gofunc Forkdaemon () error {args: = os. Argsos.setenv ("__daemon", "true") procattr: = &syscall. Procattr{env:os. Environ (), Files: []uintptr{os. STDIN.FD (), OS. STDOUT.FD (), OS. STDERR.FD ()},}pid, err: = Syscall. Forkexec (Args[0], []string{args[0], "Dev"}, procattr) if err! = Nil {panic (err)}savepid (PID) fmt. Printf ("\ n%c[0;48;32m%s%c[0m", 0x1B, "[" +strconv. Itoa (PID) + "] Bingo running ...", 0x1B) fmt. PRINTLN () return nil} ' Syscall ' package does not support the win system, which means that if you want to do development on ' windows ', you can only use virtual machines or ' Docker ' where the main function is to use ' syscall. Forkexec () ', ' fork ' a process to run this process executes the command that is here (because our original command is ' go run start.go Dev '), so here's ' args[0 ' is actually ' start.go ' Compiled binaries) and then save the ' fork ' out of the process number in ' pidfile ' so the end result is what we said in the first step of the ' Bingo run dev ' achieves the effect > case ' Restart ': This is relatively simple, through ' Pidfile ' Judging whether the program is running, if it is running, will continue to execute the function body is also relatively simple, only two lines of "' Gosyscall." Kill (PID, Syscall. SIGHUP)//kill-hup, daemon only, will exit Forkdaemon () "First line to kill this process second line to open a new process > case ' Stop ': Here is a line of code, is to kill the process # # Additional ideas in the development process, whenever there is a slightest change (such as change to the slightest controller), you need to execute the ' Bingo Run daemon restart ' command again, let the new changes take effect, verySo I developed the ' Bingo Run Watch ' command, monitored the changes, and automatically restarted the server I used [github.com/fsnotify/fsnotify] (https://github.com/fsnotify/ Fsnotify) package to implement listening "Gofunc startwatchserver (port string, handler http. Handler) {//Listen to directory changes, if there is a change, restart the service//Daemon open service, the main process block constantly scan the current directory, there are any updates, to pass the signal to the daemon, Daemon Restart Service//Open a co-operation service//monitor directory changes, there are changes to run bingo Run Daemon restartf, err: = Fsnotify. Newwatcher () if err! = Nil {panic (err)}defer f.close () dir, _: = OS. GETWD () Wddir = Dirfilewatcher = ff. Add (dir) did: = Make (chan bool) go func () {procattr: = &syscall. Procattr{env:os. Environ (), Files: []uintptr{os. STDIN.FD (), OS. STDOUT.FD (), OS. STDERR.FD ()},}_, err: = Syscall. Forkexec (OS. Args[0], []string{os. Args[0], "daemon", "Start"}, procattr) if err! = Nil {fmt. PRINTLN (Err)}} () go func () {for {select {case EV: = <-f.events:if ev. Op&fsnotify. Create = = Fsnotify. Create {fmt. Printf ("\ n%c[0;48;33m%s%c[0m", 0x1B, "[" +time. Now (). Format ("2006-01-02 15:04:05") + "]created file:" +ev. Name, 0x1B)}if ev. Op&fsnotify. Remove = = Fsnotify. Remove {fmt. Printf ("\ n%c[0; 48;31m%s%c[0m ", 0x1B," ["+time. Now (). Format ("2006-01-02 15:04:05") + "]deleted file:" +ev. Name, 0x1B)}if ev. Op&fsnotify. Rename = = Fsnotify. Rename {fmt. Printf ("\ n%c[0;48;34m%s%c[0m", 0x1B, "[" +time. Now (). Format ("2006-01-02 15:04:05") + "]renamed file:" +ev. Name, 0x1B)} else {fmt. Printf ("\ n%c[0;48;32m%s%c[0m", 0x1B, "[" +time. Now (). Format ("2006-01-02 15:04:05") + "]modified file:" +ev. Name, 0x1B)}//has changed, put in the restart array restartslice = append (restartslice, 1) case ERR: = <-f.errors:fmt. Println ("Error:", Err)}}} ()//Ready to restart Daemon Go restartdaemonserver () <-done} "first follow the ' fsnotify ' document to create a ' watcher ', Then add the Listening directory (this is just the file in the listening directory, not to listen to subdirectories) and then turn on the two-thread: 1. Listen for file changes, if there are file changes, write the number of changes to a ' slice ', which is a blocking ' for ' Loop 2. Every 1s to see the record file changes of ' slice ', if any, restart the server, and reset the listening directory, and then empty ' slice ', or skip recursively traverse subdirectories, to listen to the entire project directory effect: "' Gofunc Listeningwatcherdir (dir string) {filepath. Walk (dir, func (path string, info OS). FileInfo, err Error) Error {dir, _: = OS. GETWD () pidfile = dir + "/" + Env.get ("Pid_file") filewatcher.add (path)//cannot listen pidfile here, otherwise each reboot will cause PIDfile has an update, will constantly trigger the restart function Filewatcher.remove (pidfile) return nil})} ' This ' slice ' function is to avoid the time when multiple files are updated at once, Also restarted several times under the server to see the code to restart the server: "' Gogo func () {//Execute restart command cmd: = Exec.command (" Bingo "," Run "," daemon "," restart ") stdout, err: = cmd. Stdoutpipe () if err! = Nil {fmt. PRINTLN (Err)}defer stdout. Close () If err: = cmd. Start (); Err! = Nil {panic (err)}reader: = Bufio. Newreader (STDOUT)//real-time loop reads a line in the output stream for {lines, err2: = Reader. ReadString (' \ n ') if err2! = Nil | | Io. EOF = = err2 {break}fmt. Print (line)}if err: = cmd. Wait (); Err! = Nil {fmt. PRINTLN (Err)}opbytes, _: = Ioutil. ReadAll (stdout) fmt. Print (String (opbytes))} () "uses the ' Exec.command () ' method to get a ' cmd ' call ' cmd. Stdoutput () ' Gets an output pipeline, and the command prints out the data from this pipe and uses ' reader: = Bufio. Newreader (STDOUT) ' reads data from the pipeline with a blocked ' for ' loop, continuously reads the data from the pipe, with ' \ n ' as a line, a line of reading and printing in the console, to achieve the output effect, if these lines do not write, in the new process of the ' FMT. The data printed out by the Println () ' method will not be displayed on the console. On the sauce, finally paste the project link [Silsuer/bingo] (Https://github.com/silsuer/bingo), welcome star, Welcome PR, Welcome suggestions 152 Reads
The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion;
products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the
content of the page makes you feel confusing, please write us an email, we will handle the problem
within 5 days after receiving your email.
If you find any instances of plagiarism from the community, please send an email to:
info-contact@alibabacloud.com
and provide relevant evidence. A staff member will contact you within 5 working days.