go如何实现服务优雅关闭

article/2023/6/4 15:46:22

为什么需要优雅关闭

什么叫优雅关闭?先说不优雅关闭,就是什么都不管,强制关闭进程,这会导致有些正在处理中的请求被强行中断

这样做有什么问题?

  • 用户本次请求会失败,降低用户体验
  • 没有事务的数据库操作,会产生部分成功的问题,破坏原子性
  • 某些缓服务需要定期将本地缓存刷到远程db,强制关闭会导致数据丢失

优雅关闭的核心是以下功能:

  1. 如何监听退出信号
  2. 如何拒绝新请求
  3. 如何等待进行中的请求处理完毕

监控服务退出信号

在go中使用下面的代码监听退出信号,如果c返回,说明监听到信号

不同的操作系统监听不同的退出信号

c := make(chan os.Signal, 1)
signals := []os.Signal{syscall.SIGINT,syscall.SIGTERM,syscall.SIGQUIT,
}signal.Notify(c, signals...)
<-c

拒绝新请求

go在1.8后增加了shutdown方法来,我们看看它怎么实现优雅关闭:

srv.inShutdown.setTrue()
lnerr := srv.closeListenersLocked()
srv.closeDoneChanLocked()
  • 设置inShutdown标志位
  • 关闭所有的listener
  • 关闭doneChan

这一段对应到http服务接收请求的流程:

