作者:十眠

流量路由,顾名思义就是将具备某些属性特色的流量,路由到指定的指标。流量路由是流量治理中重要的一环,本节内容将会介绍流量路由常见的场景、流量路由技术的原理以及实现。

流量路由的业务场景

咱们能够基于流量路由规范来实现各种业务场景,如标签路由、金丝雀公布、同机房优先路由等。

  • 标签路由

标签路由是依照标签为维度对指标负载进行划分,符合条件的流量匹配至对应的指标,从而实现标签路由的能力。当然基于标签路由的能力,赋予标签各种含意咱们就能够实现各种流量路由的场景化能力。

  • 金丝雀公布

金丝雀公布是一种升高在生产中引入新软件版本的危险的技术,办法是在将更改推广到整个基础架构并使其可供所有人应用之前,迟缓地将更改推广到一小部分用户。金丝雀公布是一种在黑与白之间,可能平滑过渡的一种公布形式。让一部分用户持续用旧版本,一部分用户开始用新版本,如果用户对新版本没有什么拥护意见,那么逐渐扩大范围,把所有用户都迁徙到新版本下面来。始终都有据说,平安生产三板斧的概念:可灰度、可观测、可回滚。那么灰度公布能力就是帮忙企业软件做到疾速迭代验证的必备能力。在K8s中金丝雀公布的最佳实际如下:第一步:新建灰度 Deployment,部署新版本的镜像,打上新版本的标签。第二步:配置针对新版本的标签路由规定。第三步:验证胜利,扩充灰度比例。第四步:若验证胜利,将稳固版本的利用更新成最新镜像;若验证失败,把灰度的 Deployment 正本数调整到 0 或删除该 Deployment。

  • 全链路灰度

当企业的倒退,微服务的数量会逐步增多。在有肯定规模的肯定数量的微服务状况下,一次发版可能波及到的服务数量会比拟多,微服务链路也相当较长。全链路灰度能够保障特定的灰度流量能够路由到所有波及到的灰度版本中。

  • 同可用区优先路由

当企业的对稳定性的要求变高时,企业的利用会抉择部署在多个可用区中进步利用的可用性,防止某个可用区呈现问题后导致影响利用的可用性。当利用在不同的可用区部署时,利用间跨可用区调用可能会被因为远距离调用造成的网络提早影响,同可用区优先路由会让咱们的Consumer利用优先调用以后可用区内的Provider利用,能够很好地缩小这种远距离调用造成的影响,同时当某个可用区呈现问题后,咱们只需在流量入口处将以后可用区的流量隔离掉,其余可用区的流量不会拜访至以后可用区的节点,能够很好地管制某个可用区呈现问题后的影响面。

流量路由能力实现的场景泛滥,下面只是列举了一些典型的场景,上面咱们将从流量路由原理动手,分析流量路由的实现与技术细节。

流量路由原理

须要实现上述所提的流量路由的场景,那么对于Consumer利用来说,同一个 Provider 利用的不同节点之间是有一些非凡的标识。金丝雀公布场景来说,新版本代码所部署的节点须要被标上成新版本的标识;同机房优先路由来说,Provider节点要被标识上机房的信息;全链路灰度场景来说,灰度环境的节点须要被带上灰度标。因而,咱们须要在Provider服务注册的过程中,就在注册到注册核心的地址信息中带上治理场景所需的标识。

  • 节点打标

首先介绍一下节点打标的能力,咱们先看看 Apache Dubbo 的设计,其中 Dubbo 服务节点的地址信息应用 URL 模型来承载。

class URL implements Serializable {    protected String protocol;    // by default, host to registry    protected String host;    // by default, port to registry    protected int port;    protected String path;    private final Map<String, String> parameters;}

举个简略的例子,如果 Consumer 收到这样一条 dubbo://10.29.0.102:20880/GreetingService?tag=gray&az=az_1 地址信息,示意 GreetingService 服务应用的是 dubbo 协定,服务绑定的 ip 与 port 别离为  10.29.0.102 跟 20880,该地址携带上了 tag=gray、az=az_1 这样两条元数据信息,别离示意以后节点的标签为灰度,以后节点所处的可用区(az:Availability Zone 为云上的机房的可用区概念)为 az_1 。那么节点打标的能力其实就比拟明确了,咱们在服务提供者向注册核心注册服务地址之前,咱们在以后服务提供者的地址信息上减少须要减少的元数据信息比方 verion = gray,比方在 Apache Dubbo 的 URL 中减少 paramters 信息,一般来说元数据信息都是 k-v 的 map 构造,这样框架向注册核心注册该节点时会为其增加须要的标签信息verison=gray

类似的, Spring Cloud 中通过示意服务节点信息的形象

public class Server {   public static interface MetaInfo {        ...    }    private String host;    private int port = 80;    ...}

Sentinel2.0 心愿作为流量治理能力的实现,思考到会被较多框架即成,因而须要思考到各个框架的通用点以及自身设计的易用性,Sentinel2.0 中应用 Instance 模型表示服务节点信息的形象,并且在其中保留了原有类型的援用。

public class Instance {  private String host;  private Integer port;  private Map<String, String> metadata;  private Object targetInstance;}

其中 metadata 用来存储用于服务治理的元数据,比方AZ标、版本标签等等。

