跳转到内容
文档
社区
联系我们
学习
解决方案
中文

3.1 线上发布稳定性解决方案

绝大多数的软件应用生产安全事故发生在应用上下线发布阶段,尽管通过遵守业界约定俗成的可灰度、可观测和可滚回的安全生产三板斧,可以最大限度的规避发布过程中由于应用自身代码问题对用户造成的影响。但对于高并发大流量情况下的短时间流量有损问题却仍然无法解决。因此,本节将围绕云原生场景下发布过程中如何解决的流量有损问题实现应用上下线过程中的流量无损效果相关内容展开方案介绍。

无损上下线背景

据统计,应用的事故大多发生在应用上下线过程中,有时是应用本身代码问题导致。但有时我们也会发现尽管代码本身没有问题,但在应用上下线发布过程中仍然会出现短时间的服务调用报错,比如调用时出现Connection refused和No instance等现象。相关问题的产生原因有相关发布经历的同学或多或少可能有一定了解,大家同时也发现该类问题一般在流量高峰时刻尤为明显,半夜流量少的时候就比较少见,于是很多人便选择半夜三更进行应用发布希望以此来规避线上发布事故。本节将就这些问题出现的背后真实原因以及业界对应的设计方案展开介绍。常见的流量有损现象出现的原因包括但不限于以下几种:

  • **服务无法及时下线:**服务消费者感知注册中心服务列表存在延时,导致应用下线后在一段时间内服务消费者仍然调用已下线应用造成请求报错。
  • **初始化慢:**应用刚启动接收线上流量进行资源初始化加载,由于流量太大,初始化过程慢,出现大量请求响应超时、阻塞、资源耗尽从而造成刚启动应用宕机。
  • **注册太早:**服务存在异步资源加载问题,当服务还未初始化完全就被注册到注册中心,导致调用时资源未加载完毕出现请求响应慢、调用超时报错等现象。
  • **发布态与运行态未对齐:**使用Kubernetes的滚动发布功能进行应用发布,由于Kubernetes的滚动发布一般关联的就绪检查机制,是通过检查应用特定端口是否启动作为应用就绪的标志来触发下一批次的实例发布,但在微服务应用中只有当应用完成了服务注册才可对外提供服务调用。因此某些情况下会出现新应用还未注册到注册中心,老应用实例就被下线,导致无服务可用。

接下来,将就具体的下线和上线过程中如何避免流量损耗问题进行分别介绍。

无损下线

由于微服务应用自身调用特点,在高并发下,服务提供端应用实例的直接下线,会导致服务消费端应用实例无法实时感知下游实例的实时状态因而出现继续将请求转发到已下线的实例从而出现请求报错,流量有损。
第一节_3-1-1 第三章第一节第一张图.png
图1.Spring Cloud应用消费者无法及时感知提供者服务下线
例如对于Spring Cloud应用如上图1所示,当应用的两个实例提供者A’和A中的A下线时,由于Spring Cloud框架为了在可用性和性能方面做平衡,消费者默认是30s去注册中心拉取最新的服务列表,因此A实例的下线不能被实时感知,流量较大时,消费者会继续通过本地缓存调用已下线的A实例导致出现流量有损。
针对该类问题,业界一般的解决方式是通过将应用更新流程划分为手工摘流量、停应用、更新重启三个步骤,由人工操作实现客户端避免调用已下线实例。这种方式简单并且有效,但是限制较多:不仅需要借助流控能力来实现实时摘流量,还需要在停应用前人工判断来保证在途请求已经处理完毕。这种需要人工介入的方式运维复杂度较高,只适用于规模较小的应用,无法解决当前云原生架构下,自动化的弹性伸缩、滚动升级等场景中的实例下线过程中的流量有损问题。本节将对业界应用于云原生场景中的一些无损下线技术方案进行介绍。

主动通知

