Go程序中怎么重启服务器,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
原理
在基于Unix的操作系统中,signal(信号)是与长时间运行的进程交互的常用方法.
SIGTERM: 优雅地停止进程
SIGHUP: 重启/重新加载进程 (例如: nginx, sshd, apache)
如果收到SIGHUP信号,优雅地重启进程需要以下几个步骤:
服务器要拒绝新的连接请求,但要保持已有的连接。
启用新版本的进程
将socket“交给”新进程,新进程开始接受新连接请求
旧进程处理完毕后立即停止。
停止接受连接请求
服务器程序的共同点:持有一个死循环来接受连接请求:
复制代码 代码如下:
for {
conn, err := listener.Accept()
// Handle connection
}
跳出这个循环的最简单方式是在socket监听器上设置一个超时,当调用listener.SetTimeout(time.Now())后,listener.Accept()会立即返回一个timeout err,你可以捕获并处理:
复制代码 代码如下:
for {
conn, err := listener.Accept()
if err != nil {
if nerr, ok := err.(net.Err); ok && nerr.Timeout() {
fmt.Println(“Stop accepting connections”)
return
}
}
}
注意这个操作与关闭listener有所不同。这样进程仍在监听服务器端口,但连接请求会被操作系统的网络栈排队,等待一个进程接受它们。
启动新进程
Go提供了一个原始类型ForkExec来产生新进程.你可以与这个新进程共享某些消息,例如文件描述符或环境参数。
复制代码 代码如下:
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)
[…]
你会发现这个进程使用完全相同的参数os.Args启动了一个新进程。
发送socket到子进程并恢复它
正如你先前看到的,你可以将文件描述符传递到新进程,这需要一些UNIX魔法(一切都是文件),我们可以把socket发送到新进程中,这样新进程就能够使用它并接收及等待新的连接。
但fork-execed进程需要知道它必须从文件中得到socket而不是新建一个(有些兴许已经在使用了,因为我们还没断开已有的监听)。你可以按任何你希望的方法来,最常见的是通过环境变量或命令行标志。
复制代码 代码如下:
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)
然后在程序的开始处:
复制代码 代码如下:
var listener *net.TCPListener
if os.Getenv("_GRACEFUL_RESTART") == "true" {
// The second argument should be the filename of the file descriptor
// however, a socker is not 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)
}
文件描述没有被随机的选择为3,这是因为uintptr的切片已经发送了fork,监听获取了索引3。留意隐式声明问题。
最后一步,等待旧服务连接停止
到此为止,就这样,我们已经将其传到另一个正在正确运行的进程,对于旧服务器的最后操作是等其连接关闭。由于标准库里提供了sync.WaitGroup结构体,用go实现这个功能很简单。
每次接收一个连接,在WaitGroup上加1,然后,我们在它完成时将计数器减一:
复制代码 代码如下:
for {
conn, err := listener.Accept()
wg.Add(1)
go func() {
handle(conn)
wg.Done()
}()
}
至于等待连接的结束,你仅需要wg.Wait(),因为没有新的连接,我们等待wg.Done()已经被所有正在运行的handler调用。
Bonus: 不要无限制等待,给定限量的时间
复制代码 代码如下:
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
}
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。