上一篇订阅介绍了服务端如何将provider暴露给客户端。
暴露完成后,会在客户端缓存,也就是在RegistryDirectory下的methodLocalMap下。
客户端经过负载均衡和访问拦截后访问远程provider,然后返回结果给调用方。下图是客户端调用过程的序列图。
在dubbo spi核心概念我们介绍过Filter和InvokerListener是如何通过wrapper作为扩展点织入进dubbo的。通过Protocol#refer返回给客户端的invoker其实是
Cluster invoker
–filter invoker
–InvokerListener
–Invoker
这样的结构,所以在调用时会通过Cluster往下依次调用,而Cluster则会经过select和loadBalance等过程。
对Reference,Cluster invoker等陌生的可以参考dubbo cluster架构篇
备援,调用失败时,会切换到另一个节点调用。备援一般会有次数限制,超过固定次数后则判定为调用失败。
负载均衡,将请求均衡的分布到集群中的每个节点上。Dubbo支持一致性hash,轮询,随机以及最小连接等策略,默认是随机。
负载均衡和备援合起来组成了dubbo的HA方案。
客户端的调用由Proxy委派给Cluster,以下的代码以dubbo默认策略也就是备援策略– FailoverClusterInvoker,其继承了AbstractClusterInvoker
AbstractClusterInvoker#invoke,只提取关键代码
public Result invoke(final Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = list(invocation);
return doInvoke(invocation, invokers, loadbalance);
}
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
RegistryDirectory#list,从methodInvokerMap里对应方法的Invoker.
1. 方法的第一个参数可以作为动态路由参数
2. 方法名没有对应Invoker时,就取服务通用的
3. 以上几种情况都没有对应Invoker的情况下,返回第一组
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = doList(invocation);
return invokers;
}
public List<Invoker<T>> doList(Invocation invocation) {
List<Invoker<T>> invokers = null;
Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference
if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) {
String methodName = invocation.getMethodName();
Object[] args = invocation.getArguments();
if(//第一个参数是String或者Enum) {
invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // 可根据第一个参数枚举路由
}
if(invokers == null) {
invokers = localMethodInvokerMap.get(methodName);
}
if(invokers == null) {
invokers = localMethodInvokerMap.get(Constants.ANY_VALUE);
}
if(invokers == null) {
Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator();
if (iterator.hasNext()) {
invokers = iterator.next();
}
}
}
return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers;
}
FailoverClusterInvoke#doInvoke,提取这个方法的关键逻辑。
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
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) {
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);
return result;
}catch(){}
}
}
详细说备援的过程:
1. 调用抛异常时,会进行重试,重试次数通过Constants.RETRIES_KEY配置
2. 重试时会重新list,并过滤掉已调用失败的节点,select(loadbalance, invocation, copyinvokers, invoked)里的invoked里记录了失败节点
3. 通过select(loadbalance, invocation, copyinvokers, invoked)在invoker list里做负载均衡
4. select出invoker后,直接做invoke,这里的invoker也是个filter Invoke,这时候就开始链式拦截。
最后看一下LoadBalance的接口描述
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
select方法是个Adaptive方法,会从url里提取loadbalance参数值做动态委派。负载均衡比较简单,这里不做进一步展开。