开发者学堂课程【应用发布新版本如何保障流量无损 :应用发布新版本如何保障业务流量无损(一)】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1215/detail/18215
应用发布新版本如何保障业务流量无损
内容介绍:
一、背景
二、场景分析
三、解决方案
四、动手实践
一、背景
应用发布新版本如何保障业务流量无损,历史上90%的故障都源于业务新版本上线如何最大化保障功能迭代过程中,我们的业务流量无损一直是开发者比较关心的一个问题,尤其是现在微服务架构比较流行,然后我们的服务之间的依赖关系是比较复杂的,一个业务功能可能需要多个微服务共同对外提供能力才能完成,那么我们的一次业务请求也是需要流经多个微服务才能完成处理业务的发展会不断,促进我们的这个应用,需要不断的迭代,那么迭代不可避免的就是需要平台发布,我们需要做的就是需要提升我们这个应用发布的过程中,这个稳定性更高可能的建设。
二、场景分析
首先来看一个真实的业务场景,如下图是某一个电商公司的一个业务架构,可以看到它包含一个用户中心 user,购物车 chart 和订单中心 order,这样三个微服务,然后统一通过网关对外暴露,其中开发框架使用的是 spring 和double ,然后注册中心使用的是比较流行的 NACOS,针对其中一个比较具体的业务场景,比如说下单这样一个请求,它的调用链路是经过网关,然后 user,order,chart. 现在这个公司的业务,它的规模已经不断扩大,原有的这样的一个下单功能,逐渐暴露了一些历史上的一些设计,或者是程序上的缺陷,现在开发者针对这些问题进行了重新设计和 bug 修复,现在需要针对这个线上的用户中心和订单中心,需要同时发新版本进行修复,那么在这个升级过程中,开发者一般操作步骤是在正式上线之前,会小流量来验证我们这个新版本是否符合预期,然后经过充分测试之后,觉得已经可以了,这时候可以去逐步下线老版本,然后在下线的过程中也会根据这个流量比,然后会去逐步的去上线新版本,直到我们所有的流量切到这个新版本为止,这就是比较常见的一个服务发布的一个过程,那么这个过程看着简单,但是这个步骤中仍然有非常多的这样的细节要考虑,如果在这个过程中疏忽或者是遗漏某一个细节,那么可能会对我们的业务造成不可挽回的后果,接下来会针对这个某一个环节进行详细分析其中的痛点。
首先是灰度验证,先从传统的这个单体应用来讲,在单体应用中,我们的所有的服务模块,都是耦合在一个应用进程,那么这个灰度发布的过程中也是相对简单的,我们应用可以直接去部署我们的这个灰度应用,然后如果是我们选择的是基于流量比权重的方式进行灰度引流的话,我们可以在前面通过一个四层的代理,比如说SLP,然后通过对这个新应用跟老用的这个IP设置权重比来逐步完成这个服务发布的这个过程,那么更高阶的做法的是我们可以基于请求流量内容来做灰度发布,具体到业务场景,可以针对浏览器用户跟这个安卓用户,然后做一些灰度验证,到这个分布式微为服务架构中的话,应用被拆分出来多个子服务,然后这个子服务是独立部署,运行跟这个迭代的,并且我们服务之间的依赖关系是比较复杂的,可能我们某一个功能上线需要依赖多个服务,同时发展才能完成。这个user跟这order 需要同时进行发版,那么怎么从这个网关到整个后端服务来做一个端到端的这样一个全链路灰度呢?就是怎么去保证,我们这个流量能够能够在验证的过程中,能够流经我们的 user 和 order 这个灰度版本呢?这里有几个核心的问题要解决,第一个问题,就是端到端的灰度策略如何实现?就是要达到这样的效果,然后,每一环节的这个灰度策略如何实现?第二个问题,每一跳的灰度节点如何识别?第三个问题,每一跳的流量容灾怎么实现?
第二个是我们经过我们上面充分验证之后,我们现在需要对服务的老版本进行下线,那么在下线的过程中也会有一些问题,由于这个微服务应用自身调用的特点,在高并发的情况下,这个服务提供端应用实力,如果直接下线,那么会导致这个服务消费端应用实力无法感知到下游实力的实施状态,比方说图中的这个服务,当这个 order 的一个节点下线的时候,这个 chart 如果是无法及时感知到 order 的老节点,那么 chart 在访问 order 的时候,它仍然会将流量负载到这个下线的节点上,就会导致流量有损,那么为什么会出现以上问题呢?从客户端视角和服务端视角,然后分别来进行分析,首先是当这个服务端应用的某个节点下线的时候,由于注册中心有心跳保护机制能力,那么它存在一定的时间窗口范围内的一个延迟,导致注册中心感知到这个服务端节点下线的时候是一个滞后的过程,当它感知到下线过程,它再通知到客户端这个节点下线的时候,它又往后延迟了一步,那么就会导致客户端在客户端真正接收到服务端节点下线的时候,中间有一个时间窗口,这个时间窗口就是服务调用的这个报酬期,因为客户端它是不知道这个节点已经下线的,那么它在调用的时候,它仍然会将这个请求负载到这个已下线的节点上,整个过程不仅依赖于这个注册中心感知这个节点变段的时效性,并且还严重依赖我们客户端订阅的这个实现逻辑。
第二个要解决的问题是如何确保这个服务老版本下线过程中流量无损的,在服务老版本下线过程中,需要逐步的去完成这个新版本上线,以确保这个线上后端服务的机器容量能够满足线上的这个流量规模,这个过程看着简单,其实也是跟下线过程中有很多的细节要考虑,比如说服务的这个新节点启动之后,注册中心会感知服务新节点的上线事件,然后会去通知客户端来拉取服务的最新节点的信息列表,然后客户端拉取之后,它会更新自己的这个连接池,然后这样,新的访问请求就会负载到这个新的节点上,但是新节点启动之后,它会有一些资源初始化的操作,以及一些资源预热的操作,那么这时候如果客户端大量的请求,在这个服务提供者资源初始化完毕之前就访问它的话,那么就可能会导致请求出现大量的超时跟出错,甚至大流量会直接导致这个新节点不可用,比方说这个图中这个例子,当我们这个 order 发了一个新新节点之后,那么客户端感知到新节点上线。之后它就会把流量转发到这个 order-new 上面这个节点上,但是 order-new 上面它有一些资源初始化操作,那么如果是大流量过来之后,它总会托管整个流量的处理过程就会导致这个超时,甚至出错,举个例子如图,下面是这个 Java 服务新节点启动之后,涉及到的几个过程,第一个是应用初始化,然后第二步就是服务注册,当我们的应用初始化之后,然后它就会向注册中心登记自己以及对应的这个节点的信息,然后第三个就是节点就绪,然后在这个容器时代,节点就绪会有一个 readiness 检查就是给 k8s readiness 来检测我们的应用是否初始化好的一个方法,然后一些开发者,它在开发这个容器应用的时候,它是没有设计好这个外检查的,那么就会导致这个节点就绪的速度是非常快,那么当节点就绪之后,然后我们的流量开始进入,流量进入之后由于一些延迟加载的设计思想,那么当流量倒入之后,我们才会去加载一些所需的类,然后才会对热点代码进行一个加编译,然后我们缓存是失效的,意思就是我们所有的请求都会产生这个缓存击穿的效果,第四个就是我们的数据库需要建连,建连可以看到,只有流量进入以后,我们服务所需的这个资源,才是被动加载的,那么大规模流量进来之后,因为我们的资源加载需要消耗一定的硬件资源,这时候,我们的大量的请求可能得不到及时的响应,就会出现这个启动之后的一段时间内出现请求的这个超时跟错误,甚至更严重的情况下就导致新的节点直接宕机不可用,对我们的业务的整个环节,造成了一个不可挽回的影响,那么我们要解决的问题是如何确保服务新版本上线过程中流量无损的第二个环节,就是我们会去针对上面提出的三个问题,提出一些通用的解决方案,如何在实际业务场景中去快速落地全链灰度呢?