作者:十眠
流量路由,望文生义便是将具有某些特点特征的流量,路由到指定的方针。流量路由是流量办理中重要的一环,本节内容将会介绍流量路由常见的场景、流量路由技能的原理以及完成。
流量路由的业务场景
咱们能够根据流量路由标准来完成各种业务场景,如标签路由、金丝雀发布、同机房优先路由等。
- 标签路由
标签路由是依照标签为维度对方针负载进行区分,契合条件的流量匹配至对应的方针,然后完成标签路由的才能。当然根据标签路由的才能,赋予标签各种含义咱们就能够完成各种流量路由的场景化才能。
- 金丝雀发布
金丝雀发布是一种降低在出产中引入新软件版别的风险的技能,办法是在将更改推广到整个基础架构并使其可供一切人运用之前,缓慢地将更改推广到一小部分用户。金丝雀发布是一种在黑与白之间,能够滑润过渡的一种发布方式。让一部分用户继续用旧版别,一部分用户开端用新版别,如果用户对新版别没有什么对立定见,那么逐步扩展范围,把一切用户都迁移到新版别上面来。一直都有听说,安全出产三板斧的概念:可灰度、可观测、可回滚。那么灰度发布才能便是协助企业软件做到快速迭代验证的必备才能。在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 将地址列表进行缓存。
@Override
public void notify(BitList<Invoker<T>> invokers) {
super.notify(invokers);
instanceManager.storeInstances(invokersToInstances(invokers));
}
在流量路由处,咱们则调用 clusterManager#route 完成地址路由。
@Override
protected 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 办法逻辑的伪代码如下:
@Override
public 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等微服务各种流量的路由才能,而且能够快速被各干流微服务结构所集成。