一文讲懂服务的优雅重启和更新

沙海 2021年6月13日09:01:22Java评论106字数 3381阅读11分16秒阅读模式
摘要

智能摘要

智能摘要文章源自JAVA秀-https://www.javaxiu.com/31500.html

我们服务重启或者是重新发布过程中,要做到新旧服务无缝切换,同时可以保障变更服务零宕机时间!对http服务来说,一般的思路就是关闭对fd的listen,确保不会有新的请求进来的情况下处理完已经进入的请求,然后退出。服务被中断时如何通知程序,然后调用Shutdown作处理呢?服务不再接受新的请求,服务活跃的请求等待处理完成,同时也等待资源关闭(数据库连接等),如有超时,强制退出。文章源自JAVA秀-https://www.javaxiu.com/31500.html

原文约 1359 | 图片 1 | 建议阅读 3 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/31500.html

一文讲懂服务的优雅重启和更新

原创 hxl 微服务实践 文章源自JAVA秀-https://www.javaxiu.com/31500.html

收录于话题文章源自JAVA秀-https://www.javaxiu.com/31500.html

#go-zero文章源自JAVA秀-https://www.javaxiu.com/31500.html

29个文章源自JAVA秀-https://www.javaxiu.com/31500.html

在服务端程序更新或重启时,如果我们直接 kill -9 杀掉旧进程并启动新进程,会有以下几个问题:文章源自JAVA秀-https://www.javaxiu.com/31500.html

  1. 旧的请求未处理完,如果服务端进程直接退出,会造成客户端链接中断(收到 RST文章源自JAVA秀-https://www.javaxiu.com/31500.html

  2. 新请求打过来,服务还没重启完毕,造成 connection refused文章源自JAVA秀-https://www.javaxiu.com/31500.html

  3. 即使是要退出程序,直接 kill -9 仍然会让正在处理的请求中断文章源自JAVA秀-https://www.javaxiu.com/31500.html

很直接的感受就是:在重启过程中,会有一段时间不能给用户提供正常服务;同时粗鲁关闭服务,也可能会对业务依赖的数据库等状态服务造成污染。文章源自JAVA秀-https://www.javaxiu.com/31500.html

所以我们服务重启或者是重新发布过程中,要做到新旧服务无缝切换,同时可以保障变更服务 零宕机时间文章源自JAVA秀-https://www.javaxiu.com/31500.html

作为一个微服务框架,那 go-zero 是怎么帮开发者做到优雅退出的呢?下面我们一起看看。文章源自JAVA秀-https://www.javaxiu.com/31500.html

优雅退出

在实现优雅重启之前首先需要解决的一个问题是 如何优雅退出文章源自JAVA秀-https://www.javaxiu.com/31500.html

对 http 服务来说,一般的思路就是关闭对 fdlisten , 确保不会有新的请求进来的情况下处理完已经进入的请求, 然后退出。文章源自JAVA秀-https://www.javaxiu.com/31500.html

go 原生中 http 中提供了 server.ShutDown(),先来看看它是怎么实现的:文章源自JAVA秀-https://www.javaxiu.com/31500.html

  1. 设置 inShutdown 标志文章源自JAVA秀-https://www.javaxiu.com/31500.html

  2. 关闭 listeners 保证不会有新请求进来文章源自JAVA秀-https://www.javaxiu.com/31500.html

  3. 等待所有活跃链接变成空闲状态文章源自JAVA秀-https://www.javaxiu.com/31500.html

  4. 退出函数,结束文章源自JAVA秀-https://www.javaxiu.com/31500.html

分别来解释一下这几个步骤的含义:文章源自JAVA秀-https://www.javaxiu.com/31500.html

inShutdown

func (srv *Server) ListenAndServe() error {    if srv.shuttingDown() {        return ErrServerClosed    }    ....    // 实际监听端口;生成一个 listener    ln, err := net.Listen("tcp", addr)    if err != nil {        return err    }    // 进行实际逻辑处理,并将该 listener 注入    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})}func (s *Server) shuttingDown() bool {  return atomic.LoadInt32(&s.inShutdown) != 0}

ListenAndServe 是http启动服务器的必经函数,里面的第一句就是判断 Server 是否被关闭了。文章源自JAVA秀-https://www.javaxiu.com/31500.html

inShutdown 就是一个原子变量,非0表示被关闭。文章源自JAVA秀-https://www.javaxiu.com/31500.html

listeners

func (srv *Server) Serve(l net.Listener) error {    ...    // 将注入的 listener 加入内部的 map 中    // 方便后续控制从该 listener 链接到的请求    if !srv.trackListener(&l, true) {        return ErrServerClosed    }    defer srv.trackListener(&l, false)   ...}

Serve 中注册到内部 listeners maplistener,在 ShutDown 中就可以直接从 listeners 中获取到,然后执行 listener.Close(),TCP四次挥手后,新的请求就不会进入了。文章源自JAVA秀-https://www.javaxiu.com/31500.html

closeIdleConns

简单来说就是:将目前 Server 中记录的活跃链接变成变成空闲状态,返回。文章源自JAVA秀-https://www.javaxiu.com/31500.html

关闭

func (srv *Server) Serve(l net.Listener) error {  ...  for {   rw, err := l.Accept()    // 此时 accept 会发生错误,因为前面已经将 listener close了   if err != nil {    select {    // 又是一个标志:doneChan    case <-srv.getDoneChan():     return ErrServerClosed    default:    }    }  }}

其中 getDoneChan 中已经在前面关闭 listener  时,对 doneChan 这个channel中push。文章源自JAVA秀-https://www.javaxiu.com/31500.html

总结一下:Shutdown 可以优雅的终止服务,期间不会中断已经活跃的链接文章源自JAVA秀-https://www.javaxiu.com/31500.html

但服务启动后的某一时刻,程序如何知道服务被中断了呢?服务被中断时如何通知程序,然后调用Shutdown作处理呢?接下来看一下系统信号通知函数的作用文章源自JAVA秀-https://www.javaxiu.com/31500.html

服务中断

这个时候就要依赖 OS 本身提供的 signal。对应 go 原生来说,signalNotify 提供系统信号通知的能力。文章源自JAVA秀-https://www.javaxiu.com/31500.html

https://github.com/tal-tech/go-zero/blob/master/core/proc/signals.go文章源自JAVA秀-https://www.javaxiu.com/31500.html

func init() {  go func() {   var profiler Stopper       signals := make(chan os.Signal, 1)   signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM)   for {    v := <-signals    switch v {    case syscall.SIGUSR1:     dumpGoroutines()    case syscall.SIGUSR2:     if profiler == nil {      profiler = StartProfile()     } else {      profiler.Stop()      profiler = nil     }    case syscall.SIGTERM:        // 正在执行优雅关闭的地方     gracefulStop(signals)    default:     logx.Error("Got unregistered signal:", v)    }   }  }()}
  • SIGUSR1 -> 将 goroutine 状况,dump下来,这个在做错误分析时还挺有用的文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • SIGUSR2 -> 开启/关闭所有指标监控,自行控制 profiling 时长文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • SIGTERM -> 真正开启 gracefulStop,优雅关闭文章源自JAVA秀-https://www.javaxiu.com/31500.html

gracefulStop 的流程如下:文章源自JAVA秀-https://www.javaxiu.com/31500.html

  1. 取消监听信号,毕竟要退出了,不需要重复监听了文章源自JAVA秀-https://www.javaxiu.com/31500.html

  2. wrap up,关闭目前服务请求,以及资源文章源自JAVA秀-https://www.javaxiu.com/31500.html

  3. time.Sleep() ,等待资源处理完成,以后关闭完成文章源自JAVA秀-https://www.javaxiu.com/31500.html

  4. shutdown ,通知退出文章源自JAVA秀-https://www.javaxiu.com/31500.html

  5. 如果主goroutine还没有退出,则主动发送 SIGKILL 退出进程文章源自JAVA秀-https://www.javaxiu.com/31500.html

这样,服务不再接受新的请求,服务活跃的请求等待处理完成,同时也等待资源关闭(数据库连接等),如有超时,强制退出。文章源自JAVA秀-https://www.javaxiu.com/31500.html

整体流程

我们目前 go 程序都是在 docker 容器中运行,所以在服务发布过程中,k8s 会向容器发送一个 SIGTERM 信号,然后容器中程序接收到信号,开始执行 ShutDown文章源自JAVA秀-https://www.javaxiu.com/31500.html

一文讲懂服务的优雅重启和更新文章源自JAVA秀-https://www.javaxiu.com/31500.html

到这里,整个优雅关闭的流程就梳理完毕了。文章源自JAVA秀-https://www.javaxiu.com/31500.html

但是还有平滑重启,这个就依赖 k8s 了,基本流程如下:文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • old pod 未退出之前,先启动 new pod文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • old pod 继续处理完已经接受的请求,并且不再接受新请求文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • new pod接受并处理新请求的方式文章源自JAVA秀-https://www.javaxiu.com/31500.html

  • old pod 退出文章源自JAVA秀-https://www.javaxiu.com/31500.html

这样整个服务重启就算是成功了,如果 new pod 没有启动成功,old pod 也可以提供服务,不会对目前线上的服务造成影响。文章源自JAVA秀-https://www.javaxiu.com/31500.html

项目地址

https://github.com/tal-tech/go-zero文章源自JAVA秀-https://www.javaxiu.com/31500.html

欢迎使用 go-zero 并 star 支持我们!文章源自JAVA秀-https://www.javaxiu.com/31500.html

微信交流群

文章源自JAVA秀-https://www.javaxiu.com/31500.html

关注『微服务实践』公众号并点击 交流群 获取社区群二维码。文章源自JAVA秀-https://www.javaxiu.com/31500.html

继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定