【Golang】程序如何优雅的退出?
1. 背景
项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?
为什么需要优雅的退出?
- 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据。
- 你的协程 worker 的一个任务运行了一半,程序退出了,结果不符合预期。
如下我们以 http 服务,gRPC 服务,单独的 woker 协程为例子,一步步说明平滑关闭的写法。
2. 常见的几种平滑关闭
为了解决退出可能出现的潜在问题,平滑关闭一般做如下一些事情
- 关闭对外的监听端口,拒绝新的连接
- 关闭异步运行的协程
- 关闭依赖的资源
- 等待如上资源关闭
- 然后平滑关闭
2.1 http server 平滑关闭
原来的写法
- // startHttpServer start http server
- func startHttpServer() {
- mux := http.NewServeMux()
- // mux.Handle("/metrics", promhttp.Handler())
- if err := http.ListenAndServe(":1608", mux); err != nil {
- log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
- }
- }
带平滑关闭的写法
- // startHttpServer start http server
- func startHttpServer() {
- mux := http.NewServeMux()
- // mux.Handle("/metrics", promhttp.Handler())
- srv := &http.Server{
- Addr: ":1608",
- Handler: mux,
- }
- // 注册平滑关闭,退出时会调用 srv.Shutdown(ctx)
- quit.GetQuitEvent().RegisterQuitCloser(srv)
- if err := srv.ListenAndServe(); err != nil {
- log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
- }
- }
把平滑关闭注册到http.Server的关闭函数中
- // startHttpServer start http server
- func startHttpServer() {
- mux := http.NewServeMux()
- // mux.Handle("/metrics", promhttp.Handler())
- srv := &http.Server{
- Addr: ":1608",
- Handler: mux,
- }
- // 把平滑退出注册到http.Server中
- srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop)
- if err := srv.ListenAndServe(); err != nil {
- log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
- }
- }
2.2 gRPC server 平滑关闭
原来的写法
- // startGrpcServer start grpc server
- func startGrpcServer() {
- listen, err := net.Listen("tcp", "0.0.0.0:9999")
- if err != nil {
- log.Fatalf("Failed to listen: %v", err)
- return
- }
- grpcServer := grpc.NewServer()
- // helloBoot.GrpcRegister(grpcServer)
- go grpcServer.Serve(listen)
- defer grpcServer.GracefulStop()
- // ...
- }
带平滑关闭的写法
- // startGrpcServer start grpc server
- func startGrpcServer() {
- listen, err := net.Listen("tcp", "0.0.0.0:9999")
- if err != nil {
- log.Fatalf("Failed to listen: %v", err)
- return
- }
- grpcServer := grpc.NewServer()
- // helloBoot.GrpcRegister(grpcServer)
- go grpcServer.Serve(listen)
- // 把 grpc 的GracefulStop注册到退出事件中
- quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
- quit.WaitSignal()
- }
2.3 worker 协程平滑关闭
单独的协程启停,可以通过计数的方式注册到退出事件处理器中。
- 启动协程 增加计数
- quit.GetQuitEvent().AddGoroutine()
- 停止协程 减计数
- quit.GetQuitEvent().DoneGoroutine()
- 常驻后台运行的协程退出的条件改成退出事件是否结束的条件
- !quit.GetQuitEvent().HasFired()
- 常驻后台运行的协程若通过 select 处理 chan,同时增加退出事件的chan
- case <-quit.GetQuitEvent().Done()
- // myWorker my worker
- type myWorker struct {
- }
- // RunWorkerWithChan run Goroutine worker
- func (m *myWorker) RunWorkerWithChan() {
- // 启动一个Goroutine时,增加Goroutine数
- quit.GetQuitEvent().AddGoroutine()
- defer func() {
- // 一个Goroutine退出时,减少Goroutine数
- quit.GetQuitEvent().DoneGoroutine()
- }()
- // 退出时,此次退出
- for !quit.GetQuitEvent().HasFired() {
- select {
- // 退出时,收到退出信号
- case <-quit.GetQuitEvent().Done():
- break
- //case msg := <- m.YouChan:
- // handle msg
- }
- }
- }
- // RunWorker run Goroutine worker
- func (m *myWorker) RunWorker() {
- // 启动一个Goroutine时,增加Goroutine数
- quit.GetQuitEvent().AddGoroutine()
- defer func() {
- // 一个Goroutine退出时,减少Goroutine数
- quit.GetQuitEvent().DoneGoroutine()
- }()
- // 退出时,此次退出
- for !quit.GetQuitEvent().HasFired() {
- // ...
- }
- }
2.4 实现 io.Closer 接口的自定义服务平滑关闭
实现 io.Closer 接口的结构体,增加到退出事件处理器中
- // startMyService start my service
- func startMyService() {
- srv := NewMyService()
- // 注册平滑关闭,退出时会调用 srv.Close()
- quit.GetQuitEvent().RegisterCloser(srv)
- srv.Run()
- }
- // myService my service
- type myService struct {
- isStop bool
- }
- // NewMyService new
- func NewMyService() *myService {
- return &myService{}
- }
- // Close my service
- func (m *myService) Close() error {
- m.isStop = true
- return nil
- }
- // Run my service
- func (m *myService) Run() {
- for !m.isStop {
- // ....
- }
- }
2.5 集成其他框架怎么做
退出信号处理由某一框架接管,寻找框架如何注册退出函数,优秀的框架一般都会实现安全实现退出的机制。
如下将退出事件注册到某一框架的平滑关闭函数中
- func startMyServer() {
- // ...
- // xxx框架退出函数注册退出事件
- xxx.RegisterQuitter(func() {
- quit.GetQuitEvent().GracefulStop()
- })
- }
参考:
https://github.com/mygityf/go-library/blob/main/quit/quit.go
完。
祝玩的开心~
【Golang】程序如何优雅的退出?的更多相关文章
- 如何优雅地退出python程序
如何优雅地退出python程序 一个单模的python程序,启动之后要能够优雅地关闭.即当用户按Ctrl+C或者kill pid的时候,程序都能从容关闭.实现起来非常简单. is_running = ...
- C# Note11:如何优雅地退出WPF应用程序
前言 I should know how I am supposed to exit my application when the user clicks on the Exit menu item ...
- 从nsq中学习如何优雅的退出go 网络程序
退出运行中的程序,可以粗暴的kill -9 $PID,但这样会破坏业务的完整性,有可能一个正在在执行的逻辑半途而费,从而产生不正常的垃圾数据. 本文总结在go语言中,如何能优雅的退出网络应用,涉及的知 ...
- Android程序的隐藏与退出
转自Android程序的隐藏与退出 Android的程序无需刻意的去退出,当你一按下手机的back键的时候,系统会默认调用程序栈中最上层Activity的Destroy()方法来销毁当前Activit ...
- 情景linux--如何优雅地退出telnet
情景linux--在脚本中如何优雅地退出telnet 情景 telnet命令是TELNET协议的用户接口,它支持两种模式:命令模式和会话模式.虽然telnet支持许多命令,但大部分情况下,我们只是使用 ...
- python 如何优雅地退出子进程
python 如何优雅地退出子进程 主进程产生子进程,子进程进入永久循环模式.当主进程要求子进程退出时,如何能安全地退出子进程呢? 参考一些代码,我写了这个例子.运行之后,用kill pid试试.pi ...
- 在MacOS上使用gdb(cgdb)调试Golang程序
如果你在MacOS上使用GDB工具载入Golang程序时无法载入,这篇文章可以解决.本文不具体介绍调试的方法,网上的文章太多了就不赘述了. cgdb使用的是gdb的内核,方法和原理试用本文. 问题分析 ...
- 如何优雅的退出/关闭/重启gunicorn进程
在工作中,会发现gunicorn启动的web服务,无论怎么使用kill -9 进程号都是无法杀死gunicorn,经过我一番百度和谷歌,发现想要删除gunicorn进程其实很简单. 1. 寻找mast ...
- CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中
CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-10-28. 编写了个golang程序,用到了这个C ...
随机推荐
- 猿人学python爬虫第一题
打开网站.F12,开启devtools.发现有段代码阻止了我们调试 好的.接下来有几种解决方法 1- 绕过阻止调试方法 方法1(推荐) 鼠标放在debugger该行,左边数字行号那一列.右键选择不在永 ...
- docker更新portainer-ce2.0
前两天,我在使用portainer的过程中发现左下角提醒有新版本的portainer需要安装,google了一圈如何升级portainer,并没有找到我需要的资料,就算获取了portainer:las ...
- 能直接调试的开放API?这个API Hub绝了
01 此前时不时会有一些研发小伙伴和我诉苦,说很多企业由于人力财力限制或者需求不强,会直接购买使用第三方的开放API,这样一来, 一则由于开放项目不是量身定制的,寻找自己合适的接口也要搜索调研蛮多 ...
- Vue快速入门(一)
目录 Vue快速入门(一) 介绍 Vue.js 是什么 M-V-VM思想 安装 CDN引入 下载到本地 快速使用 双向数据绑定测试 模板语法 插值语法 指令 文本指令 v-html:让HTML渲染成页 ...
- Metalama简介2.利用Aspect在编译时进行消除重复代码
上文介绍到Aspect是Metalama的核心概念,它本质上是一个编译时的AOP切片.下面我们就来系统说明一下Metalama中的Aspect. Metalama简介1. 不止是一个.NET跨平台的编 ...
- 面试官:RabbitMQ过期时间设置、死信队列、延时队列怎么设计?
哈喽!大家好,我是小奇,一位不靠谱的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 RabbitMQ我们经常的使用, ...
- Java语言学习day13--7月14日
今日内容介绍1.循环练习2.数组方法练习 ###01奇数求和练习 * A: 奇数求和练习 * a: 题目分析 * 为了记录累加和的值,我们需要定义一个存储累加和的变量 * 我们要获取到1-100范围内 ...
- 2021.07.19 BZOJ2654 tree(生成树)
2021.07.19 BZOJ2654 tree(生成树) tree - 黑暗爆炸 2654 - Virtual Judge (vjudge.net) 重点: 1.生成树的本质 2.二分 题意: 有一 ...
- Python图像处理丨OpenCV+Numpy库读取与修改像素
摘要:本篇文章主要讲解 OpenCV+Numpy 图像处理基础知识,包括读取像素和修改像素. 本文分享自华为云社区<[Python图像处理] 二.OpenCV+Numpy库读取与修改像素> ...
- linux 下通过fork实现后台运行进程
1 # 通常建议使用双fork方法.在每个fork处,父级退出,子级继续 2 3 #!/usr/bin/env python 4 5 import time,platform 6 7 import o ...