五Dubbo服务引用源码分析--3服务引用-下
五Dubbo服务引用源码分析--3服务引用-下
紧接上文,对服务引用源码分析:
TAG 1.1.2 FailOverClusterInvoker.doInvoke()--cluster中调用服务
返回AbstractClusterInvoker.invoke()
上面挑选出了可以正常执行的invokers,但是多个做集群,此时该执行哪一个。需要如下:
这里仍然采用模板方法模式,doInvoke由子类实现。
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。集群对应的容错方案,可以生成对应的Invoker类。
跟入FailOverClusterInvoker.doInvoke
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException { //0
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
//获取url中invocation.getMethodName方法的参数中,关于retries重试次数
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;//默认重试次数为3
if (len <= 0) {
len = 1;
}
RpcException le = null; // 记录loop循环中上一次exception内容
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // 记录已经被invoked的invokers.
Set<String> providers = new HashSet<String>(len);//根据重试次数,每次获取不同的提供者provider
/** …………………………………………………………………………………………………失败重试failover的for循环……………………………………………………………………………………………………… */
for (int i = 0; i < len; i++) { //1
//重试时,进行重新选择,避免重试时invoker列表已发生变化.
//注意:如果列表发生了变化,那么invoked判断会失效,因为invoker示例已经改变
if (i > 0) { //2
checkWheatherDestoried();
//DOI1 list(invocation)-失败重试时重新获取invokers
//重新获取invokers列表
copyinvokers = list(invocation);
//重新检查一下
checkInvokers(copyinvokers, invocation);
} //2
//DOI2 select(loadbalance, invocation, copyinvokers, invoked)
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
//将select选出来的invoker,加入invoked队列,表示选出来过,调用过(无论invoker.invoke是否成功)
invoked.add(invoker);
RpcContext.getContext().setInvokers((List)invoked);
try { //2
//DOI3 invoker.invoke(invocation)--实际调用
//实际调用
Result result = invoker.invoke(invocation);
//如果le不为null,表示发生过失败重试,此时log记录前一次失败信息
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;
}
//如果invoker.invoke异常,不执行return,继续loop重试
catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
//loop执行中,记录每次调用服务的provider的address地址
providers.add(invoker.getUrl().getAddress());
} //2
} //1
throw new RpcException(le != null ? le.getCode() : 0, "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 != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
}//0
注意,这里实现的是failover策略,失败重试,设置重试次数len。失败重试时候,需要从新list进行选择,防止重试时候invokers列表(invokers列表,是从directory.list获得的)发生变化。
doInvoke()主要逻辑:
1 从invocation中获取重试次数参数retries=len;
2 for循环,进行失败重试逻辑:
a 判断循环是否是第一次,如果不是第一次执行,表示失败重试过,避免list获取的invokers列表变化,需要重新list获取;
b 执行select逻辑,从invokers中依据loadbanlance规则,选择一个可执行的invoker。然后记录选出来的invoker到invoked,避免再次选到;
c log(error) 失败重试时,此处log记录上次失败的信息
d invoker.invoke调用服务
上述是failover失败重试的主要逻辑。
DOI1 list(invocation)-失败重试时重新获取invokers
注意,在失败重试时,需要重新根据rpcInvocation
AbstractClusterInvoker
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
//这里,调用registryDirectory.list重新获取invokers
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
详见[TAG1.1.1 registryDirectory.list(invocation)](#TAG1.1.1 registryDirectory.list(invocation))
DOI2 AbstractClusterInvoker.select(loadbalance, invocation, copyinvokers, invoked)
然后select,通过loadbalance选择invoker
AbstractClusterInvoker.select
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers == null || invokers.isEmpty())
return null;
String methodName = invocation == null ? "" : invocation.getMethodName();
boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);//默认为false,非持久的
{
//ignore overloaded method
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
stickyInvoker = null;
}
//ignore concurrency problem
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
if (availablecheck && stickyInvoker.isAvailable()) {
return stickyInvoker;
}
}
}
//执行负载均衡策略,选择出可执行的invoker
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}
select是AbstraceClusterInvoker内的方法。
接下来就是LoadBalance模块出现。
AbstraceClusterInvoker
//参数:randomLoadBalance
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers == null || invokers.isEmpty())
return null;
//如果invokers只有一个invoker,直接返回
if (invokers.size() == 1)
return invokers.get(0);
if (loadbalance == null) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
//DOI2.1 loadbalance.select(invokers, getUrl(), invocation)
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//If the `invoker` is in the `selected` or invoker is unavailable && availablecheck is true, reselect.
//如果选出的invoker已经被执行过(在selected中)、不可用时,重新选择reselect
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
//DOI2.2 AbstraceClusterInvoker.reselect
Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rinvoker != null) {
//重新选择的部位null,直接返回
invoker = rinvoker;
} else {
//如果reselect选择的invoker为null,判断之前选择的invoker在invokers中的index,如果不是最后一个,就选择下一个;否则,返回第一个
int index = invokers.indexOf(invoker);
try {
//避免碰撞
invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}
doSelect逻辑如下:
1 Dubbo提供了多种均衡策略,缺省为随机调用,但是如果集群的数量为1,那么将直接返回。
2 当传入invokers参数大于一个,执行负载均衡选择出一个invoker;
3 如果invoked包含该invoker,执行重选。如果重选成功,赋值;如果重选出为null,则根据上一次选择invoker的位置,选择invokers列表中的下一个节点。
DOI2.1 loadbalance.select(invokers, getUrl(), invocation)
这时候进入loadBalance选择出Invoker。(相关具体算法看后面的负载均衡分析中)
此时先执行抽象类中的select方法
当invokers大于两个,执行LoadBalance实现类的doSelect方法。
仍旧是模板方法,doselect由子类实现
public class RandomLoadBalance extends AbstractLoadBalance {
private final Random random = new Random();//随机
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size(); //invokers总个数
int totalWeight = 0; // 总权重
boolean sameWeight = true; // 权重是否都一样
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight; // 累计总权重
if (sameWeight && i > 0
&& weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
//如果权重不相同且权重大于0,按总权重随机选择
int offset = random.nextInt(totalWeight);
// 确定随机值落在哪个invoker的权重区间片断上
for (int i = 0; i < length; i++) {
offset -= getWeight(invokers.get(i), invocation);
if (offset < 0) {
return invokers.get(i);
}
}
}
// 如果权重相同、或者为0,则均等随机地获取invoker
return invokers.get(random.nextInt(length));
}
总结:
在集群容错的整体架构中,consumer端invoker.invoke()的执行过程,dubbo做了如下三件事:
1 在 Directory中找出本次集群中的全部 invokers
2 在 Router中,将上一步的全部 invokers挑选出能正常执行的 invokers(即invoker.getUrl的协议头不是mock://的);
3 在 LoadBalance中,将上一步的能正常的执行 invokers中,根据配置的负载均衡策略,挑选出需要执行的 invoker。
注意:
到此为止,只是loadbalance选出一个可以执行的invoker。具体的执行invoker,继续看下面。
DOI3 invoker.invoke(invocation)--实际调用(核心)--filter链式调用
经过router路由、loadbalance负载均衡返回的invoker如下:
从返回的invoker可知,由外到内的包裹层次为:RegistryDirectory$InvokerDelegate->ProtocolFilterWrapper$1->ListenerInvokerWrapper->DubboInvoker。
RegistryDirectory$InvokerDelegate---consumer端初始化过程创建的invoker的代理类;//DOI3.1
ProtocolFilterWrapper$1--filter过滤的invoker类,包含filter链;//DOI3.2
ListenerInvokerWrapper--包裹了listener,在服务refer时执行listener.refer;//DOI3.3
dubboInvoker--核心的invoker,内部包含了远程调用的nettyClient客户端 //DOI3.4
DOI3.1 RegistryDirectory$InvokerDelegate.invoke
//RegistryDirectory$InvokerDelegate父类
public class InvokerWrapper<T> implements Invoker<T> {
@Override
public Result invoke(Invocation invocation) throws RpcException {
//代理给内部invoker.invoke处理
return invoker.invoke(invocation);
}
DOI3.2 ProtocolFilterWrapper$1.invoke(调用时,consumer端filter链式处理)
在对invoker构造的filter拦截器包装类为ProtocolFilterWrapper,其以链式结构存在。
ProtocolFilterWrapper
public Result invoke(Invocation invocation) throws RpcException {
//参数next,为filter链上的下一个invoker对象
return filter.invoke(next, invocation);
}
此时,执行filter链。
中间的ConsumerContextFilter、FutureFilter、MonitorFilter拦截器的filter.invoke(next,invocation)拦截过程,省略
以futureFilter为例:
@Activate(group = Constants.CONSUMER)
public class FutureFilter implements Filter {
protected static final Logger logger = LoggerFactory.getLogger(FutureFilter.class);
@Override
public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
fireInvokeCallback(invoker, invocation);
// need to configure if there's return value before the invocation in order to help invoker to judge if it's
// necessary to return future.
//链式调用,传递给下一个protocolFilterWrapper
Result result = invoker.invoke(invocation);
if (isAsync) {
asyncCallback(invoker, invocation);
} else {
syncCallback(invoker, invocation, result);
}
return result;
DOI3.3 DubboInvoker.invoke--(AbstractInvoker.invoke)
从DubboInvoker.invoke的调用开始,进入到consumer的请求发送阶段。
涉及到consumer---->provider端,Exchange、Transport、Serialize层级的处理逻辑。核心在于底层Netty通信框架对消息的构造、同步转异步、编解码、Netty通道上注册channelHandler的处理流程的内容,以及provider接受到请求对信息的处理。
消息的发送详情见5.3章节。