要优雅退出吗?dubbogo给你

dubbogo

Apache Dubbo是由阿里开源的一个RPC框架,而dubbogo则是相对应的go语言版本:

之前dubbogo一直没有优雅退出的机制,终于有小伙伴忍不住了强烈要求我们实现这个部分。艰难摸鱼了两周之后,我才把这个搞完,该功能的PRhttps://github.com/apache/dubbo-go/pull/255

当我们讨论优雅退出的时候,最基本的要求是自动无损停机。它同时强调了自动和无损两个方面。

首先是自动,而与自动对应的则是手动了。手工介入的缺陷是显而易见的,它要求我们在应用下线的时候手动摘掉流量。这一步可以通过网关、负载均衡或者注册中心来实现。它还容易忘和出错,如果这个东西还要求到运维身上,那就真的是下个线都得求爷爷告奶奶,开发体验十分不好。

而无损,关键则是,正在执行的事情要能执行完。这个“事情”含义会非常广泛,比如说发出去的请求我要能收回响应;收到的请求要执行完毕并且给人家返回响应……如果更加严格的来说,那么本地启动的定时任务,或者分布式事务,都应该在完成之后才能关机。

设计

我们先来看一下,一般情况下关机会发生什么:

这里面可以看出来,如果没有优雅退出机制的话,服务器是很任性的,谁都不说咔嚓一下关了。

然后注册中心隔了一小会之后,通过心跳检测或者监听到服务器跪了,心里卧槽一句之后,就赶紧通知客户端。这个过程,客户端这个傻白甜还是使劲发请求。

它收到注册中心的通知之后,就懵逼了,心里一万句MMP飘过之后,终于接受了自己刚才发出去的一些请求,收不到响应了的事实。

如果要是客户端咔嚓一下关机呢?

所以我们可以看到,不论是客户端突然关机还是服务端突然关机,都会造成问题。

于是我们的优雅关机,就是要解决这么一个问题。从前面的那些图可以看到,如果要优雅退出,关键在于好好商量:

如果客户端关机呢?那就更加简单了,稍微停一下,把发出去的请求的响应收完再关机。

现实的情况是,一个节点,往往既是服务端,也是客户端。这种情况下该怎么搞?首先在发出关机的信号后,它肯定不能关掉,至少要等到已接受的请求处理完成,才能关掉。往往是,处理一个请求会导致它作为客户端发起一个调用。于是我们可以看到,在该节点既是服务端,又是客户端的情况下,要先关闭作为服务端的功能,这样才能防止因为要处理新的请求而不得不作为客户端向别的服务器发起请求。

所以最终步骤就是:

  1. 告知注册中心,即将关闭,此时等待并处理请求;
  2. 注册中心通知别的客户端,别的客户端停止发送新请求,等待已发请求的响应;
  3. 节点处理完所有接收到的请求并且返回响应后,释放作为服务端相关的组件和资源;
  4. 节点释放作为客户端的组件和资源;

实现

如何知道关机?

不管我们如何实现优雅关机,第一个要解决的就是,我怎么知道这个节点要关机了?在Java虚拟机里面,有Runtime提供了addShutdownHook的方法:

golang就没这个便利。好在golang提供了信号(Signal)机制。在golang里有一个os/signal的包,它是一个对操作系统信号的封装——所以这是一个操作系统相关的东西,不过我这里只考虑Unix-Like系统,毕竟我还是不怎么听说有人在Windows上部署golang微服务的[手动狗头]。

