git灰度发布版本_Spring Cloud Gray 3.0 灰度解决方案

本文详细介绍了Spring Cloud Gray,一个灰度发布的解决方案,支持实例、版本和服务级别的灰度策略,提供灵活的灰度粒度控制,降低线上发版风险。通过实例灰度、版本灰度和全链路灰度等策略,实现线上测试、放量和多版本控制,减少对正常用户的影响。此外,还介绍了Mock支持和灰度策略模型,以及Long Polling的实时增量信息同步,以提升灰度发布的效率和准确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

发版的故事

做为一位技术人员,是否经历过为了不影响线上环境的稳定,不影响线上用户的正常使用,需求上线、功能上线的时间都安排在晚上或者凌晨,发版测试过后精疲力尽,甚至可能在线上测试过程中发现新的问题,然后吭哧吭哧埋头找原因修bug,再发版测试,或者无法快速找到原因修复进行回滚,第二天晚上继续上线。这还不是最惨的,最惨的是晚上辛苦上线测试验证通过后,第二天用户功能出现问题,影响用户正常使用、生产数据异常,然后着急忙慌的回滚、修复数据,最后再背个故障 02c344ee253f9b6d336f9eb16399859d.png

勊勊业业,勤勤恳恳的经过一段时间,被提拔为团队领导,晚上上线发布时仍是谨小慎微,出现问题时仍拖着疲惫不堪的身心安排协调解决问题,确保顺利上线,尤其是晚上发布出现问题时,沟通成本会增强,注意力会下降,这些都会使得发布风险大增;看着团队成员频繁晚上发布出现的满眼血丝、黑眼圈,同时还得为发布质量和风险思考解决方案。

发版的风险

由于发版引起的故障或损失占比有多高?

4f92ed205d91fa5c98bed322e0b2701f.png

这个数据没有去仔细分析过,但是由于发版引发故障的比例确实不低,所以有的公司会规定,发版第二天后线上出现问题,如果在规定的时间段不能修复问题,就立即回滚。同时为了避免上述的情况,会采取一些方法减少发版造成的故障和损失,例如在发版时会进行灰度或者蓝绿。

灰度方案

上面关于上线发布的案例大部分人都经历过,或者正在经历,或者还在经历,要跳出这个痛苦的怪圈,需要使用一些方案或者手段,比如灰度。

Spring Cloud Gray是一个灰度发布解决方案,提供server管控端,拥有web界面,可以界面上编辑灰度策略实时控制请求路由;提供多种灰度粒度和灵活的灰度策略,可以根据需要的灰度场景进行组合,支持Http全链路灰度。

支持场景
  • 线上测试 先发布1台实例,用于测试验证,指定测试的流量进入这台实例,其它流量依然进入其它正常的实例。发布成本小,快速测试,不会影响正常用户使用,如果测试不通过,只需回滚这一台实例,用户无感知。

  • 放量 通过金丝雀测试后,可以逐渐放量到新发布的服务器上;放一定比例的流量到灰度服务器上,观察一段时间没异常,可调整加大灰度的流量,将发布产生的风险保持在可控范围内。

  • 多版本控制 允许线上存在多个版本,根据不同的灰度策略将不同的请求或者用户路由到不同版本的服务实例上。

  • 蓝绿发布 蓝绿发布是指线上存在两个版本的代码运行在服务器,请求访问的时候只能访问蓝色版本或者绿版本,可以快速的切换请求流向。

灰度策略

在上一个大版本中,灰度策略是灰度实例的一个属性,这能实现了灰度请求的判定和路由,可是却限定了灰度策略的能力,在新的一版中,将灰度策略从灰度实例中抽取出来,使灰度策略不仅仅只支持实例实例,让它的能力能够去解决更多的问题和场景。

模型介绍

灰度策略模型是灰度路由的其中一个核心模块,也是一个非常基础的模块。请求是无状态的,要识别请求是否为灰度请求,则根据灰度策略去决断,如果请求能够匹配灰度策略的所有条件,那么就判定该请求就是灰度请求。

