本文作者:肖静,SpringCloudAlibabaPMC,阿里云智能技术专家。01全链路灰度背景介绍新版本发布时,为了有效、仔细地验证新版本代码逻辑的正确性,通常采用灰度发布的方式来降低第一次变更带来的影响。例如,应用集合可能包含交易中心、商品中心、库存中心等多个模块。在新版本发布过程中,可能会出现同时修改交易中心和产品中心的特性。为了验证新版本的正确性,需要让灰度流量到达交易中心和商品中心的灰度版本,并串联起来,有效验证新版本的正确性,所以全链接灰度是必需的。如上图,流量从门户应用进来后,如果识别为灰度流量,不仅会去交易中心的灰度,还会去商品中心的灰度。如果库存中心有灰度,也会去库存中心的灰度;否则降级为库存中心基线环境。假设有网关,客户端可以从iOS、Android或H5访问网关。在访问过程中,会在参数中加入一个header(http协议),header中包含用户ID信息。后台分为A、B、C三个模块,比如发布新功能,需要更新A、C两个应用模块。A和C的灰度版本需要同时发布,而B应用没有新功能发布。为了有效验证新版本A和C代码逻辑的正确性,我们设置灰度规则为用户ID=120时进入灰度环境。所以,流量从网关进来后,先去A的灰度环境;那么当A调用B时,发现B没有灰度环境,就会降级到B的基线环境;当下一跳B调用C时,发现C有灰度环境,将C的灰度环境返回给C的灰度环境,从而实现全链路的灰度释放。上述全链路灰度发布流程可以有效验证A和C两个应用合并后新版本的有效性。只有可控范围内的流量参与到灰度环境中,可以有效验证新版本的正确性,避免新版本中业务逻辑错误造成比较重大的损失或业务故障。以上是RPC层的全链路灰度图。当链接请求中有消息时,如何实现全链接灰度化?假设库存中心C收到一个订单,会产生一条消息发送给RocketMQ服务器,这条消息会被A应用消费。此时如果在这个过程中修改了消费逻辑,C应用产生的消息(灰度消息)需要被A的灰度环境,即RocketMQ的灰度消费者消费,才能实现灰度环境的闭环。02消息灰度的设计与实现在消息灰度的设计中,消息生产者是如何生产灰度消息的?带灰度标签的方式有以下三种:①如果请求在入口处被识别为灰度请求,则该消息将被标记为灰度消息。②如果节点本身是灰度节点,并且启用了流量着色,消息会被标记为灰度消息。③如果入口处的请求没有被识别为灰度流量,但是消息的载荷本身属于灰度流量,那么该消息也会被标记为灰度消息。消息生产者在生产时,可以通过在tag或user-property中添加一些字段,在消息体中附加灰度信息。但考虑到tag包含业务逻辑语义,每条消息只能有一个tag,不推荐使用tag。user-property字段属于key-value结构,更加灵活,更适合存储消息的灰度标识。RocketMQ的producer提供了SendMessageHook,可以自定义逻辑。生产消息时,可以将灰度标签存储在user-property中,消息发送到RocketMQ服务器时会包含灰度信息。消费者灰度比较复杂,同时支持客户端过滤和服务端过滤。从图中可以看出,开源的RocketMQ客户端中有一个FilterMseeageHook可以进行逻辑处理。本环境不需要消费的消息,可以直接通过FilterMseeageHook过滤掉。正式环境和灰度环境中的消费者使用不同的消费者群体来分离他们的偏移量。然后在FilterMseeageHook中添加相应的逻辑,过滤掉灰度环境下接收到的所有非灰度消息。官方环境需要拉取所有消息分析user-property字段,key值包含消息的环境标签。如果识别出灰度环境下的消息,正式环境会通过remove方法忽略该消息,灰度环境也是如此。该方案下,官方环境和灰度环境属于两个不同的消费群,都需要在本地拉取所有消息。更极端的场景,比如灰度消费者只有一台机器,而正式环境的消费者有100台机器,灰度环境需要承受巨大的压力。另一方面,RocketMQ服务器也需要每条消息推送两次,这也增加了服务器的压力。客户端过滤的方式有缺点,那么通过服务端过滤是否可以避免这些缺点呢?服务器端过滤分为Tag过滤和SQL92过滤。RocketMQ的服务端过滤包括两种过滤方式,分别是Tag过滤和SQL92过滤。在Tag过滤的实现中,RocketMQ消费者订阅服务端时,会将订阅信息传递给服务端。订阅信息为SubscriptionData,包含四个字段:topictagSetexpressionType=tag:表达式的类型,这里是tag过滤,所以取值为tagclientversion:本次订阅的版本号客户端会不断向服务端发送心跳,通过默认30每秒一次。在此过程中,SubscriptionData可以动态变化。如果tagSet或expression的类型发生变化,则clientversion的值将增加。服务器收到心跳后,发现心跳中SubscriptionData的版本号发生了变化,也就是说订阅规则也发生了变化。这时候客户端的订阅逻辑就会更新,决定服务端过滤变化的推送。服务端处理服务器的灰度过滤逻辑如下:RocketMQ有一个MessageFilter类,先比对consumer队列,匹配成功再比对tagscode。只有当两个比较匹配时,消息才会推送给客户端消费者。上述过程的好处是避免了灰度环境拉取所有的消息,可以有效减轻灰度环境下消费者的负担。同时,服务器不会对所有消息进行二次推送,大大减轻了服务器的压力。如果灰度信息存储在user-property字段中,可以通过SQL92进行过滤。服务器端ConsumerFilterManager保存了每个topic对应的FilterDataMapByTopic,FilterDataMapByTopic保存了不同消费组对应的消费逻辑ConsumerFilterData。ConsumerFilterData包含consumergroup、topic、expression和clientversion,这和client发送的信息很相似。所以你可以用它来过滤。SQL92是一种可以编写复杂表达式的过滤规则。除了实现标签过滤,它还可以基于用户属性字段进行过滤。SQL92的过滤规则如上图所示。消费带有标签A或标签B的消息可以写成:(TAGSisnotnullandTAGSin('A','B'))消费用户属性中版本为gray的消息可以写成:(versionis'gray')consumptiontagisAandtheversioninuser-propertyisgray可以写成:(TAGSis'A')and(versionis'gray')consumptionTagisAandtheversioninuser-propertyisgreenorblue的消息可以写成:(TAGSisnotnullandTAGSis'A')and((versionis'green')or(versionis'blue'))另外,在实际应用场景中要达到完美消息灰度,还有很多问题需要考虑:如果消息生产逻辑是一个独立的线程池,如何实现灰度透传?常见的方式是在向线程池提交任务时,将灰标放在任务的某个字段中,消费时读取任务字段,再次放入线程本地,实现跨线程灰标传递。全通道灰度发布过程中出现回滚。没有灰度消费者会不会有一些灰度新闻?消息没有被消费,怎么办?有两种选择。一是过滤未消费的消息并采取补偿措施;如果消息的消费逻辑没有太大变化,让基线环境消费灰度消息也是可以接受的。消息灰度过程中出现重复消费怎么办?可以在消费逻辑上保证幂等性,或者设置更精确的灰度控制逻辑。消费者的订阅行为能否支持动态变化?比如没有灰度的消费者可以在正式环境下消费灰度消息吗?如果官方环境可以消费灰度消息,那么官方环境的默认行为是消费所有消息,还是只消费官方环境的消息?如何实现自定义消息灰度逻辑?比如流量刚进来的时候,并没有识别为灰度流量,但是在发送过程中,发现消息中有一些特殊的逻辑,正好碰上了灰度规则,所以需要标记为灰度规则。这时候怎么做自定义灰度逻辑呢?假设官方环境可以消费灰度消息,如果在这个过程中开启了灰度消费者,官方环境是否可以自动检测是否需要消费灰度?实现这一点可以避免重复消费,同时可以解决发布上下游存在联动的情况。03MSE全链路灰度最佳实践阿里云的微服务引擎MSE全链路灰度最佳实践如上图所示:name=xiaoming属于灰度流量,配置规则后,流量经过A和A的灰度环境然后从B的基线环境到C的灰度环境,C产生的灰度消息可以被RocketMQServer接收,同时精准推送到A的灰度环境,实现闭环。只要是基于开源标准开发,接入和使用MSE不需要任何代码修改,无业务入侵/无感知,零升级成本,完全不需要改变现有的业务架构。主要通过OneJavaAgent方式实现,通过JavaAgent增强字节码,让消息的生产者和消费者可以自动在开源RocketMQ的发送行为和消费行为中注入灰度相关的字节码,并且可以在业务部署完成后自动部署业务。附加JavaAgent,无需修改任何逻辑即可完成消息灰度化。上图中,消息生产者挂载JavaAgent后,Agent会自动修改发送消息的逻辑。如果识别出该消息属于灰度流量,则会自动为该消息添加灰度标记,并发送给消息服务器。当消息消费者在启动时识别到灰度环境,会自动通过JavaAgent修改消费者组,发送订阅规则时,会自动订阅只消费灰度的消息。消息服务器会识别并匹配过滤规则,保证只有灰度消息才会推送给灰度消费者。接入微服务引擎MSE后,所有对消息灰度的操作都可以直接在控制台完成。用户只需在MSE控制台配置治理规则,消费规则即可通过配置中心自动推送给消费者,让消费者动态改变消费行为,完成消息灰度的完整过程。过程中无需修改任何代码,只需连接JavaAgent即可实现所有功能。步骤如下:在MSE的应用列表页面选择对应的应用,开启消息灰度,配置基线环境和忽略的标签,即可使用消息灰度。点击链接查看更多相关信息:https://help.aliyun.com/docum...连接MSE后Demo效果为:A应用的灰度环境只会消费灰度消息,基线环境只会消耗灰度消息。使用基线消息。上图是Demo效果。左边是基线环境,只会消费基线应用产生的消息,在后续调整A到B,调整C的过程中只会在基线环境中。右边的日志是灰度环境A,消费的消息是在C的灰度环境中产生的,在以后调用A对B、C的过程中,A是灰度环境。调用B时,由于B只有基线环境,流量消息只到达B的基线环境,最后到达C的灰度环境,消费消息时,可以继续透传消息的灰度,继续走下去正确的路径。Demo源码下载地址:https://github.com/aliyun/ali...
