目录

Go程序如何优雅退出

背景

很多人觉得“程序退出就是停掉进程”,但对后端服务来说,粗暴退出会引发一系列问题。先明确两个关键概念:

  • 粗暴退出:用kill -9、断电、进程崩溃等方式强制终止程序,会导致正在执行的任务中断、数据库连接未关闭、文件句柄泄露,甚至数据一致性问题(比如支付场景的“扣钱未到账”);
  • 优雅退出:收到退出信号后,程序主动执行“停止接收新请求→等待正在处理的请求完成→释放资源(关闭连接/文件)→终止进程”的流程,全程无数据丢失、无资源泄露。

优雅的退出 Go 程序:需要先关闭数据库连接、关闭 redis 连接、等待协程执行、清理数据等等…

实现

通过监听程序信号 ctrl + c 优雅退出,代码实现如下:

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {

    //TODO 执行业务代码

    //监听退出序号
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        sig := <-sigs
        log.Println(sig)
        done <- true
    }()
    log.Println("Server Start Awaiting Signal")
    <-done
     // 执行清理操作
    cleanup()
    log.Println("Exiting")
}

原理

操作系统信号机制

┌───────────────┐       ┌───────────────┐
│  用户按下Ctrl+C │─────>│ 生成SIGINT信号  │
└───────────────┘       └───────────────┘
          ↓
┌───────────────────────────────┐
│ Go程序通过signal.Notify捕获信号 │
└───────────────────────────────┘
信号名称 默认行为 触发场景 Go可否捕获
​SIGINT​ 终止进程 Ctrl+C / kill -2
​SIGTERM​ 终止进程 kill默认 / 容器停止
​SIGKILL​ 强制终止 kill -9 / 系统OOM
​SIGHUP​ 终止进程 终端断开 / 守护进程重载配置
​SIGQUIT​ 核心转储 Ctrl+\

常见问题

Q1、为什么有时候捕获不到?

操作系统强制终止进程 / 内存溢出被容器强制终止,不经过应用程序处理流程

Q2、协程里有无限循环(比如for { … }),怎么触发退出?

必须在循环里加入select { case <-ctx.Done(): return; default: ... },让协程能监听退出信号


如果大家在生产环境遇到特殊的退出场景(比如分布式服务退出),欢迎在评论区交流!

版权声明

未经授权,禁止转载本文章。
如需转载请保留原文链接并注明出处。即视为默认获得授权。
未保留原文链接未注明出处或删除链接将视为侵权,必追究法律责任!

本文原文链接: https://fiveyoboy.com/articles/go-signal-exist/

备用原文链接: https://blog.fiveyoboy.com/articles/go-signal-exist/