下面我们来分析一下Dubbo的集群容错机制。我们先来看一下各个节点之间的关系
这里的Invoker是Provider的一个可调用Service的抽象,Invoker封装了Provider地址及Service接口信息。
Directory代表多个Invoker,可以把它看成List
注册中心推送变更。
Cluster将Directory中的多个Invoker伪装成一个Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败
后,重试另一个。
Router负责从多个Invoker中按路由规则选出子集,比如读写分离,应用隔离等。
LoadBalance负责从多个Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败
后,需要重选。
再来说一下Dubbo的集群的容错模式
Failover Cluster
- 失败自动切换,当出现失败,重试其它服务器。(缺省)
- 通常用于读操作,但重试会带来更长延迟。
- 可通过retries="2"来设置重试次数(不含第一次)。
Failfast Cluster
- 快速失败,只发起一次调用,失败立即报错。
- 通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
- 失败安全,出现异常时,直接忽略。
- 通常用于写入审计日志等操作。
Failback Cluster
- 失败自动恢复,后台记录失败请求,定时重发。
- 通常用于消息通知操作。
Forking Cluster
- 并行调用多个服务器,只要一个成功即返回。
- 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
- 可通过forks="2"来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
-
通常用于通知所有提供者更新缓存或日志等本地资源信息。
重试次数配置如:(failover集群模式生效)
或:
或:
集群模式配置如:
或:
那么Dubbo的集群容错是怎么实现的呢,现在我们来看一下。
先看一下Cluster接口
/**
* Merge the directory invokers to a virtual invoker.
*
* 基于 Directory ,创建 Invoker 对象,实现统一、透明的 Invoker 调用过程
*
* @param directory Directory 对象
* @param 泛型
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
Invoker join(Directory directory) throws RpcException;
下面我们来介绍几个Cluster的实现类
AvailableCluster,失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
public Invoker join(Directory directory) throws RpcException {
return new AvailableClusterInvoker(directory);
}
看一下AvailableClusterInvoker这个类
public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
// 循环候选的 Invoker 集合,调用首个可用的 Invoker 对象。
for (Invoker invoker : invokers) {
if (invoker.isAvailable()) { // 可用
// 发起 RPC 调用
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
BroadcastCluster,广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
public Invoker join(Directory directory) throws RpcException {
return new BroadcastClusterInvoker(directory);
}
看一下BroadcastClusterInvoker的实现
public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
// 检查 invokers 即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(invokers, invocation);
// 设置已经调用的 Invoker 集合,到 Context 中
RpcContext.getContext().setInvokers((List) invokers);
// 保存最后一次调用的异常
RpcException exception = null;
// 保存最后一次调用的结果
Result result = null;
// 循环候选的 Invoker 集合,调用所有 Invoker 对象。
for (Invoker invoker : invokers) {
try {
// 发起 RPC 调用
result = invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e); // 封装成 RpcException 异常
logger.warn(e.getMessage(), e);
}
}
// 若存在一个异常,抛出该异常
if (exception != null) {
throw exception;
}
return result;
}
FailbackCluster,失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
public Invoker join(Directory directory) throws RpcException {
return new FailbackClusterInvoker(directory);
}
看一下FailbackClusterInvoker的实现
protected Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检查 invokers 即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(invokers, invocation);
// 根据负载均衡机制从 invokers 中选择一个Invoker
Invoker invoker = select(loadbalance, invocation, invokers, null);
// RPC 调用得到 Result
return invoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e);
// 添加到失败任务
addFailed(invocation, this);
return new RpcResult(); // ignore
}
}
FailfastCluster,快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
public Invoker join(Directory directory) throws RpcException {
return new FailfastClusterInvoker(directory);
}
看一下FailfastClusterInvoker的实现
public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
// 检查 invokers 即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(invokers, invocation);
// 根据负载均衡机制从 invokers 中选择一个Invoker
Invoker invoker = select(loadbalance, invocation, invokers, null);
try {
// RPC 调用得到 Result
return invoker.invoke(invocation);
} catch (Throwable e) {
// 若是业务性质的异常,直接抛出
if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
throw (RpcException) e;
}
// 封装 RpcException 异常,并抛出
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0,
"Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
}
FailoverCluster,败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
public Invoker join(Directory directory) throws RpcException {
return new FailoverClusterInvoker(directory);
}
看下FailoverClusterInvoker的实现
public Result doInvoke(Invocation invocation, final List> invokers, LoadBalance loadbalance) throws RpcException {
List> copyinvokers = invokers;
// 检查copyinvokers即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(copyinvokers, invocation);
// 得到最大可调用次数:最大可重试次数+1,默认最大可重试次数Constants.DEFAULT_RETRIES=2
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// 保存最后一次调用的异常
// retry loop.
RpcException le = null; // last exception.
// 保存已经调用过的Invoker
List> invoked = new ArrayList>(copyinvokers.size()); // invoked invokers.
Set providers = new HashSet(len);
// failover机制核心实现:如果出现调用失败,那么重试其他服务器
for (int i = 0; i < len; i++) {
// Reselect before retry to avoid a change of candidate `invokers`.
// NOTE: if `invokers` changed, then `invoked` also lose accuracy.
// 重试时,进行重新选择,避免重试时invoker列表已发生变化.
// 注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
if (i > 0) {
checkWhetherDestroyed();
// 根据Invocation调用信息从Directory中获取所有可用Invoker
copyinvokers = list(invocation);
// check again
// 重新检查一下
checkInvokers(copyinvokers, invocation);
}
// 根据负载均衡机制从copyinvokers中选择一个Invoker
Invoker invoker = select(loadbalance, invocation, copyinvokers, invoked);
// 保存每次调用的Invoker
invoked.add(invoker);
// 设置已经调用的 Invoker 集合,到 Context 中
RpcContext.getContext().setInvokers((List) invoked);
try {
// RPC 调用得到 Result
Result result = invoker.invoke(invocation);
// 重试过程中,将最后一次调用的异常信息以 warn 级别日志输出
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
// 如果是业务性质的异常,不再重试,直接抛出
if (e.isBiz()) { // biz exception.
throw e;
}
// 其他性质的异常统一封装成RpcException
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
// 最大可调用次数用完还得到Result的话,抛出RpcException异常:重试了N次还是失败,并输出最后一次异常信息
throw new RpcException(le.getCode(), "Failed to invoke the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers "
+ providers + " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion()
+ ". Last error is: " + le.getMessage(), le.getCause() != null ? le.getCause() : le);
}
FailsafeCluster,失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
public Invoker join(Directory directory) throws RpcException {
return new FailsafeClusterInvoker(directory);
}
看一下FailsafeClusterInvoker的实现
public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检查 invokers 即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(invokers, invocation);
// 根据负载均衡机制从 invokers 中选择一个Invoker
Invoker invoker = select(loadbalance, invocation, invokers, null);
// RPC 调用得到 Result
return invoker.invoke(invocation);
} catch (Throwable e) {
// 打印异常日志
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
// 忽略异常
return new RpcResult(); // ignore
}
}
ForkingCluster,并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
public Invoker join(Directory directory) throws RpcException {
return new ForkingClusterInvoker(directory);
}
看一下ForkingClusterInvoker的实现
public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
// 检查 invokers 即可用Invoker集合是否为空,如果为空,那么抛出异常
checkInvokers(invokers, invocation);
// 保存选择的 Invoker 集合
final List> selected;
// 得到最大并行数,默认为 Constants.DEFAULT_FORKS = 2
final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);
// 获得调用超时时间,默认为 DEFAULT_TIMEOUT = 1000 毫秒
final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 若最大并行书小于等于 0,或者大于 invokers 的数量,直接使用 invokers
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
// 循环,根据负载均衡机制从 invokers,中选择一个个Invoker ,从而组成 Invoker 集合。
// 注意,因为增加了排重逻辑,所以不能保证获得的 Invoker 集合的大小,小于最大并行数
selected = new ArrayList>();
for (int i = 0; i < forks; i++) {
// 在invoker列表(排除selected)后,如果没有选够,则存在重复循环问题.见select实现.
Invoker invoker = select(loadbalance, invocation, invokers, selected);
if (!selected.contains(invoker)) { //Avoid add the same invoker several times. //防止重复添加invoker
selected.add(invoker);
}
}
}
// 设置已经调用的 Invoker 集合,到 Context 中
RpcContext.getContext().setInvokers((List) selected);
// 异常计数器
final AtomicInteger count = new AtomicInteger();
// 创建阻塞队列
final BlockingQueue
MergeableCluster,分组聚合 Cluster 实现类
public Invoker join(Directory directory) throws RpcException {
return new MergeableClusterInvoker(directory);
}
看一下MergeableClusterInvoker的实现
public Result invoke(final Invocation invocation) throws RpcException {
// 获得 Invoker 集合
List> invokers = directory.list(invocation);
// 获得 Merger 拓展名
String merger = getUrl().getMethodParameter(invocation.getMethodName(), Constants.MERGER_KEY);
// 若果未配置拓展,直接调用首个可用的 Invoker 对象
if (ConfigUtils.isEmpty(merger)) { // If a method doesn't have a merger, only invoke one Group
for (final Invoker invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
return invokers.iterator().next().invoke(invocation);
}
// 通过反射,获得返回类型
Class> returnType;
try {
returnType = getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()).getReturnType();
} catch (NoSuchMethodException e) {
returnType = null;
}
// 提交线程池,并行执行,发起 RPC 调用,并添加到 results 中
Map> results = new HashMap>();
for (final Invoker invoker : invokers) {
Future future = executor.submit(new Callable() {
public Result call() {
// RPC 调用
return invoker.invoke(new RpcInvocation(invocation, invoker));
}
});
results.put(invoker.getUrl().getServiceKey(), future);
}
// 阻塞等待执行执行结果,并添加到 resultList 中
List resultList = new ArrayList(results.size());
int timeout = getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
for (Map.Entry> entry : results.entrySet()) {
Future future = entry.getValue();
try {
Result r = future.get(timeout, TimeUnit.MILLISECONDS);
if (r.hasException()) { // 异常 Result ,打印错误日志,忽略
log.error(new StringBuilder(32).append("Invoke ").append(getGroupDescFromServiceKey(entry.getKey())).append(" failed: ").append(r.getException().getMessage()).toString(), r.getException());
} else { // 正常 Result ,添加到 resultList 中
resultList.add(r);
}
} catch (Exception e) { // 异常,抛出 RpcException 异常
throw new RpcException(new StringBuilder(32).append("Failed to invoke service ").append(entry.getKey()).append(": ").append(e.getMessage()).toString(), e);
}
}
// 结果大小为空,返回空的 RpcResult
if (resultList.isEmpty()) {
return new RpcResult((Object) null);
// 结果大小为 1 ,返回首个 RpcResult
} else if (resultList.size() == 1) {
return resultList.iterator().next();
}
// 返回类型为 void ,返回空的 RpcResult
if (returnType == void.class) {
return new RpcResult((Object) null);
}
Object result;
// 【第 1 种】基于合并方法
if (merger.startsWith(".")) {
// 获得合并方法 Method
merger = merger.substring(1);
Method method;
try {
method = returnType.getMethod(merger, returnType);
} catch (NoSuchMethodException e) {
throw new RpcException(new StringBuilder(32).append("Can not merge result because missing method [ ").append(merger).append(" ] in class [ ").append(returnType.getClass().getName()).append(" ]").toString());
}
// 有 Method ,进行合并
if (method != null) {
if (!Modifier.isPublic(method.getModifiers())) {
method.setAccessible(true);
}
result = resultList.remove(0).getValue();
try {
// 方法返回类型匹配,合并时,修改 result
if (method.getReturnType() != void.class && method.getReturnType().isAssignableFrom(result.getClass())) {
for (Result r : resultList) {
result = method.invoke(result, r.getValue());
}
// 方法返回类型不匹配,合并时,不修改 result
} else {
for (Result r : resultList) {
method.invoke(result, r.getValue());
}
}
} catch (Exception e) {
throw new RpcException(new StringBuilder(32).append("Can not merge result: ").append(e.getMessage()).toString(), e);
}
// 无 Method ,抛出 RpcException 异常
} else {
throw new RpcException(new StringBuilder(32).append("Can not merge result because missing method [ ").append(merger).append(" ] in class [ ").append(returnType.getClass().getName()).append(" ]").toString());
}
// 【第 2 种】基于 Merger
} else {
Merger resultMerger;
// 【第 2.1 种】根据返回值类型自动匹配 Merger
if (ConfigUtils.isDefault(merger)) {
resultMerger = MergerFactory.getMerger(returnType);
// 【第 2.2 种】指定 Merger
} else {
resultMerger = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(merger);
}
// 有 Merger ,进行合并
if (resultMerger != null) {
List rets = new ArrayList(resultList.size());
for (Result r : resultList) {
rets.add(r.getValue());
}
result = resultMerger.merge(rets.toArray((Object[]) Array.newInstance(returnType, 0)));
// 无 Merger ,抛出 RpcException 异常
} else {
throw new RpcException("There is no merger to merge result.");
}
}
// 返回 RpcResult 结果
return new RpcResult(result);
}
Dubbo的集群容错就介绍到这里了。