作者:十眠

现在的微服务架构中,一般包含服务顾客、服务提供者、注册中心、服务办理四元素,其中服务顾客会向注册中心获取服务提供者的地址列表,并依据路由战略选出需求调用的方针服务提供者地址列表,终究依据负载算法直接调用提供者。当大规模出产环境下,服务顾客从注册中心获取到的服务提供者地址列表过大时,采用传统的路由方法在每次服务调用时都进行很多地址路由选址逻辑,导致服务调用功能低下,资源耗费过多。

云原生场景下,几千、上万乃至十万节点的集群已经不再罕见,怎么高效完结这种大规模环境下的选址问题已经成为了有必要处理的问题。

流量路由场景

流量路由,望文生义便是将具有某些特点特征的流量,路由到指定的方针。流量路由是流量办理中重要的一环,多个路由好像流水线一样,形成一条路由链,从一切的地址表中筛选出终究目的地址调集,再经过负载均衡战略挑选访问的地址。开发者能够基于流量路由规范来完结各种场景,如灰度发布、金丝雀发布、容灾路由、标签路由等。

路由选址的范式如下:target = rn(…r3(r2(r1(src))))

Dubbo 3 StateRouter:下一代微服务高效流量路由

下面将借着介绍 OpenSergo 关于流量路由所界说的 v1alpha1 规范,来告知我们完结流量路由所需的技术。

OpenSergo 流量路由 v1alpha1 规范

流量路由规矩(v1alpha1) 主要分为三部分:

  • Workload 标签规矩 (WorkloadLabelRule):将某一组 workload 打上对应的标签,这一块能够理解为是为 APISIX 的各个上游打上对应的标签
  • 流量标签规矩 (TrafficLabelRule):将具有某些特点特征的流量,打上对应的标签
  • 依照 Workload 标签和流量标签来做匹配路由,将带有指定标签的流量路由到匹配的 workload 中

咱们能够赋予标签不同的语义,从而完结各个场景下的路由才能。

Dubbo 3 StateRouter:下一代微服务高效流量路由

给 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 的路由选址能够笼统总结为四个进程:

  1. 流量依据流量特性与路由规矩挑选对应的 Tag
  2. 依据 Tag 挑选对应的服务端地址列表
  3. 将上一个 Router 的路由成果地址列表(传入的参数)与符合当时Router的路由成果的地址列表进行调集与操作
  4. 将(3.)的成果传给下一个 Router

Dubbo 3 StateRouter:下一代微服务高效流量路由

其中进程一因为流量一直在变,这个进程的核算必不可少,但是进程二,在绝大多数路由场景下其只会在地址推送于路由规矩变化时才有重新核算的必要,假如在每次调用进程中进行很多地址核算,会导致调用功能损耗过大。是否能够把进程二的逻辑进行异步核算并将核算的地址成果进行缓存缓存?防止很多的重复核算。

同时考虑到进程三中的操作属于调集与逻辑,是否有更高效的数据结构?联想到 BitMap,是否能够将地址列表经过 BitMap 形式存储从而使调集与的时间杂乱度降低一个数量级。

既然想到了能够优化的 idea,那么让咱们来一同规划新的方案并且来完结它!

高效动态选址机制的规划与完结

假定现在有如下 6 条地址,咱们需求完结依照 Tag 路由跟依照 AZ 路由的两个才能:

Dubbo 3 StateRouter:下一代微服务高效流量路由

所以咱们需求完结两个 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 ,那么极端情况下每一次调用需求核算 M*N 次。

那么假如依照咱们 StateRouter 路由方法会是怎么一个样子呢?

首先当地址通知下来后,或许路由规矩变化时,每个 Router 会先将全量地址依照各自 Router 的路由规矩将地址进行核算并将核算成果经过 BitMap 方法存下来;如下图所示:

Dubbo 3 StateRouter:下一代微服务高效流量路由

全体存储架构

咱们还是以上述 6 个 URL 为例介绍:

Dubbo 3 StateRouter:下一代微服务高效流量路由

依照路由规矩,经过 BitMap 方法进行地址缓存,接下来路由核算时,咱们只需从 AddrCache 中取出对应的 addrPool 传递给下一个 Router 即可。

假如当时恳求是需求满意 Tag=a & az=az_2,那么咱们该怎么路由呢?

  • TagRouter 逻辑
  1. 依照流量核算出方针的 Tag,假定是 a
  2. 然后 AddrCache.get(TagRouter).get(a),取出对应的 targetAddrPool
  3. 将上一次传入的 addrPool 与 targetAddrPool 取出 resultAddrPool
  4. 将 resultAddrPool 传入 AZRouter
  • AZRouter 逻辑
  1. 依照流量核算出方针的 Tag,假定是 az_2
  2. 然后 AddrCache.get(AZRouter).get(az_2),取出对应的 targetAddrPool
  3. 将上一次传入的 addrPool 与 targetAddrPool 取出 resultAddrPool
  4. 将 resultAddrPool 为终究路由成果,传递给 LoadBalance

Dubbo 3 StateRouter:下一代微服务高效流量路由

要害源码的伪代码如下:

//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%。

更多服务办理才能与规范的探究

Dubbo 3 StateRouter:下一代微服务高效流量路由

跟着分布式服务架构的不断演进带来许多杂乱的安稳性与易用性问题,单一的监控已无法满意架构的演进。在现代微服务架构中,咱们需求一些手段来对杂乱的微服务架构进行“办理”。微服务办理便是经过全链路灰度、无损上下线、流控降级、异常流量调度、数据库办理等技术手段来削减乃至防止发布和办理大规模应用进程中遇到的安稳性问题,对微服务领域中的各个组件进行办理。服务提供者、顾客、注册中心、服务办理,构成现代微服务架构中重要的几环。

服务办理是微服务改造深化到一定阶段之后的必经之路,在这个进程中咱们不断有新的问题呈现。

  • 除了全链路灰度,服务办理还有没其他才能?
  • 服务办理才能有没一个规范的界说,服务办理才能包含哪些?
  • 多言语场景下,有无全链路的最佳实践或许规范?
  • 异构微服务怎么能够一致办理?

当咱们在探究服务办理的进程中,咱们在对接其他微服务的时分,咱们发现办理系统不同形成的困扰是巨大的,打通两套甚者是多套办理系统的本钱也是巨大的。为此咱们提出了 OpenSergo 项目。OpenSergo 要处理的是不同结构、不同言语在微服务办理上的概念碎片化、无法互通的问题。

咱们在微服务办理方面的探究不只仅在流量路由方面,近日 OpenSergo 发布了流量路由与流控降级与容错的 v1alpha1 的规范。OpenSergo 社区也在联合 Apache APISIX、Apache Dubbo、Spring Cloud Alibaba、Sentinel、CloudWego 等各个社区在进行进一步的合作,希望社区来一同评论与界说一致的服务办理规范。当时社区也在联合 bilibili、字节跳动等企业一同共建规范,也欢迎感兴趣的开发者、社区与企业一同参加到 OpenSergo 服务办理规范共建中。欢迎我们参加 OpenSergo 社区沟通群(钉钉群)进行评论:34826335。

MSE 注册装备中心专业版首购享 9 折优惠,MSE 云原生网关预付费全标准享 9 折优惠。点击此处,即享优惠~