为什么要采用路由?
真实的环境中一般是以集群的方式提供服务,但对于服务调用方来说,一个接口会有多个服务提供方同时提供服务,所以 RPC 在每次发起请求的时候,都需要从多个服务节点里面选取一个用于处理请求的服务节点。
每次上线应用的时候都不止运行一台服务器实例,上线就会涉及到变更,只要变更就可能导致原本正常运行的节点出现异常,尤其是发生重大变动或升级的时候,导致应用服务的不稳定因素就很多。
为减少这种风险,一般会选择灰度方式发布应用实例,比如可以先发布部分实例观察是否存在异常,后续再根据使用的情况,选择发布更多实例或是回滚已经上线的实例。
但这种方式也会存在缺陷,线上一旦出现问题,还是会有一定影响。对于我们的服务提供方来说,服务会同时提供给很多的调用方来调用,尤其像一些基础性的服务,调用会更加频繁复杂,比如商品、价格等等,一旦线上实例有问题,将会导致所有的调用方业务受到影响。
如何减少上线变更导致的风险?这就需要在RPC应用中增加路由功能。
如何实现路由?
基于上面所讲述的问题,是不是可以在上线完成之后,先让一小部分的调用请求进行逻辑验证,待没问题后再接入其他的服务节点,从而实现流量隔离的效果。那么在 RPC 框架里面该具体如何实现呢?
1)服务注册发现方式:
如果通过服务发现的方式来隔离调用方的请求,从逻辑上看是可行,但注册中心在 RPC 里面的作用是用来存储数据并保证数据的一致性。如果把这种复杂的计算逻辑放到注册中心内,当集群节点变多之后,就会导致注册中心压力很大,而且大部分情况下我们是采用开源软件来搭建注册中心,还需要进行二次开发。所以从实际的角度出发,通过服务发现方式来实现请求隔离并不理想。
2)RPC路由策略:
在 RPC 发起请求的时候,有一个步骤就是从服务提供方节点集合里面选择一个合适的节点(负载均衡),那么就可以在选择节点前再加上一个“筛选逻辑”,把符合我们要求的节点筛选出来。这个就是路由策略:
接收请求-->请求校验-->路由策略-->负载均衡-->
常见的有IP 路由策略,用于限制哪些可以调用服务方的客户端 IP。使用了 IP 路由策略后,整个集群的调用拓扑如下图所示:
有了 IP 路由之后,上线过程中就可以只让部分请求调用到新上线的实例,相对传统的灰度发布方式来讲,这样做我们可以把试错成本降到最低。
但有些场景下,可能还需要更细粒度的路由方式,比如说根据SESSIONID要落到相同的服务节点上以保持会话的有效性,某个商品的请求都采用同一个服务节点处理,那么IP路由的方式就不能满足我们的要求, 这个时候就可以采用参数化路由:
相比 IP 策略路由,参数路由支持的灰度粒度更小,这也是一个服务治理的手段。灰度发布功能是 RPC 路由功能的一个典型应用场景,通过 RPC 路由策略的组合使用可以让服务提供更加灵活地管理、合理分配流量,进一步降低上线可能导致的风险。
RPC框架中的负载均衡
RPC 的负载均衡是由 RPC 框架自身提供实现,RPC 的服务调用方会与“注册中心”下发的所有服务节点建立长连接,在每次发起 RPC 调用时,服务调用方都会通过配置的负载均衡插件,自主选择一个最佳的服务节点,发起 RPC 调用请求。
RPC 负载均衡策略一般包括轮询、随机、权重、最少连接等。其中随机,轮询策略应该属最常用的策略,Dubbo默认就是使用随机负载均衡策略,这些策略基本可以保证每个节点接收到的请求流量是平均的;同时我们还可以通过控制节点权重的方式,来进行流量控制。比如默认每个节点的权重都设为 100,但我们把其中的一个节点的权重更改为50 时,它接收到的流量就是其他节点的 一半。
自适应的负载均衡策略
RPC 的负载均衡完全由 RPC 框架自身实现,服务调用方发起请求时,会通过所配置的负载均衡组件,自主地选择合适服务节点。调用方如果能知道每个服务节点处理请求的能力,再根据服务节点处理请求的能力来判断分配相应的流量,集群资源就能够得到充分的利用, 当一个服务节点负载过高或响应过慢时,就少给它发送请求,反之则多给它发送请求。这个就是自适应的负载均衡策略。
具体如何实现?
这就需要判定服务节点的处理能力, 服务调用方收集与之建立长连接的每个服务节点的指标数据,比如服务节点的负载指标、CPU 核数、内存大小、请求处理的耗时指标、服务节点的状态指标(CPU/内存占用率)。通过这些指标,计算出一个分数,比如总分 100 分,如果 CPU 负载达到 80%,就减它 10分,内存负载达到80%,再减10分,以此类比,需要减多少分是需要一个合理依据的计算策略。
主要步骤:
(1)添加计分器和指标采集器,将其作为自适应负载均衡算法的组件,用于采集记录服务节点的状态, 并且评定相应的分数。
(2)指标采集器收集服务节点 CPU 核数、CPU 负载以及内存占用率等指标,还包括请求耗时等数据,通过服务调用方与服务提供方的心跳数据来获取。
(3)可以配置开启哪些指标采集器,并设置这些参考指标的具体权重,再根据指标的实际数据和权重来综合打分。
(4)通过对服务节点的综合打分,最终计算出服务节点的实际权重,之后服务调用方会根据相应的路由策略,来选择合适的服务节点。
Dubbo路由负载源码剖析:
调用路径:
AbstractClusterInvoker.invoke() -> AbstractClusterInvokerd.doInvoke() ->AbstractInvoker.invoke()
源码剖析:
@Override public Result invoke(final Invocation invocation) throws RpcException { checkWhetherDestroyed(); // 给调用方绑定传递的附加信息 Map<String, String> contextAttachments = RpcContext.getContext().getAttachments(); if (contextAttachments != null && contextAttachments.size() != 0) { ((RpcInvocation) invocation).addAttachments(contextAttachments); } List<Invoker<T>> invokers = list(invocation); // 初始化负载均衡策略 LoadBalance loadbalance = initLoadBalance(invokers, invocation); RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); return doInvoke(invocation, invokers, loadbalance); }
最后调用的doInvoke是一个抽象方法,该方法主要是负责Dubbo集群调用容错不同策略的具体实现:
protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException;
Dubbo默认采用的是FailoverClusterInvoker策略。
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { ... // 选择具体的负载策略,Dubbo支持轮询、随机、权重、最少连接策略,默认采用的是随机策略 Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked); ... // 根据负载策略获取具体的执行节点, 处理调用请求 Result result = invoker.invoke(invocation); ... }
通过上述源码剖析, 我们就可以在Dubbo中实现AbstractLoadBalance的doSelect接口,自定义扩展实现自适应权重负载均衡策略,充分发挥RPC服务集群的资源,提升整体性能。
- FailoverClusterInvoker.doInvoke方法:
- AbstractClusterInvoker.invoke方法:
RPC实现原理之核心技术-路由与负载均衡
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。
下一篇:常见加密算法的Python实现
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
图书《数据资产管理核心技术与应用》分享
《数据资产管理核心技术与应用》-清华大学出版社-张永清等著
数据 获取数据 数据处理 数据血缘 元数据