作者:项良、十眠
微服务上云门槛降低,用好微服务才是关键
据调研数据显示,约 70% 的生产故障是由变更引起的。在阿里云上的企业应用如茶百道、极氪汽车和来电等,他们是如何解决变更引起的稳定性风险,实现了在白天高流量情况下应用发布平滑无损。今天我们将揭开阿里云微服务全链路无损发布解决方案的面纱, 快来一起参与探讨,并动手实践吧。
随着微服务开源以及生态的成熟,大大地推进了微服务技术的标准化。下图是展示单体和微服务架构选型的图,其中横坐标代表的是系统复杂度,纵坐标代表着效率,绿色曲线代表着单体架构,蓝色曲线代表微服务架构。我们可以看到随着系统复杂度的升高,单体架构跟微服务之间选型存在着一个拐点,拐点向右更适合选型微服务架构。随着微服务技术的标准化,意味着微服务上云的门槛也大幅度降低,他们之间选型的拐点也在持续地左移,采用微服务架构的企业日益增多。当企业开始大规模推广使用微服务时,如何用好、用稳微服务成为了大家关注的点,用好微服务的关键就是稳定性跟效率。
微服务变更风险大,发布被迫选择半夜
我们来一起看下一些客户真实的诉求,某茶饮企业变更引起事故占比一度超过 60%,每次发版都要避开高峰,在凌晨发版;某新能源企业业务连续性要求非常高,核心服务需要 7*24 小时持续在线,但是业务快速发展又需要保证迭代的效率,为了保证业务的稳定性,只能在业务低峰期即凌晨进行发布;同样,某科技公司因为缺少线上灰度能力,每次发布全量发布会导致影响面不可控,存在一定风险。
据调研数据称,70% 的生产故障都是由于变更导致的,除应用本身问题外,运行时风险概括为:不确定流量、不稳定调用、不稳定基础设施。今天,我们聊的解决方案就是为了全面消除变更态风险,解决用户生产变更态的稳定性风险。
我们先来分析一下,为什么微服务应用不能白天发布?因为相比于单体应用来说,微服务架构本身就是复杂的拓扑网络状的调用跟依赖结构。那么就意味着如果我们的微服务应用没有处理好上下线有损的问题,那么我们的任一微服务在发布的过程中都会存在短暂的服务不可用的情况,短时间内会出现大量的异常,导致业务受损。
最直观的感受就是我们一不小心点击了某个应用的重新部署,然后报警群里就会收到大量订单成功率下跌的告警,想想就很吓人。其次,发布时整个功能上线到线上的最后一个环节,一些在设计、研发、测试过程中累计下来的问题,在最后发布的环节才会触发。如果发布涉及到多个应用,如何进行合理发布,并且不会因为版本的问题而导致流量损失?我们总不希望看到,同一个用户在下单页面时候访问到新的版本,到支付阶段又碰到了老版本。
左下图是我拿开源 Spring Cloud 应用进行压测,并且在压测过程中重启应用后,整体压测请求的表现,我们可以看到有大量的异常。再进一步想一下,如果此时是生产系统,并且在白天大流量的场景下,极小的问题都会由于大流量的因素被快速放大,影响面难以控制。所以很多企业跟业务团队,宁愿选择在凌晨进行发布。
MSE 微服务全链路无损发布方案,消除变更风险
如何彻底解决变更稳定性问题?那就介绍今天的主角,MSE 微服务全链路无损发布方案。
我们分别从两个角度来分析问题:
-
如果我们新版本的代码没任何问题,服务上下线过程还是会有损,应该怎么解决?
-
如果我们新版本的代码有问题,那么我们如何控制问题的影响面?
首先,代码没问题,为什么上下线的环节中流量会有损失。当我们的微服务提供者下线后,服务的消费者无法实时感知节点下线,持续调用已经下线的节点地址,那么流量就会出现持续的报错;如果服务提供者在下线的过程中,请求执行到一半时,应用节点就直接停止了,那么就会导致在途请求以及处理中的请求失败,严重时还会导致线上的数据不一致;同样,在应用上线环节中也会有许多问题,比如新启动的节点如果没有完成预热,就直接承受打流量,那么就会导致新启动的节点直接被打垮,更有严重情况下,大流量场景下服务扩容都扩不出来。同样,微服务体系的生命周期没有跟 K8s 维度的 Readiness/Liveness 生命周期没有对齐,那么在发布的过程中会出现没对齐而导致的请求异常。
如果新版本的代码存在问题,那我们希望在发布新版本的过程中尽可能地控制影响面,阿里巴巴安全生产最佳实践告诉我们,在发布变更的过程中需要做到安全生产三板斧,即可灰度、可观测、可回滚。可灰度就是通过灰度的方式控制问题的影响面,如果我们业务代码新版本存在 bug,大多数情况下我们只能选择在业务低峰期(凌晨)进行发布;在微服务架构下,灰度发布需要做到全链路灰度的能力,目前开源自建很难实现全链路灰度的能力,如果出现流量丢标的情况,会导致灰度流量打到生产环境,引入了额外不可控的风险;可回滚,要求我们在出问题后,需要快速恢复止血,但如果没有一套完整的回滚方案与策略,回滚速度慢或者在回滚过程中引入更大的问题都是不可接受的。
如何解决微服务上下线过程流量有损问题
如果新版本代码没问题,如何解决服务上下线过程中流量有损的问题?
减少不必要的 API 报错,是最好的用户体验,也是最好的微服务开发体验。如何解决这个在微服务领域内让人头疼的问题呢。在这之前我们先来了解一下为什么我们的微服务在下线的过程中会有可能出现流量损失的问题。
原理分析:无损下线
如上图右侧所示,是一个微服务节点下线的正常流程。
-
下线前,消费者根据负载均衡规则调用服务提供者,业务正常。
-
服务提供者节点 A 准备下线,先对其中的一个节点进行操作,首先是触发停止 Java 进程信号。
-
节点停止过程中,服务提供者节点会向注册中心发送服务节点注销的动作。
-
服务注册中心接收到服务提供者节点列表变更的信号后会,通知消费者服务提供者列表中的节点已下线。
-
服务消费者收到新的服务提供者节点列表后,会刷新客户端的地址列表缓存,然后基于新的地址列表重新计算路由与负载均衡。
-
最终,服务消费者不再调用已经下线的节点。
微服务下线的流程虽然比较复杂,但整个流程还是非常符合逻辑的,微服务架构是通过服务注册与发现实现的节点感知,自然也是通过这条路子实现节点下线变化的感知。
参考我们这边给出的一些简单的实践数据,我想你的看法可能就会变得不同。从第 2 步到第 6 步的过程中,Eureka 在最差的情况下需要耗时 2 分钟,即使是 Nacos 在最差的情况下需要耗时 50 秒;在第 3 步中,Dubbo 3.0 之前的所有版本都是使用的是服务级别的注册与发现模型,意味着当业务量过大时,会引起注册中心压力大,假设每次注册/注销动作需要花费 20~30ms,五六百个服务则需要注册/注销花费掉近 15s 的时间;在第 5 步中, Spring Cloud 使用的 Ribbon 负载均衡默认的地址缓存刷新时间是 30 秒一次,那么意味着及时客户端实时地从注册中心获取到下线节点的信号,依旧会有一段时间客户端会将请求负载均衡至老的节点中。
如上图左侧所示,只有到客户端感知到服务端下线并且使用最新的地址列表进行路由与负载均衡时,请求才不会被负载均衡至下线的节点上。那么在节点开始下线的开始到请求不再被打到下线的节点上的这段时间内,业务请求都有可能出现问题,这段时间我们可以称之为服务调用报错期。
通过对微服务下线的流程分析,我们理解了解决微服务下线问题的关键就是:保证每一次微服务下线过程中,尽可能缩短服务调用报错期,同时确保待下线节点处理完任何发往该节点的请求之后再下线。
那么如何缩短服务调用报错期呢?我们想到了一些策略:
-
将步骤 3 即节点向注册中心执行服务下线的过程提前到步骤 2 之前,即让服务注销的通知行为在应用下线前执行,考虑到 K8s 提供了 Prestop 接口,那么我们就可以将该流程抽象出来,放到 K8s 的 Prestop 中进行触发。
-
如果注册中心能力不行,那么我们是否有可能服务端在下线之前绕过注册中心直接告知客户端当前服务端节点下线的信号,该动作也可以放在 K8s 的 Prestop 接口中触发。
-
客户端在收到服务端通知后,是否可以主动刷新客户端的地址列表缓存。
如何尽可能得保证服务端节点在处理完任何发往该节点的请求之后再下线?站在服务端视角考虑,在告知了客户端下线的信号后,可以提供一种等待机制,保证所有的在途请求以及服务端正在处理的请求被处理完成之后再进行下线流程。
实战演练:缩容过程中无损下线的表现
下面我们就通过一个实践来看看无损下线的效果与表现:
该实践分为四个步骤,首先我们在 ACK 控制台配置定时伸缩任务,模拟应用扩缩容的场景,我们可以配置 5 分钟内第 2 分钟扩容,第 4 分钟缩容;第二步,应用接入 MSE 服务治理后默认会具备无损下线能力,我们需要将基线环境增加环境变量关闭无损下线能力,作为对照组;灰度环境接入 MSE 服务治理后,不做任何操作,即默认开启无损下线能力,作为实验组;第三步,我们同时发起基线跟灰度的流量,观察其表现;第四步,我们在 MSE 应用详情的 QPS 数据模块中可以看出,未打标环境(关闭无损下线的应用)在扩缩容过程中出现了 99 个异常,然而灰度环境则无任何错误数。
标签:服务,无忧,流量,下新,灰度,版本,链路,下线,无损 From: https://www.cnblogs.com/alisystemsoftware/p/18101744