然而灰度策略是怎样的一种结构呢?其实灰度策略并不复杂,每个灰度策略有一个灰度决策列表,列表中包含一个或多个灰度决策:

af6179f4de8f8579910033a47d8d7140.png

只要灰度决策列表中的灰度决策都判定为通过返回true,灰度策略就返回true,表明请求是灰度请求。示意图如下:

2074554ded309ab0275503905dfcb99b.png

如果决策列表中有一个判定为不能过返回false,则灰度策略就会返回false,表明请求不是灰度请求。示意图如下:

8406923005c837b60e7bd5e74cfe67e3.png

策略组

多个灰度策略可以组合成一个灰度策略组,灰度策略组中的策略是“or”的关系。例如一个请求使用一个灰度策略组去判定,只要灰度策略组中的任意一个灰度策略返回true,就判定该请求是灰度请求。

1ed30b120ef4e6cfc602f1e638038f61.png

灰度粒度

灰度粒度是新加入的一个概念,可以理解为灰度的等级或者是灰度的范围,从小到大分别是实例灰度、版本灰度和服务灰度,三种灰度粒度可以组合使用。

实例灰度

针对实例进行灰度路由,可以设置指定的服务实例做为灰度实例,设置灰度策略,可将匹配灰度策略的灰度请求路由到灰度实例上。没有匹配灰度策略的请求会被路由到正常实例上。
以网关转发请求到service-a为例:

  • service-a有两台机器,其中一台是灰度实例,指定灰度策略:{header.role=test};

  • 有两种类型的请求,分别是header.role=test,header.role=user

请求路由如下示意图:

adec6b690cadefb57751283131287a40.png

Http Header 中role=test的请求会被判定会灰度请求,路由到灰度实例 service-a:8090上; 


role-user的请求会被判定会普通请求,路由到正常实例 service-a:8091上。

版本灰度

针对服务版本进行区分实例,进行路由。实现逻辑跟实例灰度一样,只是会根据服务实例的版本动态区分出灰度的实例。下一步再判定请求是否为灰度版本的灰度请求,如果是,就将请求路由到灰度版本的实例上。
再以网关调用service-a为例:

  • service-a有两个实例,一个实例是"service-a:8090",版本是v2;另一个实例是"service-a:8091",版本是v1;

  • 服务service-a添加一个版本灰度策略,指定的版本是v2,灰度策略是:{header.role=test};

  • 有两种类型的请求,分别是header.role=test,或header.role=user

请求的路由如下示意图:

b5841e70d791bcf6244e61127ec885d3.png

Http Header 中role=test的请求会被判定会灰度请求,路由到灰度实例 service-a:8090上,因为实例service-a:8090的版本是v2; 


role=user的请求会被判定会普通请求,路由到正常实例 service-a:8091上。

服务灰度

服务的灰度控制是目前粒度最大的,这一粒度的灰度不是为了路由,更主要的作用是为了拦截和筛选;拦截不符合条件的请求,筛选出符合条件的服务实例。

d6a6f4bf903d8732dc6a5a0c47735eb6.png

以上图的两种场景举例:1、筛选实例
第一种请求 "http request 1" 满足灰度策略中的第一个灰度决策:{header.role=test}, 第二个灰度决策会从service-a中的实例中去匹配,如果匹配上,会放到路由的实例列表中。

2、拦截
第一种请求 "http request 2" 无法满足灰度策略中的第一个灰度决策:{header.role=test},网关会直接抛出异常,原因是"No instances available for service-a"。

粒度控制

三种灰度的粒度从大到小分别是:服务灰度 > 多版本灰度 > 实例灰度。三种粒度可以组合起来使用,跟灰度策略模型配合,使灰度路由控制足够灵活。

如下示例:

0e2924102aa2a73e6517610cb6b3db22.png

服务service-a有三个实例,分别是