for {rw, err := l.Accept()if err != nil {select {case <-srv.getDoneChan():return ErrServerClosed// ...       
}

一旦关闭listener,关闭doneChan后,http服务就不会再接收新的请求,直接返回

执行关闭之前的回调

for _, f := range srv.onShutdown {go f()
}

这里的回调实现得比较粗糙:

  • 没有优先级的概念,所有回调并发执行,因此需要保证回调之间没有依赖
  • 虽然回调不适合长时间运行,但Go http没有提供机制来保证这些回调一定能执行完毕,若想做到这点需要自己处理

等待处理中的请求执行完毕

设置标识位可以拒绝新的请求,但依旧在执行的请求还在处理中,需要等这些请求执行完毕

等待处理中的请求执行完毕有两种思路:

  • 等待一段固定的时间
  • 实时维护请求的计数

go选择了两种方式结合的模式,通过ctx设置一个最大的等待时间,同时不断轮询正在请求中的计数

ctx超时或者计数变为0,都会返回

timer := time.NewTimer(nextPollInterval())
defer timer.Stop()
for {if srv.closeIdleConns() && srv.numListeners() == 0 {return lnerr}select {case <-ctx.Done():return ctx.Err()case <-timer.C:timer.Reset(nextPollInterval())}
}

这里每隔一定时间检查已有请求是否执行完毕,如果执行完毕,或者外部通过ctx设置的超时到期就会返回

  • 检查间隔是多少?

    • 从1ms开始,每轮检查后倍增,最大500ms
  • 怎么判断是否执行完毕?

    • 所有的连接都关闭
    • 所有的listener都关闭

服务收到监听信号返回之前,关闭连接和listener,会被这里检查到
在这里插入图片描述

实战

func main() {// 注册路由http.Handle("/aaa", http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {time.Sleep(time.Second * 10)fmt.Println(111)}))server := http.Server{Addr:    "localhost:8080",Handler: http.DefaultServeMux,}close := make(chan int)go func() {quit := make(chan os.Signal)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)<-quitctx, cancel := context.WithTimeout(context.Background(), time.Second*30)defer cancel()err := server.Shutdown(ctx)log.Print(err)// 控制外层退出close <- 1}()err := server.ListenAndServe()fmt.Println(err)<-close
}

该代码做了下面的事:

  1. 注册一个10s才返回的路由处理函数
  2. 开子协程监听OS的退出信号,如果监听到了开始进行优雅关闭,虽多等待30s
  3. 主协程调用 server.ListenAndServe(),开始监听请求

需要注意的是,一定要在子协程中优雅关闭结束后,主协程才能退出,这里用channel控制

因为主协程发现doneChan被关闭时会马上返回,但此时主协程开的业务处理协程还在进行中,如果主协程此时退出,无法达到优雅关闭的效果

按照以下流程测试:

  1. 启动 Web 服务
  2. 在浏览器请求http://localhost:8080/aaa
  3. 过5秒后在控制台按下ctrl+c
  4. 观察控制台程序是否不会立刻结束,而是在 10s 后结束

支持强制退出

既然有优雅退出,那就有强制退出,我们假设如果按下两次ctrl+c,代表用户希望服务强制退出:

close := make(chan int, 2)
go func() {quit := make(chan os.Signal)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)<-quitgo func() {<-quitos.Exit(1)}()// ...
}()

做法很简单,收到第一个退出信号后,再开一个子协程,如果再收到退出信号,就调用os.Exit退出进程

并且close channel的容量需要为2,避免当两次退出信号过短时丢失信号

http://www.ngui.cc/article/show-862299.html

相关文章

详解python之反射机制

一、前言 def f1():print(f1)def f2():print(f2)def f3():print(f3)def f4():print(f4)a 1 import test as ssss.f1() ss.f2() print(ss.a) 我们要导入另外一个模块,可以使用import.现在有这样的需求,我动态输入一个模块名&#xff0c;可以随时访问到导入模块中的方法或者变…

【c++】STL教程

文章目录学习链接1. list 代码测试2. stack 代码测试3. queue 代码测试3.1 priority_queue 优先队列&#xff0c;最大先出3.2 改变出队优先级4. deque 代码测试5. vector 代码测试6. set 代码测试7. map 代码测试7.1 multimap 一键对多值8. sort 代码测试9.反转和随机代码测试学…

2022黑马SpringBoot跟学笔记(一)

2022黑马SpringBoot跟学笔记一SpringBoot1.SpringBoot简介1.1 SpringBoot快速入门1.1.1 开发步骤1.1.1.1 创建新模块1.1.1.2 创建 Controller1.1.1.3 启动服务器1.1.1.4 进行测试1.1.2 对比1.1.3 官网构建工程1.1.3.1 进入SpringBoot官网1.1.3.2 选择依赖1.1.3.3 生成工程1.1.4…

【Java】int和Integer的区别?为什么有包装类?

int和Integer的区别&#xff1f;为什么有包装类&#xff1f; java是一种强类型的语言&#xff0c;所以所有的属性都必须要有一个数据类型。 PS&#xff1a;java10有了局部变量类型推导&#xff0c;可以使用var来代替某个具体的数据类型&#xff0c;但是在字节码阶段&#xff0…

第6章 Mac OSX 平台安装 MongoDB教程

Mac OSX 平台install MongoDB MongoDB 提供了 OSX 平台上 64 位的install 包&#xff0c;陛下可以在官网download install 包。 download 地址&#xff1a;https://www.mongodb.net/download-center#community 从 MongoDB 3.0 版本开始只支持 OS X 10.7 (Lion) 版本及更新版本…

DaVinci:限定器

调色页面&#xff1a;限定器Color&#xff1a;Qualifier限定器 Qualifier用来选择画面上特定的像素&#xff0c;并由此创建蒙版&#xff0c;以便于二级调色。使用限定器调板左上角的“拾取器”吸管&#xff0c;在检视器画面上要选择的颜色上点击或拖动。通过检视器面板左上角的…

C语言学习笔记-枚举

C enum(枚举) 枚举是 C 语言中的一种基本数据类型&#xff0c;它可以让数据更简洁&#xff0c;更易读。 枚举是用来干嘛的&#xff1f; 枚举在C语言中其实是一些符号常量集。直白点说&#xff1a;枚举定义了一些符号&#xff0c;这些符号的本质就是int类型的常量&#xff0c;每…

Linux之(20)arp命令

Linux之(19)IP命令总结 Author&#xff1a;OnceDay Date&#xff1a;2023年2月4日 漫漫长路&#xff0c;有人对你微笑过嘛… 参考文档&#xff1a; arp(8) - Linux manual page (man7.org)、彻底搞懂系列之&#xff1a;ARP协议 - 知乎 (zhihu.com)RFC 826: An Ethernet Add…

分享五款名不见经传但是非常实用的小众软件

我们在使用一些流行的软件的时候&#xff0c;往往会忽略一些知名度不高但是功能非常强大的软件&#xff0c;有的是因为小众&#xff0c;有的是因为名不见经传&#xff0c;总之因为不出名&#xff0c;有许多的好用的软件都不为大众所知道。 1. 多窗口管理——Multrin Multrin …

GcExcel-JAVA 6.0.3-Documents for Excel

在更短的时间内生成 Excel 电子表格&#xff0c;不依赖于 Excel&#xff01; 在任何应用程序中转换、计算、格式化和解析电子表格。快速高效&#xff1a;其轻巧的尺寸意味着 Documents for Excel 针对快速处理大型 Excel 文档进行了优化使用适用于 Windows、Linux 和 Mac 的 J…