作者:李来 华为云高级软件工程师
一、全链路流量标签透传
在微服务架构中,流量标签用于对流量进行标记和分类,能够在微服务之间实现更精细的路由、负载均衡和流控等流量治理能力。以HTTP报文为例,每一条header都可以是一条流量标签,比如x-sermant-version: v1表示通过Sermant流量染色添加的标签,可以用于标识该请求流量对应的的版本信息。
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Content-Length: 1000
Content-Type: application/json
Host: api.github.com
Origin: https://github.com
Referer: https://github.com
x-sermant-version: v1 //自定义header,表示通过Sermant流量染色添加的标签
流量标签可以在微服务治理场景中发挥作用的核心是流量标签能够在调用链上的微服务之间传递,也即——全链路流量标签透传。全链路流量标签透传是指在分布式系统中,将请求的标签信息(例如上面报文中的x-sermant-version: v1)从请求的起点一直透传到请求的终点,以便于在整个请求链路中进行流量治理。流量标签透传可以在各种服务治理场景中发挥关键作用。例如,全链路灰度发布场景下,微服务调用方可以根据请求中携带的流量标签信息将请求路由到灰度的新版本微服务实例。在流量控制场景中,流量标签透传还可以用于实施精准的流量控制策略,例如限流、熔断和降级,防止系统在高负载下崩溃。
二、如何实现全链路标签透传
在Java中目前主流的全链路标签透传的实现方式分为两种,一种是通过在SDK中集成标签传递能力,配合API网关来实现,另一种则是通过应用上挂载的JavaAgent来实现。
下图是Spring Cloud Tencent在SDK中实现的流量标签透传,该图展示了一个通过API网关来染色的金丝雀发布场景。金丝雀发布(canary)是指在生产环境上引一部分实际流量对一个新版本进行测试,测试新版本的性能和表现,在保证系统整体稳定运行的前提下,尽早发现新版本在实际环境上的问题。这种SDK实现的标签透传方式一般使用网关来添加染色标签,然后在使用Spring Cloud Tencent微服务框架的应用中来透传标签,例如图中的用户中心、积分中心、活动中心会透传网关染色的canary=true标签。
图 - Spring Cloud Tencent流量标签透传在金丝雀发布场景的应用
注:图片来源https://github.com/polarismesh/polaris/issues/631
通过JavaAgent来实现流量标签的透传,典型的例子是Skywalking对于链路trace信息的透传。在应用程序中嵌入Skywalking的JavaAgent,通过在代码中插入埋点,它会在调用链的入口节点处生成一个标记链路的traceId,然后在链路中的各个节点对调用链信息的进行传递,收集应用程序的调用链路信息。Skywalking的关注的重心是链路的追踪,也就是调用链信息的标签透传,透传的标签的来源是在入口处生成一个唯一的traceId,至于通用的流量染色能力,并不是Skywaling的场景。
不同的流量标签透传实现方式各异,也有各自适用的场景。spring-cloud-tencent需要通过引入SDK来使用,Skywalking的场景主要在于链路追踪,并不能很好的支持自定的标签染色和透传。以上都有各自的局限性,适用场景范围较小。
在微服务治理领域,一个自定义场景丰富,适用面广泛的全链路标签透传解决方案是流量治理多样化发展的基石。作为服务网格,Sermant提供了一套支持自定义的流量染色和标签透传的全链路标签透传方案。
三、Sermant在全链路流量标签透传的探索
Sermant是基于Java字节码增强技术的无代理服务网格,不仅是一个开箱即用的服务治理工具,也同样是一个易用的服务治理能力开发框架。用户只需要在Java应用启动时添加一行 -javaagent:/xxx/sermant-agent.jar即可一键以非侵入的方式接入Sermant的服务治理功能。
上文提到,全链路标签透传包含染色和透传两个部分。Sermant针对这两部分,提供两个插件来分别独立实现流量染色和标签透传的能力,分别是标签路由插件(目前染色能力放在路由插件中,最终将拆分成独立的流量染色插件,结合标签透传,为多种多样的流量治理场景提供基础能力)以及流量标签透传插件。
下图为Sermant的整体使用方式,同样用第二章中提到的金丝雀发布场景来进行说明。各服务实例通过-javaagent命令挂载Sermant启动,通过动态配置中心可以在服务运行期间动态地修改流量染色的规则以及标签透传的规则,并推送至各服务实例的Sermant。在入口处接收到外部请求流量时,入口服务实例的Sermant会去匹配当前流量是否符合规则,如符合则进行染色,比如图中稳定版本的服务A。各个服务实例的Sermant会匹配流量标签透传的规则,来识别哪些标签需要往下游透传,图中以V1版本标签需要透传来演示,整个流程实现了微服务的金丝雀发布能力。
图 – Sermant全链路流量标签透传在金丝雀发布场景的示例
在Sermant中流量染色能力和流量标签透传能力作为基础,可以为其他插件更复杂的流量治理场景提供底座能力,使得流量治理能力的开发变得更加简单。因流量染色和标签透传为Sermant的独立插件,基于API网关进行流量染色的用户可以选择不启用Sermant的流量染色能力。Sermant中基于全链路流量标签透传的高阶服务治理能力的依赖关系如下所示:
图 – Sermant服务治理能力依赖关系
3.1 流量染色
流量染色的本质就是使用不同的标签来标识不同的流量类型。当前版本中,Sermant先针对Dubbo和SpringCloud两种主流微服务框架适配了流量染色能力。流量染色目前位于Sermant的标签路由插件中,可作为独立能力使用。流量染色的主要处理流程如下:
图 – Sermant的流量染色流程
首先在动态配置中心,我们可以下发服务粒度的染色规则,用于判断入方向的流量是否符合特定条件,若符合则对该流量进行染色,在同一调用链的出方向流量中携带该染色标签。在Sermant中,染色规则的数据模型如下:
- kind: route.sermant.io/lane # 路由规则为染色规则的类型
description: lane # 规则描述
rules:
- precedence: 1
match:
method: get # http请求方式,不配置表示匹配
path: "/foo" # http请求路径,不配置表示匹配
protocol: http # 流量入口为http协议,http协议只会匹配headers/parameters
headers: # http headers匹配,不配置表示匹配
id:
exact: '1'
parameters: # http url parameters匹配,不配置表示匹配
name:
exact: 'foo'
route:
- tag-inject:
x-sermant-flag1: gray1
weight: 60
上述规则的含义为,我们尝试匹配precedence: 1这条规则,即流量入口为http协议的请求,如果请求接口url为/foo,请求方式为get,headers中id等于1,url parameters中name等于foo,就对满足条件的60% 流量打上【x-sermant-flag1: gray1】的标记,并在调用下游时进行传递。在染色规则中,我们也支持下发多条匹配规则并配置优先级。流量染色能力的使用可参考官网使用文档。
在实际应用中,可以根据需要自定义配置规则,例如我们需要对http请求的/getPrice这个接口的流量染色,如果要给携带city=Shanghai或者city=Hangzhou的全部流量在后续链路中都加上【x-sermant-region: east】染色标记,那么规则就可以如下配置。
- kind: route.sermant.io/lane
description: lane
rules:
- precedence: 1
match:
method: get
path: "/getPrice"
protocol: http
headers:
city:
in: ['Shanghai', 'Hangzhou']
route:
- tag-inject:
x-sermant-region: east
weight: 100
总之,染色的规则可以自定义配置,并且支持运行时动态修改并可立即生效,若无需染色,则删除动态配置即可。
3.2 流量标签透传
在经过3.1中的步骤对流量进行染色后,就可以利用Sermant的流量标签透传能力将染色标签进行全链路透传。Sermant对于流量标签透传分为两大类:跨进程透传和进程内透传。跨进程透传是指在不同的服务实例进程中传递流量标记,例如http请求的客户端和服务端。进程内透传是指在一个服务实例的进程内传递流量标签,包括线程内传递和跨线程传递。
如下图所示,根据需要适配的标签传递的类型,我们又分为接口调用(包括http协议和rpc协议)和消息队列(包括kafka、RocketMQ等等)两种类型。
图 – Sermant中http/rpc请求的标签透传过程
图 – Sermant中消息队列的标签透传过程
3.2.1 跨进程透传
跨进程流量标签透传包括http协议、rpc协议以及消息队列。我们借助http请求的header、Dubbo请求的attachment、grpc请求的metadata、kafka消息的header等来将流量标签在全链路中透传下去。
在Sermant流量标签透传插件中,对于客户端,我们利用字节码增强在http、rpc发送请求以及消息队列生产消息处构造切面,并在切面处获取当前进程内的流量标签,在发送请求时或生产消息时将流量标签注入header、attachment等。对于服务端,在http、rpc接收请求以及消息队列消费消息的切面处,将header、attachment等里面的流量标签取出,在服务端进程内继续透传。一次http或rpc的跨进程标签传递过程如下图所示:
图 – Sermant跨进程透传标签的过程(http/rpc)
消息队列的标签透传与之类似,不同之处在于流量标记会在生产时发送到中间件的服务端,消费时从服务端拉取消息并解析出流量标签。
3.2.2 进程内透传
流量标签在进程内透传的情况分为两种:线程内和跨线程。线程内的场景Sermant按照常规做法,使用ThreadLocal线程变量来传递标签,此处不作赘述。
跨线程的场景,主要分为三类:直接 new Thread()、普通线程池和定时线程池。针对new Thread(),使用JDK原生的InheritableThreadLocal来在父子线程中传递标记。
针对普通线程池,Sermant拦截了它的execute和submit方法,在执行新的线程任务时,将父线程的流量标记透传至Runnable或Callable对象中,在子线程中就可以继续将流量标签传递下去。定时线程池与之类似,通过拦截schedule、scheduleAtFixedRate以及scheduleWithFixedDelay方法,将父线程流量标签传递至子线程。
图 – Sermant跨线程透传标签的过程
以上三种方式可基本覆盖跨线程的所有场景,基于对它们的增强,Sermant得以实现跨线程的流量标签透传。
3.2.3 流量标签透传的动态配置
流量标签透传也支持动态配置是否开启以及透传哪些标签,如下所示:
tag.transmission.config:
# 是否开启流量标签透传
enabled: true
# 需要透传的流量标签的key的匹配规则, 支持等于、前缀、后缀匹配
matchRule:
# 精确匹配
exact: ["id", "name"]
# 前缀匹配
prefix: ["x-sermant"]
# 后缀匹配
suffix: [""]
当前支持对需要透传标签的键精确匹配、前缀匹配以及后缀匹配方式。在上一章介绍的染色规则配置完成后,再把染色标签配置在流量标签透传的配置中,这样就可以实现全链路标签透传从入口染色到整条链路传递的完整解决方案。
四、Sermant全链路流量标签透传的应用场景
全链路流量标签透传是微服务流量治理的核心基础,它将流量标签这一关键钥匙准确地传递给每一个服务实例,让流量可以按照规划的轨迹流动。全链路流量标签透传可以应用在诸多服务治理场景,如全链路灰度发布、流控等。
4.1 基于全链路流量标签透传的全链路灰度发布场景
在全链路灰度发布场景中,通常需要将新版本的代码逐步地发布到生产环境中,以便在生产环境中进行测试和验证。如下图所示,gray1版本只部署了部分服务,gray2版本部署了全部服务。利用全链路流量标签透传的能力,可以根据服务实例的灰度版本信息,来让流量沿着灰度链路进行流转。稳定版本的流量只调用稳定版本的服务实例,灰度版本的流量优先调用当前灰度版本实例,如果中间某一服务不存在灰度实例,则调用稳定版本的实例。无论中间某一次调用的服务是稳定版本还是灰度版本,流量标签持续透传,灰度流量永远优先选择灰度版本进行路由。
图 – 利用Sermant来实现全链路灰度发布的流程
在当前版本的Sermant中,标签路由插件包含了流量染色以及标签路由的能力(功能独立,最终将拆为两个独立插件),流量标签透传插件支持了标签透传能力。上图全链路灰度发布场景的实现只需要同时挂载这两个插件即可实现。使用的主要流程为:
(1)标签路由插件配置流量的染色规则,比如灰度流量添加x-sermant-version:gray1,x-sermant-version:gray2标签。(该步骤可选,可用API网关等其他方式染色)
(2) 标签透传插件配置x-sermant-version键的标签在全链路中进行透传。
(3)标签路由插件配置流量的全链路路由规则,使得gray1流量优先调用gray1版本实例,使得gray2流量优先调用gray2版本实例。
4.2 基于全链路流量标签透传的流控场景
在流量控制场景中,也可以利用标签透传的特性来通过流量标签进行精细化的流量控制。通常我们可以通过流量的url、header、token、请求参数等来对流量做分组。比如在双11这类场景中,服务的单个 API 提供的能力是有限的,但是实际的峰值调用需求可能是极限QPS的2倍,这种场景下可以利用url来对流量进行分组,对不同类型的流量实现精准流控,在保证核心业务调用不被限流的情况下满足大部分人的需求。
例如下图所示,在入口应用对不同的流量进行染色分组,可以给需要保障的核心重要接口添加vip标签,对非重要接口添加normal标签。服务A可根据不同分组流量来实现差异化的限流策略,对normal限制20%的极限QPS,对vip流量限制80%的QPS。在服务B中可以根据不同分组流量的重要性程度,保障vip流量在服务遇到非致命的错误时,可以通过重试的方式避免流量的最终调用失败,对错误率容忍程度更高的normal流量则不配置流量重试策略。
图 – 利用Sermant来实现基于标签的流控的过程
当前版本的Sermant也包含了流控插件,该插件支持限流、熔断、隔离仓、错误注入与重试、熔断指标采集、系统规则、系统自适应流控能力,并且支持配置中心动态配置规则,实时生效。上图的场景可挂载标签路由插件(提供染色能力)、流量标签透传插件以及流控插件来做实现。使用方式如下:
(1)标签路由插件配置流量的染色规则,比如针对不同账户的流量添加x-sermant-group:vip,x-sermant-group:normal标签。(该步骤可选,可用API网关等其他方式染色)
(2)标签透传插件配置透传规则,比如x-sermant-group键的标签在全链路中进行透传。
(3)流控插件配置流控规则,比如header中携带x-sermant-group:vip的流量在服务B中做相应的重试策略,x-sermant-group:normal的流量在服务A中配置限流策略。
五、总结
Sermant将流量染色和流量标签透传的能力以插件化的形式整合起来,形成了一套完整的全链路流量标签透传的解决方案。流量标签本质上是将流量进行分组,基于这套解决方案,我们可以使得流量标签遍布于整个流量拓扑中,在全链路灰度发布、限流降级、故障诊断恢复、负载均衡、流量监控等场景中发挥重要作用。此外,Sermant的插件化架构还可以支持用户单独使用流量染色能力或流量标签透传能力,以积木化的使用方式自由组合各个插件模块。Sermant的非侵入特性也能够让用户做到0代码修改地一键接入全链路流量标签透传能力。
Sermant作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。
- Sermant 官网: https://sermant.io
- GitHub 仓库地址: https://github.com/huaweicloud/Sermant
- 扫码加入 Sermant 社区交流群