Go项目中关于优雅关闭的那些事

简介: 如何实现简单的优雅关闭

优雅关闭,是使Go Web项目变得可靠的重要一环。

目录

为什么需要优雅关闭

小demo

信号:

官方源码:


为什么需要优雅关闭

想象一下这个场景:你的服务器正在处理一些用户的请求,这时你需要重启或关闭服务(例如发布新版本)。你直接用 Ctrl+C 或者 kill -9 很粗暴的终止进程,会发生什么?

  • 正在处理的请求会被突然中断,用户可能看到错误。
  • 数据库操作可能只完成一半,导致数据不一致。
  • 一些重要的清理工作(如关闭文件、释放连接)无法进行。

而优雅关闭,就是为了解决这个问题。

如:停止接收新的请求、完成所有正在进行的请求、释放资源、最后安全退出什么的....

小demo

package main
import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
    "github.com/gin-gonic/gin"
)
func main() {
    // 1. 创建Gin路由引擎
    router := gin.Default()
    // 2. 定义一个测试路由,我们让它延迟5秒响应,模拟一个长时间运行的请求
    router.GET("/", func(c *gin.Context) {
        time.Sleep(5 * time.Second) // 模拟耗时操作
        c.JSON(http.StatusOK, gin.H{
            "message": "请求处理完成!",
        })
    })
    // 3. 创建HTTP Server,并注入Gin引擎
    srv := &http.Server{
        Addr:    ":8080",
        Handler: router,
    }
    // 4. 在一个新的goroutine里启动Server
    go func() {
        log.Println("服务器正在启动,监听地址: http://localhost:8080")
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("服务器启动失败:%s\n", err)
        }
    }()
    // 5. 等待中断信号以优雅地关闭服务器(设置 10 秒的超时时间)
    // 创建一个接收信号的通道
    quit := make(chan os.Signal, 1)
    // signal.Notify会监听指定的系统信号,并发送到quit通道
    // kill 默认会发送 syscall.SIGTERM 信号
    // kill -2 发送 syscall.SIGINT 信号(我们常用的Ctrl+C)
    // kill -9 发送 syscall.SIGKILL 信号,但无法捕获,所以不需要添加
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit // 阻塞在这里,直到有信号写入quit通道
    log.Println("收到关闭信号,正在优雅地关闭服务器...")
    // 6. 创建一个带有超时的context,用于通知服务器还有10秒时间完成当前请求
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel() // 在函数返回前执行,释放资源
    // 7. 调用Shutdown方法,传入超时上下文
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("服务器强制关闭:", err)
    }
    log.Println("服务器已安全退出")
}

image.gif

信号:

本demo中,需要掌握信号(signal)。

信号的本质就是操作系统于进程之间的 “通知机制” 。

用来告诉进程进行特定的操作(如:退出、暂停、重启...啥的)

每个信号,都有特定的编号和含义,就如下:

常用信号表:

信号名 信号编号 kill 命令写法 含义与默认行为 是否可被捕获? 常见场景
SIGINT 2 kill -2 PID 或 Ctrl+C 中断信号(“优雅请求退出”) 是 用户按 Ctrl+C 终止前台进程
SIGTERM 15 kill PID(默认)或 kill -15 PID 终止信号(“友好退出请求”) 是 系统 / 工具默认的 “停止进程” 信号
SIGKILL 9 kill -9 PID 强制杀死信号(“不可抗拒的死亡命令”) 否 进程卡死时强制清理

注意,不是所有信号都可以被捕获的,如kill -9 ,根本捕获不到,因为进程会直接被杀死。

平时咱们项目正常关闭,都会发送SIGTERM(kill -15)。只有当进程对-15没响应时,才会-9强制杀死。

当然

   signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

   <-quit // 阻塞在这里,直到有信号写入quit通道

这行代码告诉Go,当收到SIGINT(Ctrl+C),或SIGTERM(kill命令默认信号)时,

会把信号发送到quit。

代码不在阻塞,可以正常进行下去,最终来到Context这里。

此后可以通过上下文设置context:

1、可以限制优雅关闭的最大等待时间

2、支持主动取消优雅关闭

官方源码:

package main
import (
  "context"
  "log"
  "net/http"
  "os"
  "os/signal"
)
func main() {
  var srv http.Server
  idleConnsClosed := make(chan struct{})
  go func() {
    sigint := make(chan os.Signal, 1)
    signal.Notify(sigint, os.Interrupt)
    <-sigint
    // We received an interrupt signal, shut down.
    if err := srv.Shutdown(context.Background()); err != nil {
      // Error from closing listeners, or context timeout:
      log.Printf("HTTP server Shutdown: %v", err)
    }
    close(idleConnsClosed)
  }()
  if err := srv.ListenAndServe(); err != http.ErrServerClosed {
    // Error starting or closing listener:
    log.Fatalf("HTTP server ListenAndServe: %v", err)
  }
  <-idleConnsClosed
}

image.gif

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

官方文档翻译:

