上一篇文章中,讲到 Dubbo 初始化一个 代理对象时,会执 ReferenceConfig
的 init
方法,而后执行其
ref = createProxy(map);
而在这一步中,会进行以下几步:
- 判断是否是
Jvm
类型应用 - 组装url
- 代理生成
invoker
- 最后 执行
(T) PROXY_FACTORY.getProxy(invoker);
返回type
类型对象。
Dubbo 中 Invoker
Invoker
是 Dubbo
的核心模型,代表一个可执行体。在服务提供方,Invoker
用于调用服务提供类。在服务消费方,Invoker
用于执行远程调用。Invoker
是由 Protocol
实现类构建而来。Protocol
实现类有很多,它本身也是一个 SPI 类型。
上一篇文章中,分析了参数组装,以及 Wrapper 类 和 SPI 组装关系。
准备工作
这篇即重点分析 invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
。
结合上两篇文章,由于 Protocol 没有 默认的 Adaptive
类,所以将使用默认的 Protocol$Adaptive
类此处,有需要可以看这篇文章:
Dubbo 中默认的 Adaptive类生成过程及例子 由于 url是 registry
类型,所以最终得到的是 RegistryProtocl
,而里面和Wrapper 类组装而成的结构为:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol
起始最终 init 中 PROXY_FACTORY
类型为 :ProtocolFilterWrapper
,有前面的分析,包装类其实是链式的组装结构,即最终会依次调用:ProtocolFilterWrapper
、ProtocolListenerWrapper
、QosProtocolWrapper
的 refer
方法,而由代码可知,当url
的协议为 registry
时候,则默认会跳过而执行下一个 Protocol
的refer
。这样一来,依次执行玩前三个Wrapper
之后,就会执行 RegistryProtocol
的 refer
。
本文将重点分析RegistryProtocol
的 refer,其中会包括Dubbo 集群容错的一些相关内容介绍。
RegistryProtocol 的 refer
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// 拼凑 url
url = URLBuilder.from(url)
.setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
.removeParameter(REGISTRY_KEY)
.build();
// 获取注册中心
Registry registry = registryFactory.getRegistry(url);
// 如果类型就是 `RegistryService` 类型
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
String group = qs.get(GROUP_KEY);
if (group != null && group.length() > 0) {
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 执行 doRefer
return doRefer(cluster, registry, type, url);
}
上文代码中,主要做了以下几步:
- 拼凑url
- 判断是否为
RegistryService
,是得化说明不是具体接口类型,所以直接返回 - 如果有group 参数的,则使用
MergeableCluster
构建invoker
,否则使用默认的FailoverCluster
构建。
Registry registry = registryFactory.getRegistry(url);
这是 AbstractRegistryFactory
中方法,是根据url获取 Registry 过程,Registry
也没有默认的 Adaptive 类,所以将使用默认的 Adaptive 类,而url在 Registry$Adatpive
被换为了 zookeeper
协议, 而后会执行 Registry registry = REGISTRIES.get(key);
从缓存中获取 Regisry
,否则会通过url新建一个。
@Override
public Registry getRegistry(URL url) {
// 重新设置path
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(EXPORT_KEY, REFER_KEY)
.build();
// 由url获取key
String key = url.toServiceStringWithoutResolving();
// Lock the registry access process to ensure a single instance of the registry
LOCK.lock();
try {
// 尝试从缓存中获取
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
// 通过 SPI 或者 IOC 创建create registry by spi/ioc
registry = createRegistry(url);
if (registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
REGISTRIES.put(key, registry);
return registry;
} finally {
// Release the lock
LOCK.unlock();
}
}
以上代码做了如下几件事:
- 将url中path设置为
org.apache.dubbo.registry.RegistryService
- 尝试获取key的缓存,key的格式为
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService
,这也就说明,不同的接口,可以有不同的注册中心。 - 如果无法找到缓存,则执行 子类具体实现的
createRegistry
新建
ZookeeperRegistyFactory
工厂模式的应用,则是通过 createRegistry
新创键一个 ZookeeperRegistry
并返回:
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
可以看下面 ZookeeperRegistry 章节分析。
4. 判断是否 有配置 group
,如果有则使用 MergeableCluster
,否则将使用默认的Cluster$Adaptive
即 FailoverCluster
去调用 子类具体实现的 doRefer
方法
可以看下面 doRefer 小节具体分析
ZookeeperRegistry
那么 这个 ZookeeperRegistry 做了什么事呢?
- ZookeeperRegistry 构造方法:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
// 执行父类构造方法
super(url);
// 判断是否为 0.0.0.0 的host
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
this.root = group;
zkClient = zookeeperTransporter.connect(url);
zkClient.addStateListener(state -> {
if (state == StateListener.RECONNECTED) {
try {
recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
}
- FailbackRegistry 构造方法:
public FailbackRegistry(URL url) {
super(url);
this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);
// since the retry task will not be very much. 128 ticks is enough.
retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
}
- AbstractRegistry 构造方法:
public AbstractRegistry(URL url) {
// 设置url
setUrl(url);
// 尝试从缓存文件中获取配置
// Start file save timer
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
String filename = url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
File file = null;
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
}
this.file = file;
// When starting the subscription center,
// we need to read the local cache file for future Registry fault tolerance processing.
// 尝试从缓存文件中获取配置
loadProperties();
// 告知 机制
notify(url.getBackupUrls());
}
以上列举了 ZookeeperRegistry 的构造方法,以及继承结构中父类 FailbackRegistry
和 AbstractRegistry
的构造方法。
从后往前讲:
- 在 AbstractRegistry中,首先是检查了是否有缓存文件配置,有则读取出来,文件位置在
~/.dubbo
下,名字格式为:url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
例如以下:/home/anla7856/.dubbo/dubbo-registry-dubbo-consumer-127.0.0.1:2181.cache
。 而后则第一次通知了 其他消息监听的去配置下 url 的backupUrl
,即Zookeeper集群的其他配置地址。 -
FailbackRegistry
是ZookeeperRegistry
的父类,里面主要封装和记录了一些失败的通知或者订阅信息,在里面开启了一个 定时器HashedWheelTimer
,用于失败重试,而重试周期如果没有指明,则为5000ms。 - 正题就是
ZookeeperRegistry
中的构造方法,除了调用父类的构造方法,它还负责连接Zookeeper
,即内部使用了 一个Dubbo 封装的ZookeeperClient
和ZookeeperTransporter
用于操作Zookeeper
。由于可能有多种ZookeeperTransporter
类型,所以事实上它也是 SPI 类型。
最后,监听Zookeeper
的状态变更消息,从而执行recover
方法。recover
方法则主要是当需要重连时候,重新尝试连接以及刷新已获取的配置。
当获取完 Registry
后,将其放入 REGISTRIES
中缓存起来,并最终返回该 registry
。
doRefer
上面的 获取 registry
方法 是 仅仅是准备工作,即初始化了 相关的 Registry
、Cluster
。而在RegistryProtocol
各自的doRefer
方法 则会进行具体一些设定,例如Directory
配置,订阅配置,注册监听器等。
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 创建一个 RegistryDirectory
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 设置 Registry 和 protocol
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
// 从新构造订阅的URL
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(subscribeUrl);
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
Invoker invoker = cluster.join(directory);
ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
return invoker;
}
上面贴出了 RegistryProtocol
的 doRefer
方法,有以下几个逻辑点:
- 创建一个
RegistryDirectory
,即服务目录,服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。而后设置Registry
、Protocol
、RegistriesConsumerUrl
。 - 而后,调用
registry.register(directory.getRegisteredConsumerUrl());
去注册 这个 registeredConsumerUrl,默认使用Zookeeper
作为注册中心进行服务注册和管理。由ZookeeperRegistry
->FailbackRegistry
->AbstractRegistry
依次初始化并调用。最终,执行ZookeeperRegistry
的doRegistry
语句在zk上面增加一个字节点:
@Override
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
- 执行
directory.buildRouterChain(subscribeUrl);
初始化 路由链 的初始化,具体路由链将由后面文章介绍。 - 执行
directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
去订阅监听。
具体订阅过程看下一篇详细分析。 -
Invoker invoker = cluster.join(directory);
使用默认的Cluster$Adatpive
,创建一个Invoker 并返回,并且缓存入ProviderConsumerRegTable
中。
看下 Cluster
的SPI 文件:
mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
registryaware=org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster
里面有个 Wrapper 类,所以Cluster 的返回必然是 Wrapper
-> Cluster
模式,具体可以看这篇文章分析:
SPI模式中 Wrapper和 SPI 类组装逻辑。
再看看 MockClusterWrapper
类:
public class MockClusterWrapper implements Cluster {
private Cluster cluster;
public MockClusterWrapper(Cluster cluster) {
this.cluster = cluster;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}
}
里面join 方法仅仅返回 一个 MockClusterInvoker
。
所以从代码中可以推断出 ,最终在 ReferenceConfig
返回必将是一个 MockClusterInvoker
。
由打断点而获取到的invoker 内容如下所示:
而最终再通过 return (T) PROXY_FACTORY.getProxy(invoker);
返回一个代理类。
所以另一方面,当我们写代码时候,直接端点进入,是无法直接进入 type
的 实现类,而是进入MockClusterInvoker
。
那具体订阅过程是如何的呢?代理工厂如何生成代理类呢?
这些问题,且看下回分解,感谢阅读。