TheR1sing3un opened a new issue, #1977:
URL: https://github.com/apache/dubbo-go/issues/1977

   <!-- Please only use this template for submitting enhancement requests -->
   
   ### 触发下线
   
   k8s在终止容器进程的时候,会由kubelet向进程发送SIGTERM信号量。Dubbo-go中已经集成对该信号量的监听逻辑。
   
   > dubbo-go/config/graceful_shutdown.go/config#gracefulShutdownInit
   
   
![image-20220608142256328](https://ther1sing3un-personal-resource.oss-cn-beijing.aliyuncs.com/typora/images/image-20220608142256328.png)
   
   ### 服务端下线期
   
   
当触发了优雅下线的时候,这时候需要一个可配置的下线期超时时间。该段时间内,需要完成对Provider的下线和对Consumer的下线。如果该段时间内下线逻辑仍未执行完,也无需继续等待。强制下线即可。
   
   因此可以将需要下线的进程的下线逻辑如下表示:
   
   
![image-20220602160253522](https://ther1sing3un-personal-resource.oss-cn-beijing.aliyuncs.com/typora/images/image-20220602160253522.png)
   
   整个流程将在设置的超时时间`Timeout`内完成。
   
   #### 反注册
   
   
这一步是为了将Provider从注册中心取消注册,让客户端可以通过反注册事件来将该请求实例缓存移除,这样的话之后的请求就不会请求到该进程了。由于我们需要实现不依赖注册中心的反注册事件的方案,这一步在这里是为了正常的下线逻辑和为了一个保底的下线通知。
   
   #### 主动通知
   
   
对于Triple等依赖长连接的协议,可以采用主动通知的逻辑,也就是在维护的长连接处对Consumer发送请求。可以直接复用该连接,客户端需要对该处的请求做出下线相关处理。
   
   ##### 重试
   
   长连接的保活机制并不能保证当前正在维护着的长连接就是活跃的。因此可能会下线通知失败,那么这时候可以进行重试。
   
   ##### 超时
   
   在超时时间内,若完成了收到了和长连接个数的正确响应,那么直接继续下一步,否则会进行某一步的重试。在超时时间到之后,不再进行重试了,直接进入到下一步。
   
   该步骤的超时时间其实可以和目前采用的等待注册中心的下线通知下发的时间相关,因为可以让注册中心的通知作为保底。
   
   #### 被动通知
   
   若不是采用长连接或者对于主动通知没有覆盖到的客户端,我们只采用被动通知的方式,
   
   
我们仍采用现在的方案,使用filter来对当前接收到的但还未返回的请求计数。但是这时候我们仍需要ProviderFilter,作用是用来在`OnResponse()`函数中返回一个标记,比如使用Attachment作为载体,来记录当前是否处于下线流程中。也就是我们在下线的时候,标记了Provider为Closing状态,那么这时候的所有Response中都会带上Closing标记。用于通知上游该进程正在下线。
   
   #### 等待发出的请求得到响应
   
   仍采用当前方案,使用filter来对当前发出但仍未接收到响应的请求计数。这段时间设置一个超时时间,避免进程有不断发起请求的情况。
   
   #### Counter为0时是否下线?
   
   这里我们换一个方法,以等待Provider的请求全部返回为例
   
   
![image-20220607195015283](https://ther1sing3un-personal-resource.oss-cn-beijing.aliyuncs.com/typora/images/image-20220607195015283.png)
   
   
原本的逻辑是以10ms为间隔轮询counter,在等待未超时的情况下,一旦发现counter为0,就结束该步骤了。那么这样其实还是会有问题,可能在那一轮询counter的一瞬间,当前的请求刚好都返回,但是在这一瞬间之前一直都有请求过来,在这瞬间之后也仍有请求继续。那么这时候结束该步骤是不妥的,因此可以维护一个滑动窗口,假设窗口期为x
 ms,在x ms之内都没有新的请求到来,那么这时候就可以认为该步骤可以结束了。
   
   算法思路就是,在counter计数的基础上,维护一个最近一个请求的时间戳,我们轮询的逻辑就可以变成:
   
   ```go
   for time.Now().Before(deadline) && 
(shutdownConfig.ProviderActiveCount.Load() > 0 || 
time.Now().Before(shutdownConfig.ProviderLastRequest.Add(300 * 
time.Millisecond))) {
                // sleep 10 ms and then we check it again
                time.Sleep(10 * time.Millisecond)
                logger.Infof("waiting for provider active invocation count = 
%d", shutdownConfig.ProviderActiveCount.Load())
        }
   ```
   
   在不超时的情况下,只有当窗口期内无请求返回并且目前也没有请求正在处理,这时候结束该步骤。
   
   #### 释放资源
   
   销毁协议,反订阅等。
   
   ### 客户端处理下线响应
   
   
![image-20220602170706466](https://ther1sing3un-personal-resource.oss-cn-beijing.aliyuncs.com/typora/images/image-20220602170706466.png)
   
   #### Filter判断是否下线
   
   当维护的长连接中接收到下线通知以及当Filter接收到响应中的Attachment的Closing标记为true的时候,需要对当前服务实例进行处理。
   
   #### 客户端接收到下线请求
   
   客户端在连接处,我们需要做出一些判断逻辑,当接收到Provider的下线请求,那么进入到该地址的请求实例的清除状态。
   
   #### 移除实例
   
   将目标实例从实例列表中移除,之后的请求就不会再向该实例发了。
   
   ### 主动通知和被动通知结合
   
   我们采用主动通知是原因:
   
   - 可以迅速的对当前的连接着的客户端做出通知,不必等待注册中心的通知,注册中心的通知可能不稳定或者被降级导致不够及时。
   
   我们采用被动通知的原因是基于以下推断:
   
   - 对于上游目前活跃的客户端,我们在一个等待期内极大概率会接收到它的请求,这时候我们进行响应中携带标识就可以将其正确通知。
   - 
对于上游目前不活跃的客户端,我们可以假定,它目前的qps不高,那么它在注册中心的反注册事件通知或者拉取最新配置之前,是没有感受到下游服务端已经下线了。或者是即使感受了,失败的请求并不会一下子很多,并且在第一个失败的请求之后,它会进行实例的更新,那么一共也产生不了几个失败请求。


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to