前言
捞一下之前的文章:
正文
上篇讲了provider注册流程:通过proxyFactory生成invoker对象然后再通过protocol生成exporter对象并注册;以及consumer引入流程:通过createProxy方法,protocol的refer方法以及其中的Directory、cluster生成invoker然后转为proxy代理对象。
本章将对Dierctory等对象做具体分析
Directory
首先查看Directory接口,主要就是两个方法,可知获取invoker集合就是list方法
public interface Directory<T> extends Node {
/**
* 获取serviceType
*/
Class<T> getInterface();
/**
* 获取invoker集合
*/
List<Invoker<T>> list(Invocation invocation) throws RpcException;
}
查看一下继承结构,一个抽象类和两个子类实现,这是模板方法设计模式的体现
进入到AbstractDirectory类,看list方法,调用的是doList方法,而这个doList方法是一个抽象方法,所以此处逝去走子类方法。
public abstract class AbstractDirectory<T> implements Directory<T> {
@Override
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed) {
throw new RpcException("Directory already destroyed .url: " + getUrl());
}
return doList(invocation);
}
protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;
}
AbstractDirectory有两个子类实现,静态的就不用看了,直接看RegistryDirectory类doList方法。
@Override
public List<Invoker<T>> doList(Invocation invocation) {
if (forbidden) {
// 1.没有生产者 2.生产者不可达 抛异常
...
}
if (multiGroup) {
return this.invokers == null ? Collections.emptyList() : this.invokers;
}
List<Invoker<T>> invokers = null;
try {
// 交给路由chain去处理并且获取所有的invokers.
invokers = routerChain.route(getConsumerUrl(), invocation);
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
...
return invokers == null ? Collections.emptyList() : invokers;
}
到这里Directory的任务就结束了,接下来就看routerChain.route方法了。
Router
首先进入到routerChain.route方法中
public List<Invoker<T>> route(URL url, Invocation invocation) {
//所有invokers
List<Invoker<T>> finalInvokers = invokers;
//有路由规则走路由 需要经过所有的路由
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
//返回结果
return finalInvokers;
}
invokers属性是在ResgitryDirectory.refreshInvoker方法时赋值
routers属性是在RegistryProtocol.doRefer方法时赋值
此处routerChain.route方法内调用了Router.route方法。先看一下Router接口
public interface Router extends Comparable<Router> {
int DEFAULT_PRIORITY = Integer.MAX_VALUE;
...
/**
* 过滤当前路由规则,只返回符合条件的
*
* @param invokers invoker list
* @param url refer url
* @param invocation invocation
* @return routed invokers
* @throws RpcException
*/
<T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
...
}
然后看一下Router的继承体系,熟悉的模板方法,先是一个抽象类,然后具体方法子类实现
这边主要通过ConditionRouter讲解一下,在看方法前有两个属性要注意一下
whenCondition和thenCondition
//是否满足判断条件
protected Map<String, MatchPair> whenCondition;
//满足判断条件后 如何选择invokers
protected Map<String, MatchPair> thenCondition;
MatchPair
其中有个MatchPaire对象,查看一下
protected static final class MatchPair {
//满足的条件
final Set<String> matches = new HashSet<String>();
//不满足的条件
final Set<String> mismatches = new HashSet<String>();
}
进入conditionRouter.route方法
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
//是否启用
if (!enabled) {
return invokers;
}
//invokers为空,则不用判断
if (CollectionUtils.isEmpty(invokers)) {
return invokers;
}
try {
//是否符合条件
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
//判断invoker的url是否符合条件
for (Invoker<T> invoker : invokers) {
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
//不为空返回
if (!result.isEmpty()) {
return result;
} else if (force) {
//为空,但必须走这段逻辑,则记录日志
logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
return result;
}
} catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
返回结果
return invokers;
}
接下来看一下判断方法
matchThen和matchWhen
boolean matchWhen(URL url, Invocation invocation) {
//如果无条件,或者符合条件则返回
return CollectionUtils.isEmptyMap(whenCondition) || matchCondition(whenCondition, url, null, invocation);
}
private boolean matchThen(URL url, URL param) {
//如果条件不为空 并且符合条件才返回
return CollectionUtils.isNotEmptyMap(thenCondition) && matchCondition(thenCondition, url, param, null);
}
具体的路由规则如何实现的,感兴趣的话可以去看一下ConditionRouter.init()方法
Cluster
在consumer引入服务时,我们有一个步骤就是将RegisterDirectory转为Invoker的,当时是调用了Cluster.Join方法,现在看一下Cluster具体做了什么。
首先查看Cluster接口,是一个Spi扩展类,默认使用FailoverCluster,
集群容错
这是dubbo的集群容错,每个类都有不同的策略
@SPI(FailoverCluster.NAME)
public interface Cluster {
/**
* 合并directory的invokers成为一个新的invoker
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
进入默认实现查看join方法,此处逻辑很简单,就是构建了一个新对象FailoverClusterInvoker
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<T>(directory);
}
}
Invoker (Cluster)
首先查看一下invoker接口,只有一个重点方法invoke,也就是执行。
public interface Invoker<T> extends Node {
/**
* 获取service接口
*
* @return service interface.
*/
Class<T> getInterface();
/**
* 执行方法
*
* @param invocation
* @return result
* @throws RpcException
*/
Result invoke(Invocation invocation) throws RpcException;
}
查看一下继承结构,又是一个模板方法结构。
AbstractClusterInvoker
首先看父类的invoke方法
@Override
public Result invoke(final Invocation invocation) throws RpcException {
//是否被销毁关闭
checkWhetherDestroyed();
// 拷贝当前RPCContext中的附加信息到当前的invocation中
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
//所有已经经过路由的invoker
List<Invoker<T>> invokers = list(invocation);
//初始化负载均衡
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
//用于适配异步请求使用
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
//抽象方法,调用子类
return doInvoke(invocation, invokers, loadbalance);
}
子类FailoverClusterInvoker
查看子类FailoverClusterInvoker.doInvoke方法
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
//检查invoker,如果没有抛出异常
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);
//获取方法名,获取方法最大重试次数
String methodName = RpcUtils.getMethodName(invocation);
int len = getUrl().getMethodParameter(methodName, 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());
Set<String> providers = new HashSet<String>(len);
//可重试多少次
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.
if (i > 0) {
//检查是否关闭
checkWhetherDestroyed();
//重新获取列表
copyInvokers = list(invocation);
//检查是否有invoker
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()) {
...
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
...
}
LoadBalance
刚刚上述Invoker.invoke方法中有一个selet方法,这个方法就是对负载均衡的使用,现在我们对负载均衡进行解读。
首先看AbstractClusterInvoker.invoke中的初始化负载均衡器方法initLoadBalance
protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
//判断是否有invokers,如果有就用invoker配置的 没有就用默认的
if (CollectionUtils.isNotEmpty(invokers)) {
return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
}
看一下LoadBalance接口使用了spi,且默认是随机负载均衡
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
/**
* 从集合中找出一个invoker
*
* @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;
}
看一下继承体系,嗯。。又是一个模板方法
AbstractLoadBalance
所以我们直接先看父类的select方法,逻辑很简单,就是判断为空直接返回,只有一个直接返回第一个,以及调用子类doSelect方法
@Override
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
return doSelect(invokers, url, invocation);
}
子类RandomLoadBalance
这边拿默认RandomLoadBalance来讲解,这个方法主要是用到了一个比较出名的算法-轮盘赌算法。
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// invokers的数量
int length = invokers.size();
// 是否是相同的权重
boolean sameWeight = true;
// 记录所有权重
int[] weights = new int[length];
// 第一个invoker的权重
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// 计算总权重,并将权重全部放入表中
int totalWeight = firstWeight;
for (int i = 1; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// 记录表中
weights[i] = weight;
// 求和
totalWeight += weight;
//如果权重不同则标记
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// 如果权重不同 通过总权重来分配
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// 循环减少每个invoker的权重,如果小于0 则选中invoker
for (int i = 0; i < length; i++) {
offset -= weights[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
//权重相同随机选择一个
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
什么是轮盘赌算法
打个比方,现在有A B C三种奖。A的概率是20 B的概率是30 C的概率是50; 将这三个数字放在一个圆圈,或者一条直线上,一条直线上比较好理解。
如图所示
- 1.随机数是19的时候
- 第一次19-20<0 所以是A
- 2.随机数是30的时候
- 第一次30-20>0
- 第二次10-30<0 所以是B
- 3.随机数是60的时候
- 第一次60-20>0
- 第二次40-30>0
- 第三次10-50<0 所以是C
这样就是轮盘赌算法