一般注册中心都提供了主动注销接口供微服务应用正常关闭时调用,以便下线实例能及时更新其在注册中心上的状态。主动注销在部分基于事件感知注册中心服务列表的微服务框架比如Dubbo中能及时让上游服务消费者感知到提供者下线避免后续调用已下线实例。但对于像Spring Cloud这类微服务框架,服务消费者感知注册中心实例变化是通过定时拉取服务列表的方式实现。尽管下线实例通过注册中心主动注销接口更新了其自身在注册中心上的应用状态信息,但由于上游消费者需要在下一次拉取注册中心应用列表时才能感知到,因此会出现消费者感知注册中心实例变化存在延时。在流量较大、并发较高的场景中,当实例下线后,仍无法实现流量无损。既然无法通过注册中心让存量消费者实例实时感知下游服务提供者的变化情况,业界提出了利用主动通知解决该类问题。主动通知过程如下图2所示:
第一节_3-1-3 第三章第一节第三张图.png
图 2.无损下线方案
如图2所示,服务提供者B中某个实例在下线时为避免主动在注册中心中注销的服务实例状态无法实时被上游消费者A感知到从而出现调用已下线实例的问题。在接收到下线命令即将下线前,提供者B对于在等待下线下线阶段内收到的请求,在其返回值中都增加上特殊标记让服务消费者接收到返回值并识别到相关标志后主动拉取一次注册中心服务实例从而实时感知B实例最新状态,从而达到服务提供者的下线状态能够被服务消费者实时感知。

自适应等待

在并发度不高的场景下,主动通知方法可以解决绝大部分应用下线流量有损问题。但对于高并发大流量应用下线场景,如果主动通知完,可能仍然存在一些在途请求需要待下线应用处理完才能下线否则这些流量就无法正常被响应。为解决该类在途请求问题,可通过给待下线应用在下线前通过自适应等待机制在处理完所有在途请求后,再下线以实现流量无损。
第一节_3-1-2 第三章第一节第二张图.png
图3.自适应等待机制
如上图3所示,自适应等待机制是通过待下线应用统计应用中是否仍然存在未处理完的在途请求,来决定应用下线的时机,从而让待下线应用在下线前处理完所有剩余请求。
利用主动通知让上游消费者实时感知到实例下线状态,再通过自适应等待机制处理剩余的在途请求即可实现应用的无损下线。

无损上线

延迟加载是软件框架设计过程中最常见的一种策略,例如在Spring Cloud框架中Ribbon组件的拉取服务列表初始化默认都是要等到服务的第一次调用时刻,例如下图4是Spring Cloud应用中第一次和第二次通过RestTemplate调用远程服务的耗时对比情况:
Picture1.png
3-1-4 第三章第一节第四张图
图4.应用启动资源初始化与正常运行过程中耗时情况对比
由图4结果可见,第一次调用由于进行了一些资源初始化,耗时是正常情况的数倍之多。因此把新应用发布到线上直接处理大流量极易出现大量请求响应慢,资源阻塞,应用实例宕机的现象。

业界针对上述应用无损上线场景提出如下包括延迟注册、小流量服务预热以及就绪检查等一系列解决方案,详细完整的方案如下图5所示:
第一节_3-1-5 第三章第一节第五张图.png
图 5.无损上线整体方案

延迟注册

对于初始化过程需要异步加载资源的复杂应用启动过程,由于注册通常与应用初始化过程同步进行,从而出现应用还未完全初始化就已经被注册到注册中心供外部消费者调用,此时直接调用由于资源未加载完成可能会导致请求报错。通过设置延迟注册,可让应用在充分初始化后再注册到注册中心对外提供服务。例如开源微服务治理框架Dubbo原生就提供延迟注册功能[1]。

小流量服务预热

在线上发布场景下,很多时候刚启动的冷系统直接处理大量请求,可能由于系统内部资源初始化不彻底从而出现大量请求超时、阻塞、报错甚至导致刚发布应用宕机等线上发布事故出现。为了避免该类问题业界针对不同框架类型以及应用自身特点设计了不同的应对举措,比如针对类加载慢问题有编写脚本促使JVM进行预热、阿里巴巴集团内部HSF(High Speed Framework)使用的对接口分批发布、延迟注册、通过mock脚本对应用进行模拟请求预热以及小流量预热等。本节将对其中适用范围最广的小流量预热方法进行介绍。
相比于一般场景下,刚发布微服务应用实例跟其他正常实例一样一起平摊线上总QPS。小流量预热方法通过在服务消费端根据各个服务提供者实例的启动时间计算权重,结合负载均衡算法控制刚启动应用流量随启动时间逐渐递增到正常水平的这样一个过程帮助刚启动运行进行预热,详细QPS随时间变化曲线如图6 所示:
第一节_3-1-6 第三章第一节第六张图.png
图 6.应用小流量预热过程QPS曲线
开源Dubbo所实现的小流量服务预热过程原理如下图7 所示:
第一节_3-1-7 第三章第一节第七张图.png
图 7.应用小流量预热过程原理图
服务提供端在向注册中心注册服务的过程中,将自身的预热时长 WarmupTime、服务启动时间 StartTime 通过元数据的形式注册到注册中心中,服务消费端在注册中心订阅相关服务实例列表,调用过程中根据 WarmupTime、StartTime 计算个实例所分批的调用权重。刚启动 StartTime 距离调用时刻差值较小的实例权重下,从而实现对刚启动应用分配更少流量实现对其进行小流量预热。
开源Dubbo所实现的小流量服务预热模型计算如下公式所示:
image.png