关机将在不中断任何活动连接的情况下优雅地关闭服务器。Shutdown的工作原理是首先关闭所有打开的侦听器,然后关闭所有空闲连接,然后无限期地等待连接返回空闲状态,然后关闭。如果提供的上下文在关闭完成之前过期,则shutdown返回上下文的错误,否则返回关闭服务器的底层侦听器所返回的任何错误。

当Shutdown被调用时,Serve, ServeTLS, ListenAndServe和ListenAndServeTLS会立即返回ErrServerClosed。确保程序没有退出,而是等待Shutdown返回。

关闭不试图关闭或等待被劫持的连接,如WebSockets。如果需要的话,Shutdown的调用者应该单独通知这些长寿命连接关闭,并等待它们关闭。看到服务器。注册关机通知函数的方法。

一旦在服务器上调用Shutdown,它可能不会被重用;以后对Serve等方法的调用将返回ErrServerClosed。


借鉴:

https://pkg.go.dev/net/http#Server.Shutdown


感谢支持,后续会继续深入讲解

目录
相关文章
|
8月前
|
设计模式 Java Go
Go中的switch的8种使用场景:没有你想的那么简单
在 Go 中灵活使用 switch,可以使代码更清晰、更易维护。 switch 是 Go 中不可或缺的控制结构之一
1068 0
|
8月前
|
存储 缓存 安全
Go map 底层原理
虽然大家天天都在用 `map`,但很多人对它的理解只停在“查得快”“底层是哈希表”“桶里有 8 个槽位”这几句。或许跟别人吹牛的时候,还有几分用处;但真到线上排查延迟抖动、锁竞争、内存占用、热点键冲突,这点认识往往是不够的。
529 1
|
8月前
|
存储 缓存 安全
一文带你读懂 Go 1.24 map 重构了什么?
本文聚焦 Go 1.24 map 底层重构,解释它如何从旧版 bucket + overflow 方案,演进为 Swiss Table + 局部 split 的新结构,以及它所带来的性能提升。
452 1
一文带你读懂 Go 1.24 map 重构了什么?
|
8月前
|
算法 机器人 测试技术
动态规划入门详解
等待了好久好久,今天终于可以对动态规划动手了(☆▽☆)
328 3
|
8月前
|
缓存 安全 测试技术
GO项目开发规范文档解读
本篇博客的目的,更多是为快速翻阅与回忆使用。
372 1
|
8月前
|
存储 缓存 编译器
依赖管理(go mod)
上部分,我们介绍了使用GOPATH的痛点:多个项目无法共享同一个GOPATH,虽然vendor仍然无法多个项目无法共享同一个GOPATH,但是它提供了一种机制,让项目的依赖隔离而不互相干扰。版本格式 v(major).(minorr).(patch)中的 major 指的是大版本,minor 指的是小版本,patch 指的是补丁版本。从GOPATH到vendor,再到Go Module,这是一个不断演进的过程,了解每代依赖管理的痛点,能够更好的明白下一代依赖管理的初衷。
346 1
|
8月前
|
机器学习/深度学习 算法 5G
基于透镜天线阵列的毫米波大规模多输入多输出(MIMO)系统可靠波束空间信道估计研究附Matlab代码
​ ✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室 👇 关注我领取海量matlab电子书和数学建模资料 🍊个人信条:格物致知,完整Matlab代码获取及仿真咨询内容私信。 🔥 内容介绍 研究背景 基本原理 ⛳️ 运行结果 📣 部分代码 function nc = noCollision(n2, n1, o)     A = [n1(1) n1(2)];     B = [n2(1) n2(2)];    
196 5
|
8月前
|
机器学习/深度学习 存储 测试技术
DFS/BFS专练-搞定图论基础!(从海岛问题过渡至图论基础应用C/C++)
从海岛问题到图论基础:7 大经典场景 + N 个实战案例,彻底吃透 DFS/BFS 核心逻辑! 遇事不决,直接上纸,画图。
330 3
|
8月前
|
算法 自动驾驶 安全
【路径规划】在二维和三维空间中实现RRT_算法,根据障碍物位置和尺寸实现的避障功能附matlab代码
✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室 👇 关注我领取海量matlab电子书和数学建模资料 🍊个人信条:格物致知,完整Matlab代码获取及仿真咨询内容私信。 🔥 内容介绍 一、路径规划与避障的重要性 在机器人运动控制、自动驾驶、无人机导航等众多领域,路径规划与避障是关键技术。例如,在工业机器人的操作场景中,机器人需要在复杂的工作空间内,从起始点运动到目标点,同时避开各种障碍物,如生产设备、固定支架等,以确保生产任务
339 0
|
8月前
|
文字识别 NoSQL API
Go-Zero微服务实战:高并发场景下的学生认证系统设计与实现
在校园社交等垂直领域应用中,"学生身份认证"是构建信任体系的核心基石。本文将会基于 Go-Zero 微服务框架,详细拆解了一个生产级的学生认证系统实现。涵盖了 OCR 双通道故障转移、WebSocket 实时推送、事件驱动架构 (EDA)、敏感数据加密 以及 有限状态机(FSM) 的设计模式。
487 7