在Go语言中,实现程序的优雅退出是一项重要的任务,特别是在涉及到HTTP服务器、gRPC服务器、以及其他后台工作的情况下。
HTTP Server 平滑关闭
Go 1.8及以上版本提供了 http.Server
结构的 Shutdown
方法,用于平滑关闭HTTP服务器。
案例一:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// 创建一个新的 ServeMux 对象,它是HTTP请求多路复用器,用于将不同的请求路由到不同的处理函数
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
server := &http.Server{
Addr: ":8088", //监听端口
Handler: mux, //监听的处理器
}
//监听并服务HTTP请求,可以想办法开一个8088端口来占用,比如在java起一个服务
go func() {
if err := server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
// 处理监听失败的错误
// 记录错误
log.Printf("HTTP服务器失败: %v", err)
// 执行清理工作,如有必要
// 可选:尝试重启服务器
time.Sleep(10 * time.Second) //等待10秒再重启
if !attemptRestart(server) {
//os.Exit(1) 将导致程序立即退出,并返回状态码 1 表示发生了错误。在实际应用中,你可能需要根据错误的性质和程序的设计来决定是否退出程序,或者采取其他的错误恢复策略
// 优雅地退出程序
os.Exit(1)
}
}
}
}()
// 等待中断信号来优雅地关闭服务器
stop := make(chan os.Signal, 1)
// 用 signal.Notify 来监听 os.Interrupt 信号,这是用户向程序发送中断信号(如Ctrl+C)时产生的信号
signal.Notify(stop, os.Interrupt)
<-stop // 程序在此处阻塞,直到接收到一个中断信号
//当有中断信号来,创建一个带有超时的 context.Context 对象,超时时间为5秒
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// 确保在函数返回时取消这个上下文,释放相关资源
defer cancel()
//当接收到中断信号时,调用 server.Shutdown 方法并传入上面创建的 ctx 对象,以优雅地关闭服务器
if err := server.Shutdown(ctx); err != nil {
// 如果在关闭过程中出现错误
fmt.Println("处理关闭服务器时的错误")
}
}
// attemptRestart 尝试重启服务器
func attemptRestart(server *http.Server) bool {
// 这里可以添加任何需要的清理或重启前的准备工作
log.Println("正在尝试重新启动服务器。。。")
// 尝试重新启动服务器
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Printf("无法重新启动服务器: %v", err)
return false
}
log.Println("重启成功。。。")
return true
}
案例二:持续监听
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// 创建一个新的 ServeMux 对象,它是HTTP请求多路复用器,用于将不同的请求路由到不同的处理函数
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
server := &http.Server{
Addr: ":8088", //监听端口
Handler: mux, //监听的处理器
}
go func() {
// 等待中断信号来优雅地关闭服务器
stop := make(chan os.Signal, 1)
// 用 signal.Notify 来监听 os.Interrupt 信号,这是用户向程序发送中断信号(如Ctrl+C)时产生的信号
signal.Notify(stop, os.Interrupt)
<-stop // 程序在此处阻塞,直到接收到一个中断信号
//当有中断信号来,创建一个带有超时的 context.Context 对象,超时时间为5秒
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// 确保在函数返回时取消这个上下文,释放相关资源
defer cancel()
//当接收到中断信号时,调用 server.Shutdown 方法并传入上面创建的 ctx 对象,以优雅地关闭服务器
if err := server.Shutdown(ctx); err != nil {
// 如果在关闭过程中出现错误
fmt.Println("处理关闭服务器时的错误")
}
}()
//监听并服务HTTP请求,可以想办法开一个8088端口来占用,比如在java起一个服务
for {
if err := server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
// 处理监听失败的错误
// 记录错误
log.Printf("HTTP服务器失败: %v", err)
// 执行清理工作,如有必要
// 可选:尝试重启服务器
time.Sleep(5 * time.Second) //等待10秒再重启
attemptRestart(server)
//os.Exit(1) 将导致程序立即退出,并返回状态码 1 表示发生了错误。在实际应用中,你可能需要根据错误的性质和程序的设计来决定是否退出程序,或者采取其他的错误恢复策略
// 优雅地退出程序
//os.Exit(1)
}
}
}
}
// attemptRestart 尝试重启服务器
func attemptRestart(server *http.Server) bool {
// 这里可以添加任何需要的清理或重启前的准备工作
log.Println("正在尝试重新启动服务器。。。")
// 尝试重新启动服务器
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Printf("无法重新启动服务器: %v", err)
return false
}
return true
}