Dubbo实践(十六)集群容错
Dubbo作为一个分布式的服务治理框架,提供了集群部署,路由,软负载均衡及容错机制。下图描述了Dubbo调用过程中的对于集群,负载等的调用关系:

集群 Cluster
将Directory中的多个Invoker伪装成一个Invoker,对上层透明,包含集群的容错机制。
Cluster接口定义:
/**
* Cluster. (SPI, Singleton, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Computer_cluster">Cluster</a>
* <a href="http://en.wikipedia.org/wiki/Fault-tolerant_system">Fault-Tolerant</a>
*
*/
@SPI(FailoverCluster.NAME)
public interface Cluster { /**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException; }
Cluster可以看做是工厂类, 将目录directory下的invoker合并成一个统一的Invoker,根据不同集群策略的Cluster创建不同的Invoker。
我们来看下默认的失败转移,当出现失败重试其他服务的策略,这个Cluster实现很简单,就是创建FailoverCluseterInvoker对象:
/**
* {@link FailoverClusterInvoker}
*
*/
public class FailoverCluster implements Cluster { public final static String NAME = "failover"; public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
} }
下图展示了Dubbo提供的所有集群方案:

| AvailableCluster | 获取可用的调用。遍历所有Invokers判断Invoker.isAvalible,只要有一个为true直接调用返回,不管成不成功 |
| BroadcastCluster | 广播调用。遍历所有Invokers,逐个调用,每个调用catch住异常不影响其他invoker调用 |
| FailbackCluster | 失败自动恢复。对于invoker调用失败,后台记录失败请求,任务定时重发, 通常用于通知 |
| FailfastCluster | 快速失败。只发起一次调用,失败立即报错,通常用于非幂等性操作 |
| FailoverCluster |
失败转移。当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟
|
| FailsafeCluster | 失败安全。出现异常时,直接忽略,通常用于写入审计日志等操作 |
| ForkingCluster | 并行调用。只要一个成功即返回,通常用于实时性要求较高的操作,但需要浪费更多服务资源 |
| MergeableCluster | 分组聚合。按组合并返回结果,比如菜单服务,接口一样,但有多种实现,用group区分,现在消费方需从每种group中调用一次返回结果,合并结果返回,这样就可以实现聚合菜单项 |
| MockClusterWrapper |
具备调用mock功能。在包装时获取url的MOCK_KEY属性
|
上面9种集群方案除了MergeableCluster比较复杂之外,其他都比较简单直观,接下来分析一下MergeableCluster是如何实现的:
- 根据MERGE_KEY从URL获取参数值;
- 为空则不需要merge,正常调用;
- 按group分组调用,将返回接口保存到集合中;
- 获取MERGE_KEY,如果是默认的话,获取默认merge策略,主要根据返回类型判断;
- 如果不是,获取自定义的merge策略;
- Merge策略合并调用结果返回。
集群模式的配置:
<dubbo:service cluster="failsafe" /> 服务提供方
<dubbo:reference cluster="failsafe" /> 服务消费方
目录服务 Directory
集群目录服务Directory,代表多个Invoker, 可以看成List<Invoker>,它的值可能是动态变化的,比如注册中心推送变更。集群选择调用服务时通过目录服务找到所有服务。
Directory的接口定义:
/**
* Directory. (SPI, Prototype, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Directory_service">Directory Service</a>
*
* @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory)
*/
public interface Directory<T> extends Node { /**
* get service type.
*
* @return service type.
*/
Class<T> getInterface(); /**
* list invokers.
*
* @return invokers
*/
List<Invoker<T>> list(Invocation invocation) throws RpcException; }
Directory有两个具体实现:

