作者:十眠
目前的微服务架构中,通常蕴含服务消费者、服务提供者、注册核心、服务治理四元素,其中服务消费者会向注册核心获取服务提供者的地址列表,并依据路由策略选出须要调用的指标服务提供者地址列表,最初依据负载算法间接调用提供者。当大规模生产环境下,服务消费者从注册核心获取到的服务提供者地址列表过大时,采纳传统的路由形式在每次服务调用时都进行大量地址路由选址逻辑,导致服务调用性能低下,资源耗费过多。
云原生场景下,几千、上万乃至十万节点的集群曾经不再常见,如何高效实现这种大规模环境下的选址问题曾经成为了必须解决的问题。
流量路由场景
流量路由,顾名思义就是将具备某些属性特色的流量,路由到指定的指标。流量路由是流量治理中重要的一环,多个路由如同流水线一样,造成一条路由链,从所有的地址表中筛选出最终目标地址汇合,再通过负载平衡策略抉择拜访的地址。开发者能够基于流量路由规范来实现各种场景,如灰度公布、金丝雀公布、容灾路由、标签路由等。
路由选址的范式如下:target = rn(…r3(r2(r1(src))))
上面将借着介绍 OpenSergo 对于流量路由所定义的 v1alpha1 规范,来通知大家实现流量路由所需的技术。
OpenSergo 流量路由 v1alpha1 规范
流量路由规定(v1alpha1) 次要分为三局部:
- Workload 标签规定 (WorkloadLabelRule):将某一组 workload 打上对应的标签,这一块能够了解为是为 APISIX 的各个上游打上对应的标签
- 流量标签规定 (TrafficLabelRule):将具备某些属性特色的流量,打上对应的标签
- 依照 Workload 标签和流量标签来做匹配路由,将带有指定标签的流量路由到匹配的 workload 中
咱们能够赋予标签不同的语义,从而实现各个场景下的路由能力。
给 Workload 打标签:
咱们对新版本进行灰度时,通常会有独自的环境,独自的部署集。咱们将独自的部署集打上 gray 标签(标签值可自定义),标签会参加到具体的流量路由中。
咱们能够通过间接在 Kubernetes workload 上打 label 的形式进行标签绑定,如在 Deployment 上打上 traffic.opensergo.io/label: gray 标签代表灰度。对于一些简单的 workload 打标场景(如数据库实例、缓存实例标签),咱们能够利用 WorkloadLabelRule CRD 进行打标。示例:
apiVersion: traffic.opensergo.io/v1alpha1
kind: WorkloadLabelRule
metadata:
name: gray-sts-label-rule
spec:
workloadLabels: ['gray']
selector:
database: 'foo_db'
给流量打标:
假如当初须要将内部测试用户灰度到新版主页,测试用户 uid=12345,UID 位于 X-User-Id header 中,那么只须要配置如下 CRD 即可:
apiVersion: traffic.opensergo.io/v1alpha1
kind: TrafficLabelRule
metadata:
name: my-traffic-label-rule
labels:
app: my-app
spec:
selector:
app: my-app
trafficLabel: gray
match:
- condition: "==" # 匹配表达式
type: header # 匹配属性类型
key: 'X-User-Id' # 参数名
value: 12345 # 参数值
- condition: "=="
value: "/index"
type: path
通过上述配置,咱们能够将 path 为 /index,且 uid header 为 12345 的 HTTP 流量,打上 gray 标,代表这个流量为灰度流量。
讲完场景,上面咱们来谈纯技术,咱们看看传统的流量路由选址形式是什么样的逻辑。
传统流量路由选址形式
一句话形容就是:每次调用时候依据申请中的条件计算结果地址列表,并遍历以后的地址列表,进行地址过滤。
每个 Router 在每次调用都须要动静计算以后申请须要调用的地址列表的子集,再传递给下一个 Router,伪代码如下:
List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) {
Tag = invocation.getTag;
List<Invoker<T>> targetInvokers = new List<>();
for (Invoker invoker: invokers) {if (invoker.match(Tag)) {targetInvokers.add(invoker);
}
}
return targetInvokers;
}
咱们剖析一下该算法的工夫复杂度,发现是 O(n)的工夫复杂度,每次每个 Router 的 route 调用逻辑都会遍历 invokers 列表,那么当 invokers 数量过大,每次 match 计算的逻辑过大,那么就会造成大量的计算成本,导致路由效率低下。
那么,针对低下效率的计算逻辑,咱们是否有什么优化的空间?让咱们先来剖析与形象 RPC 的流量路由选址过程。
路由逻辑剖析与思考
RPC 的路由选址能够形象总结为四个过程:
- 流量依据流量个性与路由规定抉择对应的 Tag
- 依据 Tag 抉择对应的服务端地址列表
- 将上一个 Router 的路由后果地址列表 (传入的参数) 与合乎以后 Router 的路由后果的地址列表进行汇合与操作
- 将 (3.) 的后果传给下一个 Router
其中过程一因为流量始终在变,这个过程的计算必不可少,然而过程二,在绝大多数路由场景下其只会在地址推送于路由规定变动时才有从新计算的必要,如果在每次调用过程中进行大量地址计算,会导致调用性能损耗过大。是否能够把过程二的逻辑进行异步计算并将计算的地址后果进行缓存缓存?防止大量的反复计算。
同时思考到过程三中的操作属于汇合与逻辑,是否有更高效的数据结构?联想到 BitMap,是否能够将地址列表通过 BitMap 模式存储从而使汇合与的工夫复杂度升高一个数量级。
既然想到了能够优化的 idea,那么让咱们来一起设计新的计划并且来实现它!
高效动静选址机制的设计与实现
假如目前有如下 6 条地址,咱们须要实现依照 Tag 路由跟依照 AZ 路由的两个能力:
所以咱们须要实现两个 Router,假如是 TagRouter 跟 AZRouter,TagRouter 有 3 种 tag 别离是 a、b、c;AZRouter 有三种 az 类型别离是 az_1、az_2、az_3 依照如上假如所示,其实是有 3 3 种组合,会存在 3 3 6 这么多的 URL 援用;假如以后的申请须要去 tag=a&az=az_2,那么依照 Dubbo 原先的形式咱们须要在每个 Router 外面遍历一次上一个 Router 计算实现的地址列表,假如存在 M 个 Router、N 条 URL,那么极其状况下每一次调用须要计算 MN 次。
那么如果依照咱们 StateRouter 路由形式会是怎么一个样子呢?
首先当地址告诉下来后,或者路由规定变动时,每个 Router 会先将全量地址依照各自 Router 的路由规定将地址进行计算并将计算结果通过 BitMap 形式存下来;如下图所示:
整体存储架构
咱们还是以上述 6 个 URL 为例介绍:
依照路由规定,通过 BitMap 形式进行地址缓存,接下来路由计算时,咱们只需从 AddrCache 中取出对应的 addrPool 传递给下一个 Router 即可。
如果以后申请是须要满足 Tag=a & az=az_2,那么咱们该如何路由呢?
- TagRouter 逻辑
- 依照流量计算出指标的 Tag,假如是 a
- 而后 AddrCache.get(TagRouter).get(a),取出对应的 targetAddrPool
- 将上一次传入的 addrPool 与 targetAddrPool 取出 resultAddrPool
- 将 resultAddrPool 传入 AZRouter
- AZRouter 逻辑
- 依照流量计算出指标的 Tag,假如是 az_2
- 而后 AddrCache.get(AZRouter).get(az_2),取出对应的 targetAddrPool
- 将上一次传入的 addrPool 与 targetAddrPool 取出 resultAddrPool
- 将 resultAddrPool 为最终路由后果,传递给 LoadBalance
要害源码的伪代码如下:
//List<Invoker<T>> -> Bitmap
List<Invoker<T>> route(List<Invoker<T>> invokers, Map<Router, Map<Tag, List<Invoker<T>>> cache, URL url, Invocation invocation) {pool = cache.get(this);
Tag = invocation.getTag;
return pool.get(Tag) & invokers;
}
Dubbo3 应用案例
Dubbo3 在总体性能、集群吞吐量、稳定性等方面相比上一代微服务框架都有了显著的晋升,尤其实用于大规模微服务集群实际的场景,StateRouter 即是其中十分重要的一环,该计划通过 Dubbo3 曾经在包含阿里之内的泛滥企业实际验证。
感兴趣实现细节的同学能够 在「阿里巴巴中间件」后盾回复关键词【dubbo3】获取 参考代码
另外提一下,感兴趣的开发者可搜寻并退出贡献者群钉钉群 31982034,收费参加每周一次的 Dubbo 技术分享并有机会赢取定制礼品,如果您是 Dubbo3 企业用户欢送退出钉钉交换群 34129986。
总结
目前 MSE 服务治理的 离群实例摘除、标签路由、金丝雀公布、全链路灰度等性能曾经应用该路由计划,通过咱们的压测与演练,在 CPU、RT 等方面均有不少晋升,以 Demo 利用为例 (服务调用的跳数为 2,上游 30 节点,每个节点 1c2g) 其中调用 RT 晋升约 6.7%。
更多服务治理能力与规范的摸索
随着分布式服务架构的一直演进带来诸多简单的稳定性与易用性问题,繁多的监控已无奈满足架构的演进。在古代微服务架构中,咱们须要一些伎俩来对简单的微服务架构进行“治理”。微服务治理就是通过全链路灰度、无损高低线、流控降级、异样流量调度、数据库治理等技术手段来缩小甚至防止公布和治理大规模利用过程中遇到的稳定性问题,对微服务畛域中的各个组件进行治理。服务提供者、消费者、注册核心、服务治理,形成古代微服务架构中重要的几环。
服务治理是微服务革新深刻到肯定阶段之后的必经之路,在这个过程中咱们一直有新的问题呈现。
- 除了全链路灰度,服务治理还有没其余能力?
- 服务治理能力有没一个规范的定义,服务治理能力蕴含哪些?
- 多语言场景下,有无全链路的最佳实际或者规范?
- 异构微服务如何能够对立治理?
当咱们在摸索服务治理的过程中,咱们在对接其余微服务的时候,咱们发现治理体系不同造成的困扰是微小的,买通两套甚者是多套治理体系的老本也是微小的。为此咱们提出了 OpenSergo 我的项目。OpenSergo 要解决的是不同框架、不同语言在微服务治理上的概念碎片化、无奈互通的问题。
咱们在微服务治理方面的摸索不仅仅在流量路由方面,近日 OpenSergo 公布了流量路由与流控降级与容错的 v1alpha1 的规范。OpenSergo 社区也在联结 Apache APISIX、Apache Dubbo、Spring Cloud Alibaba、Sentinel、CloudWego 等各个社区在进行进一步的单干,心愿社区来一起探讨与定义对立的服务治理规范。以后社区也在联结 bilibili、字节跳动等企业一起共建规范,也欢送感兴趣的开发者、社区与企业一起退出到 OpenSergo 服务治理规范共建中。欢送大家退出 OpenSergo 社区交换群(钉钉群)进行探讨:34826335。
MSE 注册配置核心专业版首购享 9 折优惠,MSE 云原生网关预付费全规格享 9 折优惠。点击此处,即享优惠~