Code前端首页关于Code前端联系我们

Golang语言如何拦截系统信号并优雅退出http服务器?

terry 2年前 (2023-09-24) 阅读数 54 #后端开发

01

简介

系统信号是类Unix系统中进程间通信的一种方法。我们可以使用命令 kill -l 显示每个系统支持的信号列表。每个信号都有一个名称和编号。我们可以使用kill命令向特定进程发送具有指定信号名称或信号编号的系统信号。

系统信号分为同步信号和异步信号。同步信号是在程序执行过程中由错误触发的信号。在 Golang 程序中,同步信号通常会转换为运行时恐慌。异步信号是系统内核或其他程序发送的信号。

有关系统信号的更多信息,感兴趣的读者朋友可以搜索相关资料并自行学习。在这篇文章中,我们主要介绍如何使用Golang语言捕获系统信号以及如何优雅地终止http服务器。

02

Golang 标准库 os/signal

在如何使用 Golang 语言捕获系统信号方面,Golang 在标准库中提供了几个函数 os/signal 。我们重点关注通知功能。

func Notify(c chan<- os.Signal, sig ...os.Signal)

os/signal包的通知功能将输入信号转发到通道c。如果没有指定信号(sig参数为空),则所有输入信号将转发到通道c。否则,仅将输入信号转发到通道c。指定的信号将被捕获。数据包

os/signal不会阻塞向通道c发送输入信号。响铃的通知功能必须确保通道c有足够的缓冲空间来跟上预期的信号速率。对于仅用于发送信号值的通道,大小为 1 的缓冲区就足够了。

接收指定信号的示例代码:

func main() {
 // Set up channel on which to send signal notifications.
 // We must use a buffered channel or risk missing the signal
 // if we're not ready to receive when the signal is sent.
 c := make(chan os.Signal, 1)
 signal.Notify(c, os.Interrupt)

 // Block until a signal is received.
 s := <-c
 fmt.Println("Got signal:", s)
}

接收所有信号的示例代码:

package main

import (
 "fmt"
 "os"
 "os/signal"
)

func main() {
 // Set up channel on which to send signal notifications.
 // We must use a buffered channel or risk missing the signal
 // if we're not ready to receive when the signal is sent.
 c := make(chan os.Signal, 1)

 // Passing no signals to Notify means that
 // all signals will be sent to the channel.
 signal.Notify(c)

 // Block until any signal is received.
 s := <-c
 fmt.Println("Got signal:", s)
}

03

优雅地上移系统信号并退出http服务器

我们可以使用的通知功能- 通过http.Server的Shutdown方法封装拦截系统信号并优雅退出http服务器。

func (srv *Server) Shutdown(ctx context.Context) error

Golang 1.8 中的新关闭方法可以在不中断任何活动连接的情况下优雅地关闭服务器。关闭的工作原理是首先关闭所有打开的侦听器,然后关闭所有空闲连接,然后等待所有活动连接空闲后再关闭服务器。

如果给定的上下文在关闭完成之前超时,Shutdown 将返回上下文的错误,否则返回从关闭服务器的侦听器返回的错误。

Serve、ListenAndServe 和 ListenAndServeTLS 在调用 Shutdown 时立即返回 ErrServerClosed。确保当关机未返回时程序不会退出。

需要注意的是,Shutdown 不会尝试关闭或等待长连接,例如 WebSockets。如有必要,Shutdown 的调用者应单独通知此类长时间运行的连接并等待它们关闭。

当调用服务器的shutdown方法时,服务器无法使用。如果再次调用Serve方法,将返回ErrServerClosed。

优雅退出http服务器的示例代码如下:

func main() {
  // 优雅退出
 http.HandleFunc("/", hello)
 server := http.Server{Addr: ":8080"}
 go func() {
  if err := server.ListenAndServe(); err != nil {
   fmt.Println("server start failed")
  }
 }()
 c := make(chan os.Signal, 1)
 signal.Notify(c, os.Interrupt)
 s := <-c
 fmt.Printf("接收信号:%s\n", s)
 ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
 defer cancel()
 if err := server.Shutdown(ctx); err != nil {
  fmt.Println("server shutdown failed")
 }
 fmt.Println("server exit")
}

func hello (w http.ResponseWriter, r *http.Request) {
 time.Sleep(5 * time.Second)
 fmt.Fprintln(w, "Hello Go!")
}

阅读上面的代码可以发现,我们捕获到系统信号SIGNINT后,睡眠模拟程序的执行还没有结束(例如,必须进行一些后续工作)。此时,系统不会直接终止应用程序进程(直接终止是系统处理信号SIGINT的默认方式),而是等待程序执行完成后,系统才终止应用程序进程。

04

总结

本文主要介绍Golang语言如何捕获系统信号,利用os/signal包的通知功能,结合❀http包 http.Server 的 Shutdown 方法可以从 http 服务器正常退出。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门