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

退出运行中的程序,可以粗暴的kill -9 $PID,但这样会破坏业务的完整性,有可能一个正在在执行的逻辑半途而费,从而产生不正常的垃圾数据。
本文总结在go语言中,如何能优雅的退出网络应用,涉及的知识包括:signal,channel,WaitGroup等。
从这里:https://gobyexample.com/channel-synchronization 可以简单了解到,在go中如何使用channel实现goroutines同步。
在nsq中,也使用了相同的机制,不过封装更复杂了些。我们以nsqadmin中的实现为例进行简单的分析。
代码段1(来自:https://github.com/bitly/nsq/blob/master/nsqadmin/main.go):

1
2
3
4
5
6
7
8
9
10
11
exitChan := make (chan int )
signalChan := make (chan os. Signal , 1 )
go func ( ) {
    <-signalChan
    exitChan <- 1
} ( )
signal. Notify (signalChan , syscall. SIGINT , syscall. SIGTERM )
//....
nsqadmin. Main ( )
<-exitChan
nsqadmin. Exit ( )

上面的代码正常执行后,会卡到倒数第二句的<-exitChan中,直到exitChan中有数据进入。当我们通过命令行执行:kill -s SIGINT $PID时,signalChan中收到一个信息,上面第四行代码中的goroutines会停止阻塞,继续向下执行,exitChann中加入一条数据。当exitChann中有了数据,倒数第二句也会停止阻塞,执行nsqadmin.Exit()实现优雅退出。当然上面的代码,也可以简化:

1
2
3
4
5
6
signalChan := make (chan os. Signal , 1 )
signal. Notify (signalChan , syscall. SIGINT , syscall. SIGTERM )
//....
nsqadmin. Main ( )
<-signalChan
nsqadmin. Exit ( )

至于nsq为什么没有这么做,还不太清楚。能力有限,体会不到其深意所在。
上面的例子只适用于两个goroutines之间,一个处理完业务后给exitChan写数据,主goroutines卡在exitChan上等数据。(主goroutines必须比处理业务的goroutines后退出)。
如果一个主线程下开了多个子goroutines,使用用channel的方式就不够优雅了。可以使用WaitGroup,关于WaitGroup的介绍可以参考:http://www.baiyuxiong.com/?p=913
在nsq中同样使用了WaitGroup实现退出。
代码段2(来自:https://github.com/bitly/nsq/blob/master/util/wait_group_wrapper.go)

1
2
3
4
5
6
7
8
9
10
11
type WaitGroupWrapper struct {
    sync. WaitGroup
}

func (w *WaitGroupWrapper ) Wrap (cb func ( ) ) {
    w. Add ( 1 )
    go func ( ) {
        cb ( )
        w. Done ( )
    } ( )
}

代码段3(来自:https://github.com/bitly/nsq/blob/master/nsqadmin/nsqadmin.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (n *NSQAdmin ) Main ( ) {
    httpListener , err := net. Listen ( "tcp" , n. httpAddr. String ( ) )
    if err != nil {
        n. logf ( "FATAL: listen (%s) failed - %s" , n. httpAddr , err )
        os. Exit ( 1 )
    }
    n. httpListener = httpListener
    httpServer := NewHTTPServer ( &Context {n } )
    n. waitGroup. Wrap (func ( ) {
        util. HTTPServer (n. httpListener , httpServer , n. opts. Logger , "HTTP" )
    } )
    n. waitGroup. Wrap (func ( ) { n. handleAdminActions ( ) } )
}

func (n *NSQAdmin ) Exit ( ) {
    n. httpListener. Close ( )
    close (n. notifications )
    n. waitGroup. Wait ( )
}

在代码段2中,对waitGroup进行了简单封装,开启goroutines前计数加1,执行完计数减1。
代码段3中,Main()方法里,调用了两次waitGroup.Wrap()方法,参考代码段2可以知道,这会启动两个子goroutines,并使waitGroup计数加2.而子goroutines中使用了http包监听网络服务,阻塞goroutines,使得计数减1的操作不能被调用。
在我们的代码段1中可以知道,命令行发送了kill以后,会执行代码段3的Exit()方法,当方法里的n.httpListener.Close()被调用后,网络服务中断,代码段2中的阻塞就会停止,进而执行计数减1的,当两个子goroutines中计数各减1以后。Exit()方法中的n.waitGroup.Wait()就会继续执行,主线程结束,程序退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值