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】程序如何优雅的退出?的更多相关文章

  1. 如何优雅地退出python程序

    如何优雅地退出python程序 一个单模的python程序,启动之后要能够优雅地关闭.即当用户按Ctrl+C或者kill pid的时候,程序都能从容关闭.实现起来非常简单. is_running = ...

  2. C# Note11:如何优雅地退出WPF应用程序

    前言 I should know how I am supposed to exit my application when the user clicks on the Exit menu item ...

  3. 从nsq中学习如何优雅的退出go 网络程序

    退出运行中的程序,可以粗暴的kill -9 $PID,但这样会破坏业务的完整性,有可能一个正在在执行的逻辑半途而费,从而产生不正常的垃圾数据. 本文总结在go语言中,如何能优雅的退出网络应用,涉及的知 ...

  4. Android程序的隐藏与退出

    转自Android程序的隐藏与退出 Android的程序无需刻意的去退出,当你一按下手机的back键的时候,系统会默认调用程序栈中最上层Activity的Destroy()方法来销毁当前Activit ...

  5. 情景linux--如何优雅地退出telnet

    情景linux--在脚本中如何优雅地退出telnet 情景 telnet命令是TELNET协议的用户接口,它支持两种模式:命令模式和会话模式.虽然telnet支持许多命令,但大部分情况下,我们只是使用 ...

  6. python 如何优雅地退出子进程

    python 如何优雅地退出子进程 主进程产生子进程,子进程进入永久循环模式.当主进程要求子进程退出时,如何能安全地退出子进程呢? 参考一些代码,我写了这个例子.运行之后,用kill pid试试.pi ...

  7. 在MacOS上使用gdb(cgdb)调试Golang程序

    如果你在MacOS上使用GDB工具载入Golang程序时无法载入,这篇文章可以解决.本文不具体介绍调试的方法,网上的文章太多了就不赘述了. cgdb使用的是gdb的内核,方法和原理试用本文. 问题分析 ...

  8. 如何优雅的退出/关闭/重启gunicorn进程

    在工作中,会发现gunicorn启动的web服务,无论怎么使用kill -9 进程号都是无法杀死gunicorn,经过我一番百度和谷歌,发现想要删除gunicorn进程其实很简单. 1. 寻找mast ...

  9. CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中

    CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-10-28. 编写了个golang程序,用到了这个C ...

随机推荐

  1. Hyperledger Fabric定制联盟链网络工程实践

    总体来看,网络上成体系的可用的 Fabric 教程极少--不是直接在 Fabric 官网复制内容大谈基础理论就是在描述一个几乎无法复现的项目实践,以至于学习 Fabric 的效率极低,印象最深刻的就是 ...

  2. 微信小程序循环列表点击每一个单独添加动画

    首先,咱们看一下微信小程序动画怎么实现,我首先想到的是anime.js,但是引入之后用不了,微信小程序内的css也无法做到循环的动态,我就去找官方文档看看有没有相应的方法,哎,还真有 点击这里查看 微 ...

  3. 2021.08.09 P5018 对称二叉树(树形结构)

    2021.08.09 P5018 对称二叉树(树形结构) [P5018 NOIP2018 普及组] 对称二叉树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 求一棵子树,关 ...

  4. Docker极简入门:使用Docker-Compose 运行网站浏览量统计Demo

    Docker-Compose 是一个可以对 Docker 容器集群的快速编排的工具,能够减轻您心智和手指的负担. 简单的来说 Docker-Compose 就是将你运行多个容器的命令编写到了一起,类似 ...

  5. Java基础语法Day_04

    第11节 开发工具-IDEA day04_01_集成开发环境IDE的概述 day04_02_IntelliJ-IDEA的安装 day04_03_IDEA的项目结构 day04_04_IDEA的Hell ...

  6. python数据处理-matplotlib入门(2)-利用随机函数生成变化图形2

    鉴于上一篇中最后三个问题: 1.上述程序是否能进行优化(比如功能相同的) 2.创建三个3个实例,用了3个语句,能否建一个函数,只输入一个数n,就自动创建n个实例?同时,每个实例的num_times随机 ...

  7. Java安全之SnakeYaml反序列化分析

    Java安全之SnakeYaml反序列化分析 目录 Java安全之SnakeYaml反序列化分析 写在前面 SnakeYaml简介 SnakeYaml序列化与反序列化 常用方法 序列化 反序列化 Sn ...

  8. Hive 3.x 配置&详解

    Hive 1. 数据仓库概述 1.1 基本概念 数据仓库(英语:Data Warehouse,简称数仓.DW),是一个用于存储.分析.报告的数据系统. 数据仓库的目的是构建面向分析的集成化数据环境,分 ...

  9. 字符编码,存储引擎,MySQL字段类型,MySQL字段约束条件

    字符编码 查看MySQL默认编码命令:\s """ 如果是5.X系列 显示的编码有多种 latin1 gbk 如果是8.X系列 显示的统一是utf8mb4 utf8mb4 ...

  10. 使用CSS实现《声生不息》节目Logo

    声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 背景 <声生不息> 是芒果TV.香港电视广播有限公司和湖南卫视联合推 ...