模型中应用QPS对应的 f(x) 随调用时刻 x 线性变化,x表示调用时刻的时间,startTime是应用开始时间,warmupTime是用户配置的应用预热时长,k是常数,一般表示各实例的默认权重。
第一节_3-1-8 第三章第一节第八张图.png
图 8.应用小流量预热权重计算
通过小流量预热方法,可以有效解决,高并发大流量下,资源初始化慢所导致的大量请求响应慢、请求阻塞,资源耗尽导致的刚启动应用宕机事故。

微服务就绪检查

在介绍微服务就绪检查之间,先简单介绍一下相关的Kubernetes探针技术作为技术背景,以便更好的理解后文内容:

Kubernetes探针技术

在云原生领域,Kubernetes为了确保应用 Pod 在对外提供服务之前应用已经完全启动就绪或者应用Pod长时间运行期间出现意外后能及时恢复,提供了探针技术来动态检测应用的运行情况,为保证应用的无损上线和长时间健康运行提供了保障。
存活探针
Kubernetes 中提供的存活探测器来探测什么时候进行容器重启。 例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。 在这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。
就绪探针
Kubernetes 中提供的就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作就绪了。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 在 Pod 还没有准备好的时候,会从 Service 的负载均衡器中被剔除的。
启动探针
Kubernetes 中提供的启动探测器可以知道应用程序容器什么时候启动了。 如果配置了这类探测器,就可以控制容器在启动成功后再进行存活性和就绪检查, 确保这些存活、就绪探测器不会影响应用程序的启动。 这可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
探针使用小结

  1. 当需要在容器已经启动后再执行存活探针或者就绪探针检查,则可通过设定启动探针实现。
  2. 当容器应用在遇到异常或不健康的情况下会自行崩溃,则不一定需要存活探针,Kubernetes 能根据 Pod 的 restartPolicy 策略自动执行预设的操作。
  3. 当容器在探测失败时被Kill并重新启动,则可通过指定一个存活探针,并指定restartPolicy 为 Always 或 OnFailure。
  4. 当希望容器仅在探测成功时 Pod 才开始接收外部请求流量,则可使用就绪探针。

Kubernetes探针技术使用实例:
https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

当前容器+Kubernetes的应用运维部署方式已经成为了业界的事实标准,相关技术为微服务应用运维部署带来巨大便利的同时,在某些特殊的应用部署场景中也有一些问题需要解决。比如,在使用Kubernetes的滚动发布功能进行应用发布的时候,由于Kubernetes的滚动发布一般关联的就绪检查机制,是通过检查应用特定端口是否启动作为应用就绪的标志来触发下一批次的实例发布,但在微服务应用中只有当应用完成了服务注册才可对外提供服务调用。因此某些情况下会出现新应用还未注册到注册中心,老应用实例就被设置下线,导致无服务可用。
针对这样一类微服务应用的发布态与应用运行态无法对齐的问题导致的应用上线事故,当前业界也已经有相关解决方案进行应对。比如阿里云微服务引擎MSE就通过就绪检查关联服务注册的方法,通过字节码技术植入应用服务注册逻辑前后,然后在应用中开启一个探测应用服务是否完成注册的端口供Kubernetes的就绪探针进行应用就绪态探测进而绑定用户的发布态与运行态实现微服务的就绪检查,避免出现相关状态不一致导致的应用发布上线流量有损问题。[

](https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)

参考资料

[1] Dubbo延迟注册:https://dubbo.apache.org/zh/docs/advanced/delay-publish/