目录
这张图很好的说明了Dubbo中Cluster的作用,但是Cluster是如何起作用的,如何包装Inovker的呢? 我会在下面的文章中说明。
Directory
维护服务提供者列表(List),并且实时监控服务提供者的状态,当服务提供者下线后,需要把提供其移除。 并且这类还会保存有本地缓存, 当注册中心挂了后就会使用本地缓存的信息。
Router
路由, 比如我们需要同机房的调用,或者同地域的调用,可以实现这个接口来满足需求,这个接口的逻辑是把Directory中的所有服务者按照要求拿出来给后面的LoadBalance来使用。
从图中看到好像有两种方式可以 脚本和编码的方式来实现不同的路由。 这部分我还没有了解, 后面会深入了解的。
LoadBalance
负载均衡的实现, 这个我比较熟悉,这个主要是把Router提供的服务者 按照规则挑选一个来调用。
Cluster
把上面的所有接口封装起来的接口, 并且它会把所有的服务提供者封装成一个逻辑的Invoker 给上层使用, 他会屏蔽路由负载均衡 和失败策略的逻辑,对上层来说就是简单的调用。
它会根据不同的失败策略有不同的实现, 默认是的FailOver 实现, 就是失败了后会换个服务提供者调用,默认会换两个调用者重试。
Cluster 是通过SPI加载的。
SPI的配置文件地址在dubbo-cluster模块的resources/META-INF.dubbo.internal目录下
而Cluster是给消费者使用的,所以使用的地方自然是org.apache.dubbo.config.ReferenceConfig
类了。
ReferenceConfig 类
看下ReferenceConfig中类的源码
最重要的三大类都是通过SPI加载到ReferenceConfig 类中。
ReferenceConfig的调用流程
ReferenceConfig类是一个FactoryBean类, 它最终是把get方法的返回值放入容器的, 就是在get方法中会调用一次init方法,在init方法中会调用createProxy类生成代理类
createProxy创建代理类
生成代理类的方法 org.apache.dubbo.config.ReferenceConfig#createProxy
private T createProxy(Map<String, String> map) {
if (shouldJvmRefer(map)) {
...........本地服务提供者的逻辑...忽略掉.................
} else {
..... 对于Url的校验处理,忽略.................
}
if (urls.size() == 1) {
// 只有一个服务提供者, 直接使用DubboProtocol生成对应的Invoker。 集群就没有意义了,所以直接返回这个Invoker
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
//对于多个URL(服务提供者), 逐个遍历
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
//逐个生成Invoker并加入到List中
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
// The invoker wrap relation would be like: ZoneAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, routing happens here) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else { // not a registry url, must be direct invoke.
// 调用CLUSTER的spi方法, 把所有的Invoker都封装到Directory中, 并返回一个包装好的Invoker
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
if (shouldCheck() && !invoker.isAvailable()) {
invoker.destroy();
throw new IllegalStateException("Failed to check the status of the service "
+ interfaceName
+ ". No provider available for the service "
+ (group == null ? "" : group + "/")
+ interfaceName +
(version == null ? "" : ":" + version)
+ " from the url "
+ invoker.getUrl()
+ " to the consumer "
+ NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
String metadata = map.get(METADATA_KEY);
WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
if (metadataService != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataService.publishServiceDefinition(consumerURL);
}
// create service proxy
//根据返回包装好的Invoker, 生成代理类,proxyFactory是使用字节码的方式生成代理类的
return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}
整个createProxy的逻辑如下:
- 先校验URL, 并完善处理好URL的内容,这部分的逻辑不重要, 在上面的源码中也忽略掉了
- 遍历每个URL, 每个URL都调用DubboProtocol#refer来生成一个Invoker , 其中每个URL代表一个服务者
- 把Invoker的的list集合传入Cluster的join方法中, 并返回一个Invoker。 Cluster的逻辑在后文中说明。
- 把Cluster返回的Invoker传入ProxyFactory#getProxy来生成放入容器中的代理类的Bean , ProxyFactory内部是使用字节码的方式生成代理类的。
Cluster#join方法
它的默认实现类是FailoverCluster
/**
* {@link FailoverClusterInvoker}
*
*/
public class FailoverCluster extends AbstractCluster {
public final static String NAME = "failover";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<>(directory);
}
}
返回的实际上是FailoverClusterInvoker
类, 看下FailoverClusterInvoker
类;
直接看其核心方法
/**
*
* @param invocation 方法调用参数等信息的封装类
* @param invokers Router挑选过一遍的服务提供者
* @param loadbalance 负载均衡策略类
* @return
* @throws RpcException
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
//校验逻辑
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
//获取配置的重试次数。 默认是2+1次
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, 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++) {
//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);
// check again
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 " + methodName
+ " 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;
} 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());
}
}
throw new RpcException(le.getCode(), "Failed to invoke the method "
+ methodName + " 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.getMessage(), le.getCause() != null ? le.getCause() : le);
}
在这个方法中会处理配置的重试次数,会循环设置的次数, 如何循环次数用完了还没有成功,就会抛出异常,默认是2+1次。
具体的选择调用者的逻辑又会跑到父类中, 在父类中会使用会使用loadBalance
类的select方法来确定具体的invoker。
总结
Cluster是Dubbo的集群负载处理接口, 默认实现是FailoverCluster。 它的作用场景是在ReferenceConfig中, 它会返回 FailoverClusterInvoker来代替所有服务提供者Invoker,并且Inovker的选择逻辑也是封装在FailoverClusterInvoker中的, Router和LoadBalance的逻辑都是封装在这个类中, 最后会使用ProxyFactory来根据这个FailoverClusterInvoker创建出代理类放入容器中。