StaticDirectory:静态目录服务,它的所有Invoker通过构造函数传入, 服务消费方引用服务的时候, 服务对多注册中心的引用,将Invokers集合直接传入 StaticDirectory构造器,再由Cluster伪装成一个Invoker。
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// use AvailableCluster only when register's cluster is available
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { // not a registry url
invoker = cluster.join(new StaticDirectory(invokers));
}
StaticDirectory的list方法直接返回所有invoker集合。
RegistryDirectory:注册目录服务,它的Invoker集合是从注册中心获取的,它实现了NotifyListener接口实现了回调接口notify(List<Url>)。
比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有服务提供方,服务提供方数据有变动时回调消费方的NotifyListener服务的notify方法。NotifyListener.notify(List<Url>) 回调接口传入所有服务的提供方的url地址然后将urls转化为invokers, 也就是refer应用远程服务。
keys.add(key);
// Cache key is url that does not merge with consumer side parameters, regardless of how the consumer combines parameters, if the server url changes, then refer again
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) { // Not in the cache, refer again
try {
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = !url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) { // 这里
invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
到此时RegistryDirectory中有对这个远程服务调用的所有invokers。RegistryDirectory.list(invocation)就是根据服务调用方法获取所有的远程服务引用的invoker执行对象。
路由服务 Router
Router服务路由,根据路由规则从多个Invoker中选出一个子集。AbstractDirectory是所有目录服务实现的上层抽象,它在list出所有invokers后,会通过Router服务进行路由过滤。
Router接口定义:
/**
* Router. (SPI, Prototype, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Routing">Routing</a>
*
* @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory)
* @see com.alibaba.dubbo.rpc.cluster.Directory#list(Invocation)
*/
public interface Router extends Comparable<Router> { /**
* get the router url.
*
* @return url
*/
URL getUrl(); /**
* route.
*
* @param invokers
* @param url refer url
* @param invocation
* @return routed invokers
* @throws RpcException
*/
<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; }

这里如果要了解具体的路由规则,需要查看阿里的原始文档,在该文档的路由规则部分有详细介绍。
我们来分析下,路由规则在哪里用到
在RegistryDirectory类中:
private List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {
Invocation invocation = new RpcInvocation(method, new Class<?>[0], new Object[0]);
List<Router> routers = getRouters();
if (routers != null) {
for (Router router : routers) {
if (router.getUrl() != null) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
}
}
return invokers;
}
而route函数又是在如下位置被用到: toMethodInvokers(Map<String, Invoker<T>> invokersMap) ,最终调用是在refreshInvoker函数,而refreshInvoker又是在notify函数中调用的。
负载均衡 LoadBalance
LoadBalance负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择。
LoadBalance接口定义:
/**
* LoadBalance. (SPI, Singleton, ThreadSafe)
* <p>
* <a href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load-Balancing</a>
*
* @see com.alibaba.dubbo.rpc.cluster.Cluster#join(Directory)
*/
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance { /**
* select one invoker in list.
*
* @param invokers invokers.
* @param url refer url
* @param invocation invocation.
* @return selected invoker.
*/
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException; }
类注解@SPI说明可以基于Dubbo的扩展机制进行自定义的负责均衡算法实现,默认是随机算法。方法注解@Adaptive说明能够生成适配方法。
select方法设配类通过url的参数选择具体的算法,再从invokers集合中根据具体的算法选择一个invoker。

接下来介绍以上4种负载策略。
RandomLoadBalance
RandomLoadBalance:随机访问策略,按权重设置随机概率,是默认策略:
1. 获取所有invokers的个数;
2. 遍历所有Invokers, 获取计算每个invokers的权重,并把权重累计加起来 每相邻的两个invoker比较他们的权重是否一样,有一个不一样说明权重不均等;
3. 总权重大于零且权重不均等的情况下,按总权重获取随机数 offset = random.netx(totalWeight) ,遍历invokers确定随机数offset落在哪个片段(invoker上);
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offset = random.nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
4. 权重相同或者总权重为0, 根据invokers个数均等选择 invokers.get(random.nextInt(length))
RoundRobinLoadBalance
RoundRobinLoadBalance:轮询,按公约后的权重设置轮询比率。
1. 获取轮询key:服务名+方法名,获取可供调用的invokers个数length,设置最大权重的默认值maxWeight=0,设置最小权重的默认值minWeight=Integer.MAX_VALUE;
2. 遍历所有Inokers,比较得出maxWeight和minWeight;
3. 如果权重是不一样的,根据key获取自增序列,自增序列加一并与最大权重取模得到currentWeigth,遍历所有invokers筛选出大于currentWeight的invokers,设置可供调用的invokers的个数length;
4. 自增序列加一并与length取模,从invokers获取invoker。
LeastActiveLoadBalance
LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机选择。
活跃数是指调用前后的计数差, 使慢的提供者收到更少的请求,因为越慢的提供者前后的计数差越大。
活跃计数的功能在消费者的ActiveLimitFilter中设置的:
long begin = System.currentTimeMillis();
RpcStatus.beginCount(url, methodName);
try {
Result result = invoker.invoke(invocation);
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
return result;
} catch (RuntimeException t) {
RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
throw t;
}
最少活跃的选择过程如下:
1. 获取可调用invoker的总个数,初始化最小活跃数,相同最小活跃的个数,相同最小活跃数的下标数组等等;
2. 遍历所有invokers,获取每个invoker的活跃数active和权重,找出最小权重的invoker,如果有相同最小权重的inovkers,将下标记录到数组leastIndexs[]数组中,累计所有的权重到totalWeight变量;
3. 如果invokers的权重不相等且totalWeight大于0,按总权重随机 offsetWeight = random.nextInt(totalWeight) 计算随机值在哪个片段上并返回invoker
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
int offsetWeight = random.nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexs[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight <= 0)
return invokers.get(leastIndex);
}
4. 如果invokers的权重相等或者totalWeight等于0,均等随机
这里用到了RpcStatus,由RpcStatus的类描述可知,该项内容是在filter中进行记录。
/**
* URL statistics. (API, Cached, ThreadSafe)
*
* @see com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
* @see com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
* @see com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
*/
public class RpcStatus {}
ConsistentHashLoadBalance
ConsistentHashLoadBalance:一致性hash,相同参数的请求总是发到同一个提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。对于一致性哈希算法介绍网上很多,这个给出一致性hash算法 - consistent hashing供参考,读者请自行阅读ConsistentashLoadBalance中对一致性哈希算法的实现,还是比较通俗易懂的这里不再介绍。
配置规则Configurator
配置规则实际上是在生成invoker的过程中对url进行改写
Configurator接口定义:
/**
* Configurator. (SPI, Prototype, ThreadSafe)
*
*/
public interface Configurator extends Comparable<Configurator> { /**
* get the configurator url.
*
* @return configurator url.
*/
URL getUrl(); /**
* Configure the provider url.
* O
*
* @param url - old rovider url.
* @return new provider url.
*/
URL configure(URL url); }

目前包含两种规则:AbsentConfigurator和OverrideConfigurator,前者是如果缺少项,则新增,而后者是直接覆盖。具体是在RegistryDirectory中的mergeUrl函数中用到。
另外,根据dubbo文档描述,向注册中心写入动态配置覆盖规则通常由监控中心或治理中心的页面完成。
Dubbo实践(十六)集群容错的更多相关文章
- Dubbo 源码分析 - 集群容错之 LoadBalance
1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通 ...
- Dubbo 源码分析 - 集群容错之 Router
1. 简介 上一篇文章分析了集群容错的第一部分 -- 服务目录 Directory.服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由.上一篇文章关于服务路由相关逻辑没有 ...
- Dubbo 源码分析 - 集群容错之 Cluster
1.简介 为了避免单点故障,现在的应用至少会部署在两台服务器上.对于一些负载比较高的服务,会部署更多台服务器.这样,同一环境下的服务提供者数量会大于1.对于服务消费者来说,同一环境下出现了多个服务提供 ...
- dubbo服务引用与集群容错
服务引用无非就是做了两件事 将spring的schemas标签信息转换bean,然后通过这个bean的信息,连接.订阅zookeeper节点信息创建一个invoker 将invoker的信息创建一个动 ...
- Dubbo工作原理,集群容错,负载均衡
Remoting:网络通信框架,实现了sync-over-async和request-response消息机制. RPC:一个远程过程调用的抽象,支持负载均衡.容灾和集群功能. Registry:服务 ...
- Dubbo 源码分析 - 集群容错之 Directory
1. 简介 前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码.这部分源码包含四个部分,分别是服务目录 Directory.服务路由 Router.集群 ...
- Dubbo负载均衡与集群容错机制
1 Dubbo简介 Dubbo是一款高性能.轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 作为一个轻量级RPC框架,D ...
- dubbo源码分析- 集群容错之Cluster(一)
1.集群容错的配置项 failover - 失败自动切换,当出现失败,重试其他服务器(缺省),通常用于读操作,但重试会带来更长的延时. failfast - 快速失效,只发起一次调用,失败立即报错.通 ...
- Dubbo的10种集群容错模式
学习Dubbo源码的过程中,首先看到的是dubbo的集群容错模式,以下简单介绍10种集群容错模式 1.AvailableCluster 顾名思义,就是可用性优先,遍历所有的invokers,选择可用的 ...
- 二十六. 集群及LVS简介 LVS-NAT集群 LVS-DR集群
方案:安装ipvsadm软件包,关于ipvsadm的用法可以参考man ipvsadm资料. 常用ipvsadm命令语法格式如表-1及表-2所示. 1.ipvsadm命令用法(proxy) 1.1 创 ...
随机推荐
- UGUI——重写Image类实现进度条
目的: 游戏中经常会用到进度条,但是美术给的图片用filled一拉伸就很难看,如下图 第一种模式是九宫格模式,第二种是filled.而我们需要的是两种可结合的. 如何实现: 新建一个类,继承image ...
- Java 学习笔记(1)——java基础语法
最近抽时间在学习Java,目前有了一点心得,在此记录下来. 由于我自己之前学过C/C++,而Java的语法与C/C++基本类似,所以这一系列文章我并不想从基础一点点的写,我想根据我已有的C/C++经验 ...
- 洛谷 P2469 [SDOI2010]星际竞速 解题报告
题目描述 10年一度的银河系赛车大赛又要开始了.作为全银河最盛大的活动之一,夺得这个项目的冠军无疑是很多人的梦想,来自杰森座α星的悠悠也是其中之一. 赛车大赛的赛场由N颗行星和M条双向星际航路构成,其 ...
- cf1056B. Divide Candies(数论 剩余系)
题意 题目链接 求满足\(i^2 + j^2 \% M = 0\)的数对\((i, j)\)的个数,\(1 \leqslant i, j \leqslant 10^9, M \leqslant 100 ...
- 洛谷P3763 [TJOI2017]DNA(后缀数组 RMQ)
题意 题目链接 Sol 这题打死我也不会想到后缀数组的,应该会全程想AC自动机之类的吧 但知道这题能用后缀数组做之后应该就不是那么难了 首先把\(S\)和\(S0\)拼到一起跑,求出Height数组 ...
- 前端动画小记---bilibili ( ゜-゜)つロ客户下载小动画
逛哔哩哔哩 ( ゜-゜)つロPC版的时候看到一个蛮有意思的动画,指导用户去下载客户端,于是摸索实现了一个. 原动画效果 可以看到,一个静止的小电视人,当鼠标移动到电视人身上时,电视人慢慢变身成为一个小 ...
- drupal的权限设置
通过hook_menu()设置url的权限,有两种方式: 方式一:定义函数,通过 access callback 'access callback' => 'fun()', function f ...
- c# copy类中值到另外一个对象中
贴图: 调用:
- Node服务端极速搭建 -- nvmhome
> 本文意在让你掌握极速搭建Node服务端(任何Project) ```$ whoaminame: kelvinemail: kelvv@outlook.comhomepage: www.kel ...
- jekyll开发静态网站
一.Ruby环境安装配置 首先下载ruby安装ruby download ,安装完ruby后,再安装rubyGems:运行gem update --system即可. 然后下载DevKit-mingw ...