golang的文档(https://golang.org/pkg/os/signal/)里面有很详细的描述。我大概总结一下:

  1. SIGKILLSIGSTOP可能捕捉不到;
  2. SIGHUPSIGINTSIGTERM会导致系统退出;
  3. SIGQUITSIGILLSIGTRAPSIGABRTSIGSTKFLTSIGEMTSIGSYS会导致系统退出,并且打印此时的栈;

所以我们只需要监听这些信号的处理就可以了。

释放资源步骤

前面我们讨论了关机释放资源所需要按序执行的步骤,那么落地到dubbogo里面该如何实现呢?

dubbogo的源码能够发现,关键的组件就是RegistryProtocol

其中Protocol从逻辑上来说,可以分成供Provider使用的Protocol和供Consumer使用的Protocol。当然,Protocol也可能同时提供两者使用。因此我们考虑到这种情况,在销毁ProviderProtocol的时候,要把共用的那些Protocol剔除出来。

按照我们的预先分析的步骤,释放资源的步骤应该是:

  1. 销毁所有的Registry实例,这也就是从注册中心里面注销。这个过程,客户端因为有监听注册中心的事件,所以很快就能知道某个服务器已经不可用;
  1. 在步骤1之后,理论上来说所有的客户端都不会再发请求过来了。但是还有很多时候,一个是注册中心通知客户端的延时,二是不同的客户端可能有一些奇怪的缓存机制,再一个就是此时正在发送的请求。这几种情况下,还会有部分请求到达服务器,所以服务器还需要接收这部分请求然后处理掉,因而要等待一段时间;
  1. 在步骤2之后,绝大部分情况下,服务端就可以直接销毁掉扮演ProviderProtocol了。然而,如果步骤2等待时间过短,或者说客户端和注册中心就服务器下线这个事情达成一致的时间太长,那么这个阶段还会收到请求。这个时候我们就只能拒绝请求了。此时,我们还要判断一下,当前正在处理的请求处理完了没有,如果处理完了,或者等了一段时间之后都还没处理完,就进入下一个阶段;
  1. 在这个步骤,服务器才真的摧毁作为ProviderProtocol

  2. 经过步骤4,服务器还可能处在一种“虽然我无法响应别人,但是我还在处理点事情,我要等别人的响应”的状态中,所以这个时候我们再稍微停下来等一下,如果所有的响应都收到请求了,或者超时,进入下一个阶段;

  1. 摧毁掉剩下的Protocol
  2. 理论上来说,经过步骤6,在框架层面上,所有的资源都释放了。但是这个时候我们要考虑到开发者可能在此时需要释放他创建的资源,因此我们要提供一个回调机制,允许他们在这个时间节点回收资源;

我们的源码里面很容易看出来这些步骤:

如何确定每一步的超时时间

在实现这个优雅退出的时候,有一个参数非常关键,就是每一步的退出时间step_timeout,它代表的是,在前面提及的每一个步骤,如果需要停下来等待,那么会在多久以后超时,结束等待。

在大大大大大多数情况下,设置这个时间只需要考虑第一个停下来等待的步骤,即服务端在宣称了自己要停机,并且销毁了Registry之后停下来等待新请求的时长。也就是执行方法waitAndAcceptNewRequests的超时时间。

有一个简单的式子可以描述这个时间:客户端收到注册中心通知的时长+请求响应时长。

第一个“客户端收到注册中心通知的时长”很好理解,但是也比较难估算。这主要取决于注册中心和客户端缓存机制。我个人经验是使用ZK的情况下,一般不会超过1秒。

第二个“请求响应时长”最复杂了。首先,这是一个从客户端观察的值。也就是说,它不是我们监控到的服务端的服务响应时间,而是从客户端发出一个请求到它收到完全响应的时长。于服务端而言,大概是“请求传输时长+服务响应时长+响应传输时长”。

然后我们又会面临一个问题,一个服务端往往提供多个服务,我该取哪个服务的请求响应时长?答案是取决于你具体的业务和你的期望。开发者可以基于自己的服务的重要性,取比较重要的服务的999线;又或者全部服务一起考虑,取999线。这里我比较不建议使用平均线,因为平均线意味着有很多的请求无法再这个时间内返回响应。

另外一种比较罕见的选择是,使用定时任务的执行时间,或者事务——尤其是分布式事务——的完成时间。

核心就是,你觉得哪个东西最重要,你就用那个东西的执行时间。

上面的逻辑也适用于单纯是Consumer的应用。

大多数情况下,step_timeout默认值10秒足以应付了。

未实现部分

特殊回调

这个小标题有点不太准确。大家注意到的是,我只在所有框架资源都被销毁之后才会回调开发者注册的回调。这个时候就有这么一些问题:

  1. 如果开发者在自定义的回调里面希望用到dubbogo的功能,特别是发起远程调用,那么显然是不可能的——虽然我也觉得不会有人会这么干;
  2. 如果开发者的回调希望按照顺序来执行,那么也是不支持的。我们只会按照注册回调的顺序来依次调用。当然开发者可以通过将多个回调按序调用组成一个更复杂的回调来实现这个目标。不支持它主要是一个取舍问题。我相信有这种需求的人是少数以至于几乎没有的……

底层支持的优雅停机

前面所有的步骤,都是直接建立在应用层面上。实际上,还有一些业界的做法,是在底层协议上就直接提供了支持。比如说,通过TCP连接发送一个只读事件,那么客户端后续就自然不会再把请求发过来。

我们的优雅停机并没有使用到这一种机制,因为在应用层面上就能够解决。dubbogo里面的RegistryProtocolDestroy都没采用这种机制。

但是这的确是一个很不错的实现思路。dubbo就是采用了这种方式。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • /*--------------------------- 01 HTTP请求 -----------------...
    蓝心儿的蓝色之旅阅读 2,122评论 0 4
  • 原文地址:http://casatwy.com/iosying-yong-jia-gou-tan-wang-luo...
    大澎湃阅读 1,498评论 1 2
  • 夜 深夜 冷静又凄凄 弯月斜钩孤星泪 青山黛眉远山愁 凭栏到天明 无眠 夜
    留客天阅读 397评论 4 8
  • 朋友圈是个很好的宣传工具,但是就向店说的发的要有血有肉,能吸引到别人,可以发一些生活照和自己的练习照,这样会显得得...
    陈鹏云阅读 122评论 0 0