  • 流量路由

到目前为止,咱们算是搞明确了 Consumer 收到的 Provider 的地址列表长什么样子。假如 Consumer 收到了 如下图所示 GreetingService 服务的6条地址,那么咱们该如何进行抉择呢?

算是进入到正题,咱们看一下 Sentinel2.0 是如何实现流量路由能力的。

目前咱们在 Sentinel2.0 中别离形象了InstanceManager、RouterFilter 以及 LoadBalancer 三个对象,并通过 ClusterManager 将它们治理起来。其中 InstanceManager 将地址列表按需进行存储与治理,RouterFilter做为流量路由能力实现的主体,LoadBalancer做为负载平衡能力实现的主体。

Dubbo 在收到注册核心同步过去的 Provider URL 之后会生成对应的 Invoker ,Invoker 列表咱们能够了解为就是能够调用的 Provider 节点列表的形象。流量路由则是须要将传入的 Invoker 列表依照路由规定进行路由筛选,筛选出合乎路由规定的服务提供者,即合乎路由规定的 Invoker 列表。咱们如何能够通过 Sentinel2.0 的形象来实现流量路由的能力呢?当地址告诉下来后,咱们须要通过 instanceManager#storeInstances 将地址列表进行缓存。

@Overridepublic void notify(BitList<Invoker<T>> invokers) {    super.notify(invokers);    instanceManager.storeInstances(invokersToInstances(invokers));}

在流量路由处,咱们则调用 clusterManager#route 实现地址路由。

@Overrideprotected BitList<Invoker<T>> doRoute(BitList<Invoker<T>> invokers, URL url, Invocation invocation, boolean needToPrintMessage, Holder<RouterSnapshotNode<T>> routerSnapshotNodeHolder, Holder<String> messageHolder) throws RpcException {    TrafficContext trafficContext = getTrafficContext(invocation);    List<Instance> instances = clusterManager.route(trafficContext);    return instancesToInvokers(instances);}

其中 ClusterManager 会将路由执行的逻辑交给 RouterFiler.route 进行执行。

public List<Instance> route(TrafficContext context) {    List<Instance> instances = instanceManager.getInstances();    for (RouterFilter routerFilter : routerFilterList) {        instances = routerFilter.filter(instances, context);    }    return instances;}

每个 RouterFilter 服务路由都能够蕴含一条路由规定,路由规定决定了服务消费者的调用指标,即规定了服务消费者可调用哪些服务提供者;一次微服务调用的地址列表能够由多个 RouterFilter 服务路由独特影响,比方咱们心愿以后的 Consumer 流量拜访到在同时合乎灰度公布以及同可用区优先调用路由规定的节点上。咱们能够依照需要减少路由链中的 RouterFilter,并且路由链的 Route 办法是循环调用每个 RouterFilter 的 Route 办法。并且上一个 Router 的输入 Invoker 列表会做为下一个 Router 的输出。介绍到这里,大家可能对下图会有一个更加粗浅的了解了。

路由的整体模型大家曾经了解了,咱们来重点看一下具体的 RouterFilter 服务路由是如何实现的。

public interface RouterFilter {  List<Instance> filter(List<Instance> instanceList, TrafficContext context) throws TrafficException;}

RouterFilter 的 Route 办法会在每次申请调用时被执行,Route 办法有要害的两个入参 InstanceList 跟 TrafficContext,instanceList 是可调用的服务提供者节点列表的形象。TrafficContext 是以后调用流量的申请上下文的形象,咱们能够从中读到申请中携带着的 RouterFilter 所关怀的一些元数据(比方以后申请的AZ信息、申请参数中指定 key 的值等内容)。Route 办法会在每次调用时候依据申请中的上下文信息联合路由规定计算出以后申请须要匹配的指标节点特色,并遍历以后的地址列表,依据指标节点特色进行地址过滤。筛选出指标节点的地址列表,是输出地址列表的子集,而后传递给下一个 RouterFilter。

RouterFilter 的 Route 办法逻辑的伪代码如下:

@Overridepublic List<Instance> filter(List<Instance> instanceList, TrafficContext context) throws TrafficException {    List<Instance> targetInstances = new ArrayList<>();    for (Instance instance : instanceList) {        if (trafficRouteMatch(instance, context)) {            targetInstances.add(instance);        }    }    return targetInstances;}

instanceList 为输出地址列表,targetInstances 为输入地址列表即以后 Router 服务路由的后果。

Sentinel2.0流量路由布局

Sentinel2.0 将基于 OpenSergo 流量路由规定实现根本的流量路由能力,反对多种流量路由策略、负载平衡策略、虚构工作负载等。Sentinel2.0 冀望反对 Http、RPC、SQL等微服务各种流量的路由能力,并且能够疾速被各支流微服务框架所集成。