InstanceVersion
service-a:8091v1
service-a:8090v2
service-a:8092

假设请求的请求是 :

curl --location --request GET 'http://gateway/service-a/api/test?version=5&userId=30 \--header 'role: test'

实例筛选流程如下

1、 经过服务级灰度(Instance.version in [v2,v3])后,把version=v1的服务实例service-a:8091过滤掉,得到的划分结果为:

GrayNormal
service-a:8090, service-a:8092

2、 经过版本级灰度(Header.role=test)后,请求中包含header role=test, 灰度策略通过,把version=v2的服务实例service-a:8090放到灰度实例,得到的划分结果为:

GrayNormal
service-a:8090service-a:8092

3、 最后经过实例级灰度后(Parameter.userId=20),请求中的参数userId=30,不能通过灰度策略,将灰度实例service-a:8090排除掉,得到的划分结果为

GrayNormal
service-a:8092

4、 最终请求会被路由到服务实例 service-a:8092


如果请求中的参考userId=20,例如:

curl --location --request GET 'http://gateway/service-a/api/test?version=5&userId=20 \--header 'role: test'

请求中的userId参数值与服务实例的灰度策略能匹配,最后会被路由到服务实例 service-a:8090

全链路灰度

全链路灰度是为了解决一个用户请求的逻辑处理,需要调用多个服务,如果请求调用链中发送的请求能够匹配上对应服务的实例策略,被判定是灰度请求,就会路由到对应服务的灰度实例上;否则,判定为正常请求,就会路由到对应服务的正常实例上。如下:

假如网关接收到的请求是:

curl --request GET 'http://gateway/service-a/api/test?version=5 \--header 'role: test'

整个请求链是:gateway -> service-a -> service-b -> service-c

825ac94ddb670efb63c73fd9808c600b.png

网关会先记录将要透传到所有请求链路的追踪信息:[trackInfo.header.role=test, trackInfo.parameter.version=5]

网关调用service-a时,会将请求路由到service-a:2 这个灰度实例上,因为请求能匹配service-a:2 的灰度策略(trackInfo.header.role=test);

service-a调用service-b时,会将请求路由到service-b:1 这个正常实例上,因为请求不能匹配service-b:2 的灰度策略(trackInfo.parameter.version=6);

service-b调用service-c时,会将请求路由到service-c:2 这个灰度实例上,因为请求能匹配service-c:2 的灰度策略(trackInfo.header.role=test);

接口mock

Mock也抽象了一个基础模型出来,定位是不只做接口的mock,也可以mock其它的能力,当然这有一定的限制,在这篇文章中就先简单介绍下接口mock。

Mock是基于处理模型(Handle Model)实现的,处理模型可以定义各种处理动作,比如Mock各种能力,接口返回是其中一种。

定义了Mock动作后,就要有触发条件或触发入口,触发条件就由Handle Rule定义,在触发入口处判断触发条件是否达到,如果达到就执行Mock 动作。

197e9f4c37d4509a78861aead56781b1.png

示例

例如 Spring Mvc模拟接口返回:

1、 先创建Mock Handle 和 Mock Action

9d2f0d98af7949108c6e04773ec669ae.png

934f648fc46b0078fc94202987ad1e8a.png

2、 创建处理规则

7a5416abf26532ad84a4aef9126aadb1.png

灰度策略:test-version

99ad25e7f594a1c34a768c1e54c340cd.png

3、 通过网关访问服务 service-a

请求到 service-a 添加了mock的实例

返回的内容如下

f65438931409846ec24363111d619c7c.png

确认一下Mock的内容

97b1e4200441f734df487b239c43c9e0.png

body和header, 返回的内容都与Mock定义的内容一致。

请求到其它service-a没有添加mock的实例

返回内容如下

1b32404bef27a50c8a2d12591f75bb09.png

返回的内容就不是Mock的内容了,返回的内容与代码一致了。代码截图见下方:

