在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
引用dubbo官方的一个图
从consumer的调用开始追踪源码
由于consumer端的注入是一个动态代理对象,所以会进入InvokerInvocationHandler#invoke方法
关注
return invoker.invoke(new RpcInvocation(method, args)).recreate();
这里的invoker是一个MockClusterInvoker 进入他的invoke方法
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")){
//no mock
result = this.invoker.invoke(invocation);
。。。。。。
这里面Invocation中封装了调用的信息
invoker对象=failoverClusterInvoker
failoverClusterInvoker的invoker方法在abstractClusterInvoker中
public Result invoke(final Invocation invocation) throws RpcException {
checkWheatherDestoried();
LoadBalance loadbalance;
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && invokers.size() > 0) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}
List<Invoker<T>> invokers = list(invocation);这句话进入
。。。。
List<Invoker<T>> invokers = directory.list(invocation);
。。。
这里的directory要重点说明下,从从第一张图上
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed){
throw new RpcException("Directory already destroyed .url: "+ getUrl());
}
List<Invoker<T>> invokers = doList(invocation);
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && localRouters.size() > 0) {
for (Router router: localRouters){
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, true)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}
Directory接口有两个实现StaticDirectory和RegistryDirectory
前者和静态注册有关系,我们重点关注RegistryDirectory
RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener
RegistryDirectory实现了NotifyListener接口,我们就可以知道RegistryDirectory会在zookeeper的服务发生变化时进行通知
methodInvokerMap这个成员变量中封装了所有的服务提供者
RegistryDirectory#notify中有一行代码
refreshInvoker(invokerUrls);这部分代码之前讲过,会根据提供者得变化更新提供者
-------------------------
回到list方法List<Router> localRouters = this.routers;
是路由相关的
Router是一个路由组件
这个routers中装的是一个MockInvokersSelector 实现了Router接口
MockInvokersSelector中的
return getNormalInvokers(invokers);
这个方法会根据路由配置(通常是控制台配置)返回可用的invokers
回到list方法
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(),Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
这个loadbalance是 RoundRobinBalance
@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;
}
LoadBalance是一个组件,默认是RandomLoadBalance
进入FailoverClusterInvoker的doinvoker方法
这里说明一下Cluster
@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;
}
这是一个集群容错模式,默认是FailoverCluster,上一篇讲到RegistryProtocol#doRefer中有一个
cluster.join(directory);
FailoverCluster#join方法默认返回 new FailoverClusterInvoker<T>(directory);
我们进入FailoverClusterInvoker的doInvoke方法
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
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.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//重试时,进行重新选择,避免重试时invoker列表已发生变化.
//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
if (i > 0) {
checkWheatherDestoried();
copyinvokers = list(invocation);
//重新检查一下
checkInvokers(copyinvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List)invoked);
try {
Result result = invoker.invoke(invocation);
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;
for循环中会进行重试。。
看到Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);这句话
Invoker<T> invoker = doselect(loadbalance, invocation, invokers, selected);
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
由于我们的loadBalance是RoundRobinLoadBalance
select 方法在abstractLoadBalance中
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
。。。。
return doSelect(invokers, url, invocation);
}
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int length = invokers.size(); // 总个数
int maxWeight = 0; // 最大权重
int minWeight = Integer.MAX_VALUE; // 最小权重
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
maxWeight = Math.max(maxWeight, weight); // 累计最大权重
minWeight = Math.min(minWeight, weight); // 累计最小权重
}
if (maxWeight > 0 && minWeight < maxWeight) { // 权重不一样
AtomicPositiveInteger weightSequence = weightSequences.get(key);
if (weightSequence == null) {
weightSequences.putIfAbsent(key, new AtomicPositiveInteger());
weightSequence = weightSequences.get(key);
}
int currentWeight = weightSequence.getAndIncrement() % maxWeight;
List<Invoker<T>> weightInvokers = new ArrayList<Invoker<T>>();
for (Invoker<T> invoker : invokers) { // 筛选权重大于当前权重基数的Invoker
if (getWeight(invoker, invocation) > currentWeight) {
weightInvokers.add(invoker);
}
}
int weightLength = weightInvokers.size();
if (weightLength == 1) {
return weightInvokers.get(0);
} else if (weightLength > 1) {
invokers = weightInvokers;
length = invokers.size();
}
}
AtomicPositiveInteger sequence = sequences.get(key);
if (sequence == null) {
sequences.putIfAbsent(key, new AtomicPositiveInteger());
sequence = sequences.get(key);
}
// 取模轮循
return invokers.get(sequence.getAndIncrement() % length);
}
以上是RoundRobinLoadBalance的逻辑
回到FailoverClusterInvoker#doInvoke方法的
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
这里就会返回一个合适的invoker
从上面分析我们就把Direcotry,LoadBalance,Cluster,Router四个组件串起来了
总结下来就是在Cluster中的通过找到Direcotry中有效的invokers,然后根据Router规则过滤去一部分invokers,然后在根据loadBalance从过滤后的invokers找到一个合适invoker
----------------------------------------------------------
上述分析我们都是针对正常调用的情况,dubbo提供了容错和屏蔽两种服务降级得方式
在MockClusterInvoker#invoke中
首先看第二个else if 分支
else {
//fail-mock
try {
result = this.invoker.invoke(invocation);
}catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}
result = doMockInvoke(invocation, e);
}
}
这是一个服务容错,如果调用抛异常了就会进入catch分支
result = doMockInvoke(invocation, e);
看doMockInvoke中的
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
进入List<Invoker<T>> invokers = directory.list(invocation);
invokers = router.route(invokers, getConsumerUrl(), invocation);
public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
URL url, final Invocation invocation) throws RpcException {
if (invocation.getAttachments() == null) {
return getNormalInvokers(invokers);
} else {
String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
if (value == null)
return getNormalInvokers(invokers);
else if (Boolean.TRUE.toString().equalsIgnoreCase(value)){
return getMockedInvokers(invokers);
}
}
return invokers;
}
这里因为是容错,所以走else分支
看这行代码
else if (Boolean.TRUE.toString().equalsIgnoreCase(value)){
return getMockedInvokers(invokers);
}
private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
if (! hasMockProviders(invokers)){
return null;
}
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
for (Invoker<T> invoker : invokers){
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)){
sInvokers.add(invoker);
}
}
return sInvokers;
}
这里会走 if (! hasMockProviders(invokers)){ return null; }分支,所以返回的invokers = null
所以回到MockClusterInvoker#doMockInvoke中
mockInvokers =null
所以
if (mockInvokers == null || mockInvokers.size() == 0){
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
minvoker是自己创建的一个MockInvoker
然后进入MockInvoker#invoke
关注这句话
if (StringUtils.isBlank(mock)){
mock = getUrl().getParameter(Constants.MOCK_KEY);
}
这个是在控制台上配置的
mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
mock = mock.replace('`', '"');
try {
Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
Object value = parseMockValue(mock, returnTypes);
return new RpcResult(value);
这里就会直接返回这个配置的值
-----------------------------------
再看下屏蔽
else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
屏蔽没有去调用服务端代码,而是直接执行doMockInvoke,这部分我们在容错已经分析过了。