本文基于dubbo v2.6.x
1. 介绍
在《深度解析dubbo集群之API》一文中,我们介绍Cluster 接口以及它的实现类与实现类的功能,通过上文学习,我们知道它每个实现类的join方法里面都会创建一个与之功能对应的invoker, 本文我们就来介绍下invoker是怎样实现对应功能的。首先我们先来看看那些invoker的继承关系
我们可以看到,它们继承AbstractClusterInvoker 这个抽象类,这个抽象类实现Invoker接口,重写invoke()方法,继承这个抽象类的需要实现doInvoke方法。我们来具体看下AbstractClusterInvoker 抽象类。
2. AbstractClusterInvoker源码解析
首先看下AbstractClusterInvoker的成员变量,我们重点关注的是directory 成员,这个对象就可以理解为目录,里面有一堆的该接口的服务提供者的invoker,我们获取服务提供者列表就是找它拿的
protected final Directory<T> directory;//服务提供者的一个目录
protected final boolean availablecheck; //集群中是否排除不可用的invoker
// 判断是否已经销毁
private AtomicBoolean destroyed = new AtomicBoolean(false);
private volatile Invoker<T> stickyInvoker = null;
再来看看它的构造方法:
public AbstractClusterInvoker(Directory<T> directory) {
this(directory, directory.getUrl());
}
public AbstractClusterInvoker(Directory<T> directory, URL url) {
if (directory == null)//验证参数
throw new IllegalArgumentException("service directory == null");
this.directory = directory;
//"cluster.availablecheck" 默认true 集群中是否排除不可用的invoker
//sticky: invoker.isAvailable() should always be checked before using when availablecheck is true.
this.availablecheck = url.getParameter(Constants.CLUSTER_AVAILABLE_CHECK_KEY, Constants.DEFAULT_CLUSTER_AVAILABLE_CHECK);
}
可以看出构造中是对availablecheck ,directory赋值,接着看下其他方法:
// 实现接口的invoker方法
@Override
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed(); // 检验是否销毁
LoadBalance loadbalance = null; //定义负载均衡
// binding attachments into invocation.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {// 就是将一些公共的kv 设置到invocation中
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 获取所有服务提供者的集合
List<Invoker<T>> invokers = list(invocation);
if (invokers != null && !invokers.isEmpty()) {
// invokers 不是空 根据dubbo spi 获取负载均衡策略 默认是random
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
}
// 如果是异步调用,就设置调用编号
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
// 具体实现是由子类实现的
return doInvoke(invocation, invokers, loadbalance);
}
这个invoke方法重点看下,首先检查 是否销毁,然后将context上下文中的参数塞到invocation调用信息中,调用list方法获取服务提供者列表。如果服务提供者列表不是空的,然后根据你配置的负载均衡的值使用dubbo spi 获取其实例,默认是使用随机负载的。接着就是如果是一部调用的话,设置调用编号。最后交给子类来实现具体的调用,这里也就是调用doInvoke(该方法是个抽象方法,需要其子类实现)
// 模版方法, 交由子类来实现
protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException;
接下来我们看看list 方法是怎样获取服务提供者列表的。
/**
* 获取所有服务提供者的invoker
* @param invocation
* @return
* @throws RpcException
*/
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
// 从directory中获取所有的invokers
List<Invoker<T>> invokers = directory.list(invocation);
return invokers;
}
可以看出来是调用的directory 的list方法获取的服务提供者列表,关于directory 我们后面会有文章讲解,现在只需要知道他能给我们服务提供者列表。我们再来看下这个抽象类的其他方法:
/**
* Select a invoker using loadbalance policy.</br>
* a)Firstly, select an invoker using loadbalance. If this invoker is in previously selected list, or,
* if this invoker is unavailable, then continue step b (reselect), otherwise return the first selected invoker</br>
* b)Reslection, the validation rule for reselection: selected > available. This rule guarantees that
* the selected invoker has the minimum chance to be one in the previously selected list, and also
* guarantees this invoker is available.
* 从候选的invoker 集合里面获取一个最终调用的invoker
* @param loadbalance load balance policy
* @param invocation
* @param invokers invoker candidates
* @param selected exclude selected invokers or not
* @return
* @throws RpcException
*/
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();
// 是老使用一个invoker 默认是false的
boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY);
{
//ignore overloaded method
// 如果stickyInvoker 不是null 而且没有在invokers 中了, 则置为空
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
stickyInvoker = null;
}
//ignore concurrency problem
// 支持粘滞连接 && stickyInvoker 不是null && (排除列表是null 或者排除列表中不包含stickyInvoker )
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
if (availablecheck && stickyInvoker.isAvailable()) {//是否排除不可用的invoker
return stickyInvoker; // 直接返回 粘滞连接
}
}
}
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
// 如果支持 粘滞连接 则设置 缓存 粘滞连接
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}
在这个方法中出现了一个新概念:粘滞连接,官网对于它的解释:粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。可以看下官网文档:链接,可以看出来这个粘滞连接 开启,总会调用一个服务提供者,如果这个服务提供者不可用了,就换一台。关于粘滞连接配置可以看下官方文档。
好,我们看下代码,首先获取是否开启粘滞连接 ,如果开启了粘滞连接 ,粘滞连接不是null,然后没在排除列表中,直接返回这个粘滞连接invoker。如果没有开启粘滞连接 或者 粘滞连接 不可用, 接着调用doSelect方法选择出来一个invoker,最后开启粘滞连接就把这个invoker赋值给stickyInvoker ,返回选择出来的invoker。
我们再来看下doSelect放是怎样选择的。
//进行选择
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers == null || invokers.isEmpty())// invoker 列表是空就返回null
return null;
if (invokers.size() == 1) // 就一个的话就 就用第一个
return invokers.get(0);
if (loadbalance == null) { // 获取负载均衡策略 默认是random(随机)
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
// 使用策略进行选择
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
//If the `invoker` is in the `selected` or invoker is unavailable && availablecheck is true, reselect.
if ((selected != null && selected.contains(invoker)) // selected 存在选择的invoker 或者 检测可用参数是true + invoker 不可用
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// 进行重新选择
Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rinvoker != null) { // 重新选择出来的不是null
invoker = rinvoker;
} else { // 如果重新选择出来的是null
//Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
int index = invokers.indexOf(invoker);
try {
//Avoid collision 选择 后一个 或者是第一个invoker
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;
}
首先,如果服务提供者列表就一个元素的话,直接返回那一个,如果负载均衡对象loadbalance 是空的话,使用dubbo spi获取一个负载均衡实例,先使用负载均衡算法从服务提供者列表中选出来一个invoker,如果选择出来的这个invoker 不可用或者在selected列表中中了,就调用reselect方法进行重新选择,如果重新选出来的这个invoker不是null,就直接返回,如果是null,就根据之前选出来的那个invoker的位置选择服务提供者列表的第一个或者是它后面一个元素,就是 之前那个invoker 位置不是最后一个元素的话,就选它后面那个invoker,如果是最后一个就选第一个invoker。
接下来在来看看reselect 这个重选方法:
/**
* Reselect, use invokers not in `selected` first,
* if all invokers are in `selected`,
* just pick an available one using loadbalance policy.
* 重新选择, 首先使用invokers 中没有在selected 里面的,
* 如果所有的invokers 中都在selected 中,就根据负载均衡策略选择一个
* @param loadbalance
* @param invocation
* @param invokers
* @param selected
* @return
* @throws RpcException
*/
private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck)
throws RpcException {
//Allocating one in advance, this list is certain to be used.
List<Invoker<T>> reselectInvokers = new ArrayList<Invoker<T>>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());
//First, try picking a invoker not in `selected`. 只要没有选择过的。
if (availablecheck) { // invoker.isAvailable() should be checked 检测是否可用
for (Invoker<T> invoker : invokers) { // 找出所有可用并且没有在selected 中的 invoker, 然后添加到reselectInvokers 中
if (invoker.isAvailable()) {
if (selected == null || !selected.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}
// 如果 reselectInvokers 不是空的, 就根据负载均衡的策略选择一个invoker
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
} else { // do not check invoker.isAvailable() 不检测可用性
for (Invoker<T> invoker : invokers) { // 遍历invokers ,找出不在selected 中的invoker 添加到reselectInvokers中
if (selected == null || !selected.contains(invoker)) {
reselectInvokers.add(invoker);
}
}// 不是空的话,就使用负载均衡策略 选择一个
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
}
// Just pick an available invoker using loadbalance policy 只要可用就可以了
{
if (selected != null) { // 这个只选择可用的就行了, 在没在selected 列表中的就不管了。。。
for (Invoker<T> invoker : selected) {
if ((invoker.isAvailable()) // available first
&& !reselectInvokers.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}// 使用负载均衡策略 选择一个
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
}
return null;
}
首先是判断是否检查服务提供者可用性,如果检查可用性的话,遍历服务提供者列表,如果对应服务提供者可用,然后又没有在排除集合中,就将它加入到reselectInvokers这个集合中,如果reselectInvokers这个集合不是空的,根据负载均衡算法选择一个invoker返回。
如果不检查可用性,只要是没有在排除列表中就加入到reselectInvokers这个集合中 ,如果集合不是空,就根据负载均衡算法选择一个返回。
如果这时候还没有找到一个合适的invoker,就遍历服务提供者列表,这个只选择可用的就行了, 并且没有被添加到reselectInvokers集合中,就添加到reselectInvokers 集合中,如果这个集合不是空,使用负载均衡算法选择一个返回。
好了,我们在看下这个抽象类的其他方法:
@Override
public Class<T> getInterface() {
return directory.getInterface();
}
@Override
public URL getUrl() {
return directory.getUrl();
}
@Override
public boolean isAvailable() {
Invoker<T> invoker = stickyInvoker;
if (invoker != null) {// 如果invoker不等于null 就使用invoker的
return invoker.isAvailable();
}
// 否则就使用directory的
return directory.isAvailable();
}
// 销毁
@Override
public void destroy() {
if (destroyed.compareAndSet(false, true)) {
directory.destroy(); // 先设置状态 ,然后调用directory的destroy操作
}
}
可以看出来getInterface ,getUrl都是获取的directory对象的,isAvailable 检查的是stickyInvoker的可用性或者是directory的可用性,然后这个销毁方法destroy ,销毁的时候先cas设置销毁状态,然后调用directory.destroy()方法进行销毁
3. 总结
本文主要是介绍了集群抽象类AbstractClusterInvoker的实现,主要解析了它的invoke方法,select 方法,doSelect方法,reselect方法等等,后面的文章我们将会一一介绍它的实现类。