885ca6f1cabf98edaa72e1898ecfd081.png

Mock支持的触发入口
触发入口Mock类型
Web MVC Filtermock_application_response
WebFlux Filtermock_application_response
zuulmock_server_client_response
feignmock_server_client_response
RestTemplatemock_server_client_response
Long Polling

上一版本中提供了两种client/server的信息交互方式,一种是定时全量刷新,一种是使用spring cloud stream实时推送;定时全量刷新有几个缺点:

  • 不能实时,在server更新后需要一个时间段才能被client刷新。

  • 全量刷新灰度信息随着数据量的增长,server接口会变慢,并且频繁的查询和返回全量数据。

  • client会定时从server端拉取最新的全量数据,但这些信息改动不频繁。

针对上述缺点,在这个版本中,去掉了定时全量刷新,新增了长轮询(Long Polling)增量刷新信息。
下图就是长轮询实时拉取增量信息的示意图:

051fb165001e5dcdcdf0d502b99e4094.png

示意图中的流程如下:
1、 Client启动后发送获取全量信息请求。
2、 Server返回全量信息和最新的sortmark (sortmark是一个信息的时间戳标记,用来比对)。
3、 Client接收到全量信息并初始化后,发送长轮询请求,并携带最新的sortmark。
4、 Server接收到长轮询请求,并使用请求中的sortmark和数据库中的最新sortmark进行比对,如果请求中的sortmark >= 数据库中的最新sortmark,则hold请求,直至超时返回。 
5、 Client接收到Server对长轮询的响应,判断sortmark是否有更新,如果没有,重新发起长轮询请求。
6、 Server接收到长轮询请求并hold住,还没超时,sortmark更新了,表示有新的信息。
7、 Server响应长轮询请求通知Client sortmark更新了。
8、 Client接收到Server对长轮询的响应,并判断sortmark有更新,立刻发起一个请求获取增量信息并将Client标记的最新sortmark做为参数。
9、 Server接收到获取增量信息的接口,返回sortmark > 100的增量信息和最新的sortmark。
10、 Client更新增量信息后,使用最新的sortmark做为参数重新发起长轮询请求。

这样就通过Long Polling实现了Client实时拉取增量信息。

Long Polling 推送同步

Long Polling是一种Http请求,当有多个Server实例时,会随机访问一个Server实例,任何一个Server实例都会Hold住部分Long Polling Request,这样分散了Server的压力,但是也出现了一个问题:当某一个Server实例更新了信息后,其它Server实例如何实时的响应Long Polling Request通知Client信息有更新? 

为了解决这个问题,特地实现了Cluster Sychro机制:当任何一个Server实例在更新信息并响应Long Polling Request的同时,会将更新信息同步给Server Cluster中的其它Server实例,这些Server实例接收到同步的更新信息后,也会响应Long Polling Request通知Client有了更新信息。
示意图如下:

4731871bfd6edaff2cf46d97fb5c7a5d.png

示例中集群中的server1实例更新了sortmark,会同步到集群中其它的server实例中。

Spring Cloud Stream

spring cloud stream推送方式仍然支持,通过MQ将更新信息推送到Client。

d4b3cbf96e591d3da7f8c9f52cef4c93.png

项目方向

有一次脑海中冒出一个画面:上线发布可以随时进行,不必顾虑时间、环境;按即定计划管理放量,让发布不再有故障;可以随时控制流量/请求路由,及时有效。

我认为这是未来灰度的方向,要做到这三点,需要实现服务实例级的所有请求/流量的路由控制,包括MQ流量。任重、路远,接下来会先考虑抽取灰度内核,实现各种Plugin,支持不同场景、不同业务的流量路由控制。

下一小阶段

  • 将灰度核心代码、核心包单独抽取出来

  • 支持dubbo

项目地址

Github: https://github.com/SpringCloud/spring-cloud-gray

系列文章

Spring Cloud Gray - 微服务灰度中间件Spring cloud 多版本控制及灰度发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值