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 创 ...
随机推荐
- [Telegram X]旧版分享 突破被锁群组
Telegram X的锁群是由于 App Store 审核时发现Telegram官方并不限制18+.社会舆论等的讨论:在 版本 5.0.2 (版本号825487096)时就已经封禁该类群组 注:可能由 ...
- 最短路问题(dijkstral 算法)(优化待续)
迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法.是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题.迪杰斯特拉算法主要特点是以起始点为中心向 ...
- sql语句中as的意思是什么?
as 一般用在两个地方,一个是query的时候,用来重新指定返回的column(列) 名字如:一个table 有个column叫 id, 我们的query是select id from table1. ...
- php中怎么导入自己写的类
如果写的类是写在当前php文件内,就直接实例化若你的类写在其他的php文件里,就要先用include或require,将类文件引入<?php include("class.php&qu ...
- 使用iview时,页面没了滚动条
场景:页面中有一个确认按钮,保存后弹框预览在点保存按钮,实现数据提交.提交后回到数据列表页,用this.$router.push('list'),返回后页面无法滚动了. 原因:排查后发现弹框时在bod ...
- C++学习笔记(4)----模板实参推断
1. 如图所示代码,模板函数 compare(const T&, const T&) 要求两个参数类型要一样. compare("bye","dad&qu ...
- Maximum Depth of Binary Tree 二叉树的深度
Given a binary tree,find its maximum depth. The maximum depth is the number of nodes along the longe ...
- windows7x64系统中配置mysql5.7.17为本地开发环境(win2008类似)
1. 下载mysql压缩包mysql-5.7.17-winx64.ziphttps://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.17-winx64.z ...
- 封装网络请求并在wxml调用
https://blog.csdn.net/qq_35713752/article/details/78109084 // url:网络请求的url method:网络请求方式 data:请求参数 m ...
- PHPStorm/webstorm/PyCharm tips
phpstorm对于使用PHP开发web的人员来说,是一个非常不错的编辑开发IDE,以前用过sublime,但是相比于storm,sublime在浏览legacy代码,类代码编辑方面明显要逊色不少.同 ...