Spring Cloud Gray 3.0 灰度解决方案
灰度方案
Spring Cloud Gray是一个灰度发布解决方案,提供server管控端,拥有web界面,可以界面上编辑灰度策略实时控制请求路由;提供多种灰度粒度和灵活的灰度策略,可以根据需要的灰度场景进行组合,支持Http全链路灰度。
原文:https://mp.weixin.qq.com/s/WnE25klxLvbKZoBFHHuyfA
Github: https://github.com/SpringCloud/spring-cloud-gray
支持场景
线上测试
先发布1台实例,用于测试验证,指定测试的流量进入这台实例,其它流量依然进入其它正常的实例。发布成本小,快速测试,不会影响正常用户使用,如果测试不通过,只需回滚这一台实例,用户无感知。
放量
通过金丝雀测试后,可以逐渐放量到新发布的服务器上;放一定比例的流量到灰度服务器上,观察一段时间没异常,可调整加大灰度的流量,将发布产生的风险保持在可控范围内。
多版本控制
允许线上存在多个版本,根据不同的灰度策略将不同的请求或者用户路由到不同版本的服务实例上。
蓝绿发布
蓝绿发布是指线上存在两个版本的代码运行在服务器,请求访问的时候只能访问蓝色版本或者绿版本,可以快速的切换请求流向。
灰度策略
在上一个大版本中,灰度策略是灰度实例的一个属性,这能实现了灰度请求的判定和路由,可是却限定了灰度策略的能力,在新的一版中,将灰度策略从灰度实例中抽取出来,使灰度策略不仅仅只支持实例实例,让它的能力能够去解决更多的问题和场景。
模型介绍
灰度策略模型是灰度路由的其中一个核心模块,也是一个非常基础的模块。请求是无状态的,要识别请求是否为灰度请求,则根据灰度策略去决断,如果请求能够匹配灰度策略的所有条件,那么就判定该请求就是灰度请求。
然而灰度策略是怎样的一种结构呢?其实灰度策略并不复杂,每个灰度策略有一个灰度决策列表,列表中包含一个或多个灰度决策:
只要灰度决策列表中的灰度决策都判定为通过返回true
,灰度策略就返回true
,表明请求是灰度请求。示意图如下:
如果决策列表中有一个判定为不能过返回false
,则灰度策略就会返回false
,表明请求不是灰度请求。示意图如下:
策略组
多个灰度策略可以组合成一个灰度策略组,灰度策略组中的策略是or
的关系。例如一个请求使用一个灰度策略组去判定,只要灰度策略组中的任意一个灰度策略返回true
,就判定该请求是灰度请求。
灰度粒度
灰度粒度是新加入的一个概念,可以理解为灰度的等级或者是灰度的范围,从小到大分别是实例灰度、版本灰度和服务灰度,三种灰度粒度可以组合使用。
实例灰度
针对实例进行灰度路由,可以设置指定的服务实例做为灰度实例,设置灰度策略,可将匹配灰度策略的灰度请求路由到灰度实例上。没有匹配灰度策略的请求会被路由到正常实例上。
以网关转发请求到service-a
为例:
service-a
有两台机器,其中一台是灰度实例,指定灰度策略:{header.role=test}
- 有两种类型的请求,分别是
header.role=test
,header.role=user
请求路由如下示意图:
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
请求的路由如下示意图:
Http Header 中role=test
的请求会被判定会灰度请求,路由到灰度实例service-a:8090
上,因为实例service-a:8090
的版本是v2
而role=user
的请求会被判定会普通请求,路由到正常实例service-a:8091
上
版本灰度需要配置版本信息:
eureka.instance.metadata-map.version=v1
服务灰度
服务的灰度控制是目前粒度最大的,这一粒度的灰度不是为了路由,更主要的作用是为了拦截和筛选;拦截不符合条件的请求,筛选出符合条件的服务实例。
以上图的两种场景举例:
筛选实例
第一种请求 "http request 1" 满足灰度策略中的第一个灰度决策:{header.role=test}
,第二个灰度决策会从service-a中的实例中去匹配,如果匹配上,会放到路由的实例列表中。
拦截
第一种请求 "http request 2" 无法满足灰度策略中的第一个灰度决策:{header.role=test}
,网关会直接抛出异常,原因是No instances available for service-a
。
粒度控制
三种灰度的粒度从大到小分别是:服务灰度 > 多版本灰度 > 实例灰度。三种粒度可以组合起来使用,跟灰度策略模型配合,使灰度路由控制足够灵活。
如下示例:
服务service-a有三个实例,分别是
Instance | Version |
---|---|
service-a:8091 | v1 |
service-a:8090 | v2 |
service-a:8092 | v3 |
假设请求的请求是 :
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
过滤掉,得到的划分结果为:
Gray | Normal |
---|---|
service-a:8090 , service-a:8092 |
(2)经过版本级灰度(Header.role=test)
后,请求中包含header role=test
,灰度策略通过,把version=v2
的服务实例service-a:8090
放到灰度实例,得到的划分结果为:
Gray | Normal |
---|---|
service-a:8090 |
service-a:8092 |
(3)最后经过实例级灰度后(Parameter.userId=20)
,请求中的参数userId=30
,不能通过灰度策略,将灰度实例service-a:8090排除掉,得到的划分结果为:
Gray | Normal |
---|---|
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
网关会先记录将要透传到所有请求链路的追踪信息:[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 动作。
接口mock演示
例如Spring MVC模拟接口返回:
(1)先创建处理策略Mock Handle
和处理规则Mock Action
(2)创建处理规则
灰度策略:test-version
(3)通过网关访问服务service-a
请求到service-a
添加了mock的实例,返回的内容如下
确认一下Mock的内容
body
和header
,返回的内容都与Mock定义的内容一致。
(4)请求到其它service-a
没有添加mock的实例
返回内容如下:
返回的内容就不是Mock的内容了,返回的内容与代码一致。代码截图见下方:
Mock支持的触发入口
触发入口 | Mock类型 |
---|---|
Web MVC Filter |
mock_application_response |
WebFlux Filter |
mock_application_response |
zuul |
mock_server_client_response |
feign |
mock_server_client_response |
RestTemplate |
mock_server_client_response |
Long Polling
上一版本中提供了两种client/server
的信息交互方式,一种是定时全量刷新,一种是使用Spring Cloud Stream
实时推送;定时全量刷新有几个缺点:
- 不能实时,在Server更新后需要一个时间段才能被Client刷新
- 全量刷新灰度信息随着数据量的增长,Server接口会变慢,并且频繁的查询和返回全量数据
- Client会定时从Server端拉取最新的全量数据,但这些信息改动不频繁
针对上述缺点,在这个版本中,去掉了定时全量刷新,新增了长轮询(Long Polling
)增量刷新信息。
下图就是长轮询实时拉取增量信息的示意图:
示意图中的流程如下:
(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有了更新信息。
示意图如下:
示例中集群中的server1实例更新了sortmark,会同步到集群中其它的server实例中。
Spring Cloud Stream
Spring Cloud Stream
推送方式仍然支持,通过MQ将更新信息推送到Client。
项目方向
有一次脑海中冒出一个画面:上线发布可以随时进行,不必顾虑时间、环境;按即定计划管理放量,让发布不再有故障;可以随时控制流量/请求路由,及时有效。
这是未来灰度的方向,要做到这三点,需要实现服务实例级的所有请求/流量的路由控制,包括MQ流量。任重、路远,接下来会先考虑抽取灰度内核,实现各种Plugin,支持不同场景、不同业务的流量路由控制。
下一小阶段
- 将灰度核心代码、核心包单独抽取出来
- 支持dubbo
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/26/spring-cloud-gray-3-gray-publish-solution/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论