How to implement server restart in Go program _golang

Source: Internet
Author: User
Tags stdin server port

Go is designed as a background language and is often used in back-end programs. The server-side program is the most common software product in the Go language. The question I'm going to address here is how to cleanly upgrade a running server-side program.
Goal:

    • Do not close existing connections: for example, we do not want to turn off programs that have been deployed in operation. But want to upgrade the service without any restrictions.
    • Socket connection to respond to user requests at any time: Any moment the closure of the socket may cause the user to return the ' connection denied ' message, which is undesirable.
    • The new process should be able to start and replace the old.

Principle

In a unix-based operating system, signal (signal) is a common method of interacting with long-running processes.

    • Sigterm: gracefully Stop the process
    • Sighup: Reboot/Reload Process (for example: Nginx, sshd, Apache)

If you receive a sighup signal, gracefully restarting the process requires the following steps:

    • The server wants to deny new connection requests, but to maintain existing connections.
    • To enable a new version of a process
    • "Give" the socket "to" the new process, and the new process begins to accept the new connection request
    • Stop the old process as soon as it has finished processing.

Stop accepting connection requests

What server programs have in common: hold a dead loop to accept a connection request:

Copy Code code as follows:
for {
Conn, err: = Listener. Accept ()
Handle Connection
}

The easiest way to jump out of this loop is to set a timeout on the socket listener when calling listener. SetTimeout (time. Now ()) after the listener. Accept () will immediately return a timeout err, which you can capture and handle:

Copy Code code as follows:
for {
Conn, err: = Listener. Accept ()
If Err!= nil {
If nerr, OK: = Err. (NET. ERR); OK && nerr. Timeout () {
Fmt. Println ("Stop Accepting connections")
Return
}
}
}

Note that this operation differs from the closing listener. This process still listens on the server port, but the connection requests are queued by the operating system's network stack, waiting for a process to accept them.
start a new process

Go provides an original type forkexec to produce a new process. You can share some messages with this new process, such as file descriptors or environment parameters.

Copy Code code as follows:
Execspec: = &syscall. procattr{
Env:os. Environ (),
Files: []uintptr{os. STDIN.FD (), OS. STDOUT.FD (), OS. STDERR.FD ()},
}
Fork, err: = Syscall. Forkexec (OS. ARGS[0], OS. Args, Execspec)
[...]

You will find that the process uses exactly the same parameters as the OS. Args started a new process.
send socket to child process and restore it

As you saw earlier, you can pass the file descriptor to the new process, which requires some Unix magic (everything is a file) and we can send the socket to the new process so that the new process can use it and receive and wait for the new connection.

But the fork-execed process needs to know that it has to get the socket from the file instead of creating a new one (some might already be in use because we haven't disconnected the existing listening). You can do it any way you want, most commonly through environment variables or command-line flags.

Copy Code code as follows:
Listenerfile, err: = Listener. File ()
If Err!= nil {
Log. Fatalln ("Fail to get Socket file descriptor:", err)
}
LISTENERFD: = LISTENERFILE.FD ()

Set a flag for the new process START process
Os. Setenv ("_graceful_restart", "true")

Execspec: = &syscall. procattr{
Env:os. Environ (),
Files: []uintptr{os. STDIN.FD (), OS. STDOUT.FD (), OS. STDERR.FD (), LISTENERFD},
}
Fork exec The new version of your server
Fork, err: = Syscall. Forkexec (OS. ARGS[0], OS. Args, Execspec)

Then at the beginning of the program:

Copy Code code as follows:
var listener *net. TcpListener
If OS. Getenv ("_graceful_restart") = = "true" {
The second argument should is the filename of the file descriptor
However, a socker is isn't a named file but we should fit the interface
of the OS. NewFile function.
FILE: = os. NewFile (3, "")
Listener, err: = Net. Filelistener (file)
If Err!= nil {
Handle
}
var bool ok
Listener, OK = listener. (*net. TcpListener)
If!ok {
Handle
}
} else {
Listener, err = Newlistenerwithport (12345)
}

The file description is not randomly selected as 3, because the UIntPtr slice has been sent fork, listening for the index 3. Note the implicit declaration problem.
last step, wait for the old service connection to stop

So far, we have passed it to another process that is running correctly, and the last operation for the old server is to wait for its connection to close. Sync is provided in the standard library. Waitgroup structure, using go to implement this function is simple.

Each time you receive a connection, add 1 to the Waitgroup, and then we reduce the counter by one when it completes:

Copy Code code as follows:
for {
Conn, err: = Listener. Accept ()

Wg. ADD (1)
Go func () {
HANDLE (conn)
Wg. Done ()
}()
}

As for waiting for the end of the connection, you only need the WG. Wait (), because there is no new connection, we are waiting for the WG. Done () has been invoked by all running handler.
Bonus: Do not wait indefinitely, given a limited amount of time

Copy Code code as follows:
Timeout: = time. Newtimer (time. Minute)
Wait: = Make (chan struct{})
Go func () {
Wg. Wait ()
Wait <-struct{}{}
}()

Select {
Case <-timeout. C:
Return Waittimeouterror
Case <-WAIT:
return Nil
}

Complete example

The code snippets in this article are all extracted from this complete example: https://github.com/Scalingo/go-graceful-restart-example
Conclusions

Socket delivery with Forkexec is indeed an effective way to do without interfering with the update process, and at the maximum time, the new connection waits a few milliseconds-for the service's startup and recovery socket, but this is a short time.

Related Article

Contact Us

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.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.