Golang实现程序优雅退出的方法详解

目录
  • 1. 背景
  • 2. 常见的几种平滑关闭
    • 2.1 http server 平滑关闭
    • 2.2 gRPC server 平滑关闭
    • 2.3 worker 协程平滑关闭
    • 2.4 实现 io.Closer 接口的自定义服务平滑关闭
    • 2.5 集成其他框架怎么做

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 协程平滑关闭

单独的协程启停,可以通过计数的方式注册到退出事件处理器中。

1.启动协程 增加计数

quit.GetQuitEvent().AddGoroutine()

2.停止协程 减计数

quit.GetQuitEvent().DoneGoroutine()

3.常驻后台运行的协程退出的条件改成退出事件是否结束的条件

!quit.GetQuitEvent().HasFired()

4.常驻后台运行的协程若通过 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()
    })
}

以上就是Golang实现程序优雅退出的方法详解的详细内容,更多关于Golang程序退出的资料请关注我们其它相关文章!

(0)

相关推荐

  • Golang信号处理及如何实现进程的优雅退出详解

    Linux系统中的信号类型 各操作系统的信号定义或许有些不同.下面列出了POSIX中定义的信号. 在linux中使用34-64信号用作实时系统中. 命令 man 7 signal 提供了官方的信号介绍.也可以是用kill -l来快速查看 列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为32 ~ 63的信号是后来扩充的,称做可靠信号(实时信号).不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会. Linux支持的标准信号有以下一

  • Golang实现for循环运行超时后自动退出的方法

    前言 for循环是用来遍历数组或数字的.用for循环遍历字符串时,也有 byte 和 rune 两种方式.第一种为byte,第二种rune.下面话不多说了,来一起看看详细的介绍吧. Golang实现for循环 package main import "fmt" func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) } 跟C语言中一样,可以让前置.后置语句为空. package ma

  • 解决go在函数退出后子协程的退出问题

    该问题来源于自己在读fabric源码时,看到的一个测试代码,在一个函数中启用协程,然后该函数退出了,由于平常没有这样处理过,以及受原有c++函数域的影响,认为函数退出,子协程应该也退出了呀. 这其实是自己对go协程的理解不到位引起的,go的协程作用域不是在某个函数中的,当然,如果那个函数是main函数,就符合要求了. 该代码为solo算法的测试代码: func goWithWait(target func()) *waitableGo { wg := &waitableGo{ done: mak

  • Golang实现超时退出的三种方式

    前段时间发现线上有个服务接口,总是间歇性告警,有时候一天两三次,有时候一天都没有. 告警的逻辑是在一个接口中异步调用了另一个HTTP接口,这个HTTP接口调用出现超时.但是我去问了负责这个HTTP接口的同学,人家说他们的接口相应都是毫秒级别,还截图监控了,有图有真相,我还能说啥. 但是,超时是确实存在的,只是请求还可能没有到人家服务那边. 这种偶发性问题不好复现,偶尔来个告警也挺烦的,第一反应还是先解决问题,思路也简单,失败后重试. 解决方法 且不谈重试策略,先说说什么时候触发重试. 我们可以在

  • Go语言 go程释放操作(退出/销毁)

    情况1: Go语言中,若在子go程中创建一个新 go程,子go程释放(销毁),新创建的go程不会随着子go程的销毁而销毁. 原因: go程共享堆,不共享栈,go程由程序员在go的代码里显示调度(释放). 实例: package main import ( "fmt" "time" "runtime" ) func test() { for i:=0;i<10;i++{ fmt.Printf("执行第%d次go程\n",i

  • Golang实现程序优雅退出的方法详解

    目录 1. 背景 2. 常见的几种平滑关闭 2.1 http server 平滑关闭 2.2 gRPC server 平滑关闭 2.3 worker 协程平滑关闭 2.4 实现 io.Closer 接口的自定义服务平滑关闭 2.5 集成其他框架怎么做 1. 背景 项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出? 为什么需要优雅的退出? 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据. 你的协程 worker

  • Golang实现快速求幂的方法详解

    今天讲个有趣的算法:如何快速求nm,其中n和m都是整数. 为方便起见,此处假设m>=0,对于m< 0的情况,求出n|m|后再取倒数即可. 另外此处暂不考虑结果越界的情况(超过 int64 范围). 当然不能用编程语言的内置函数,我们只能用加减乘除来实现. n的m次方的数学含义是:m个n相乘:n*n*n...*n,也就是说最简单的方式是执行 m 次乘法. 直接用乘法实现的问题是性能不高,其时间复杂度是 O(m),比如 329要执行29次乘法,而乘法运算是相对比较重的,我们看看能否采用什么方法将时

  • Golang控制协程执行顺序方法详解

    目录 循环控制 通道控制 互斥锁 async.Mutex 在 Go 里面的协程执行实际上默认是没有严格的先后顺序的.由于 Go 语言 GPM 模型的设计理念,真正执行实际工作的实际上是 GPM 中的 M(machine) 执行器,而我们的协程任务 G(goroutine) 协程需要被 P(produce) 关联到某个 M 上才能被执行.而每一个 P 都有一个私有队列,除此之外所有的 P 还共用一个公共队列.因此当我们创建了一个协程之后,并不是立即执行,而是进入队列等待被分配,且不同队列之间没有顺

  • 微信小程序 setData的使用方法详解

    微信小程序 setData的使用方法详解 最近在使用微信小程序的setData时,遇到了以下问题.如下: 官网文档在使用setData()设置数组对象的某个元素的属性时,是这么使用的: Page({ data: { array: [{text: 'init data'}], }, changeItemInArray: function() { this.setData({ 'array[0].text':'changed data' }) } }) 使用了 'array[0].text' : '

  • 对python捕获ctrl+c手工中断程序的两种方法详解

    日常编写调试运行程序过程中,难免需要手动停止,以下两种方法可以捕获ctrl+c立即停止程序 1.使用python的异常KeyboardInterrupt try: while 1: pass except KeyboardInterrupt: pass 2.使用signal模块 def exit(signum, frame): print('You choose to stop me.') exit() signal.signal(signal.SIGINT, exit) signal.sign

  • python为QT程序添加图标的方法详解

    Qt是一种基于C++的跨平台图形用户界面应用程序开发框架.如何跨平台?上到服务器上位机,下到嵌入式GUI,上天入地无所不能.Qt最早是由1991年由Qt Company开发,但是到2008年,Qt Company科技被诺基亚公司收购,是的,就是拥有着我们很多情怀的诺基亚.但在2012年,Qt又被Digia收购.等到了2014年,跨平台集成开发环境Qt Creator 3.1.0正式发布出来,至此,全面支持iOS.Android.WP,QT的时代开始逐步展开. 本文重点给大家介绍python为QT

  • 微信小程序自定义toast实现方法详解【附demo源码下载】

    本文实例讲述了微信小程序自定义toast实现方法.分享给大家供大家参考,具体如下: 一.微信官方默认toast toast最常见了,几乎每个App都有这样的特效,先看下小程序自带的toast效果,立马想死的心都有了~~ 微信自带toast的效果: js文件: wx.showToast({ title: '成功', icon: 'success', duration: 2000 }) 用法超级简单,但官方小程序有几个问题: 只能显示success.loading两种icon 且icon不可去除 持

  • Java程序的逻辑控制和方法详解

    一.顺序结构 从上往下执行,非常简单,不做过多赘述. 二.分支结构 1.if语句 与c语言不同的是,java的if(布尔表达式)必须是布尔表达式 eg:判断某一年是否是闰年 public static void main(String[] args) { Scanner scan = new Scanner(System.in); int year = scan.nextInt(); if((year%4==0 && year%100 !=10)||(year%400==0)) Syste

  • 关于docker容器优雅退出的问题详解

    前言 最近因为工作的原因,谈到了关于如何正确的退出运行中的docker容器,这是一个非常值得讨论的话题了.本文将给出详细的介绍,下面来一起看看吧. 容器信号使用 我们跑在容器中的程序通常想在容器退出之前做一些清理操作,比较常用的方式是监听一个信号,延迟关闭容器. docker提供了这样的功能: ╰─➤ docker stop --help Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] Stop one or more running

  • Golang利用自定义模板发送邮件的方法详解

    前言 在几周前,我开始工作于一个证券投资组合网站.虽然我只能使用 React 完成整个网站,但我决定使用 Go 来创建一个可以处理某些任务(例如发送 email)的 API 服务器,相信这是一个很好的做法. 我其中的一个页面是一个 contact 页面,目前看起来像这样: contact me 我想使用专门为此 contact 表单申请的 Gmail 帐户发送一封邮件.除了我以前用过 Javascript 发送电子邮件的事实,我没有特别选择 Go.但为什么不尝试 Go 呢?我觉得 Go 很棒.

随机推荐