Previous Article If someone asked you in Dubbo registry works, put this article to give him a general understanding of the central role and Dubbo Registry registration module source code, this article will delve Dubbo ZooKeeper module, to learn how to implement dynamic service discovery .
ps: The following will be abbreviated as ZooKeeper zk.
A, dubbo zk data structure
In sharing the basic concepts ZooKeeper article sermon, ZK inside a hierarchical tree structure, there are various types of nodes. The Dubbo will only create lasting temporary nodes and nodes.
If the service provider for the service interface com.service.FooService
, ZK will be created in the following path created /dubbo/com.service.FooService/providers/providerURL
.
Service route is divided into four, defaults to root Dubbo, you may be provided to change the attribute value group dubbo-registry.
ps: If there is no registry isolation requirements, it should not be modified.
The second layer node serving node full name, such as com.service.FooService
.
The third layer node directory services, such as providers. In addition there are other directory node, respectively, consumers (consumer directory), configurators (configuration directory), routers (routing directory). The following subscription service focused on this layer node.
The fourth node for the specific service node, a node named specific URL string, such as dubbo://2.0.1.13:12345/com.dubbo.example.DemoService?xx=xx
the temporary default node node. dubbo ZK internal configuration example of a tree:
ZK Internal Service Specific examples are as follows:
Two, RegistryFactory achieve
Dubbo registry can specify in the configuration file may be used dubbo.registry.protocol
to specify the specific type of registry may be provided dubbo.registry.address
to specify. Registration center related to implementation will use RegistryFactory
to create a factory class.
RegistryFactory
Interface source code as follows:
@SPI("dubbo")
public interface RegistryFactory {
@Adaptive({"protocol"})
Registry getRegistry(URL url);
}
复制代码
RegistryFactory
Interface method using @Adaptive
annotations, will be used herein Dubbo SPI mechanism automatically generates the code implementing logic. Here will be based on the URL protocol
property, to call and ultimately sub-class.
RegistryFactory
As shown in FIG implementation subclass:
AbstractRegistryFactory
We will implement the interface getRegistry
methods, mainly to complete the lock, and calls the abstract template method createRegistry
to create specific registry implementation class, and cached in memory.
AbstractRegistryFactory#getRegistry
Source as follows:
public Registry getRegistry(URL url) {
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
.removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY)
.build();
String key = url.toServiceStringWithoutResolving();
// 加锁,防止并发
LOCK.lock();
try {
// 先从缓存中取
Registry registry = REGISTRIES.get(key);
if (registry != null) {
return registry;
}
//使用 Dubbo SPI 进制创建
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();
}
}
复制代码
Registry through concrete examples will be created by the factory, where we look at ZookeeperRegistryFactory
the source code:
public class ZookeeperRegistryFactory extends AbstractRegistryFactory {
private ZookeeperTransporter zookeeperTransporter;
/**
* 通过 Dubbo SPI 进制注入
* @param zookeeperTransporter
*/
public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) {
this.zookeeperTransporter = zookeeperTransporter;
}
@Override
public Registry createRegistry(URL url) {
return new ZookeeperRegistry(url, zookeeperTransporter);
}
}
复制代码
ps: Dubbo SPI IOC mechanism further has properties, where ZookeeperTransporter
implantation may refer to: Dubbo extension point loading
Three, zk module source code parsing
Finished registry instance creation process, the following in-depth ZookeeperRegistry
implementation source code.
ZookeeperRegistry
Inherit FailbackRegistry
the abstract class, so it needs to achieve its parent class abstract template method, mainly to understand the following doRegister
with doSubscribe
source code.
3.1 doRegister
Service providers need to be registered with the service registry, the purpose of registration is to allow consumers to perceive the existence of services, to initiate a remote call, on the other hand also allows the service management center aware of the new on-line service provider. zk service module registration code is simple, create a node in the registry directly zk client.
ZookeeperRegistry#doRegister
Implementation source code as follows:
public void doRegister(URL url) {
try {
zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
} catch (Throwable e) {
throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
复制代码
zkClient.create
Method requires two arguments.
void create(String path, boolean ephemeral);
复制代码
The first parameter is the path node, will pass toUrlPath
the URL converted to Example ZK path format, the conversion results are as follows:
## 转化前 URL 如下:
dubbo://10.20.82.31:12345/com.dubbo.example.DemoService
## 调用 `toUrlPath` 转换之后
/dubbo/com.dubbo.example.DemoService/providers/dubbo%3A%2F%2F10.20.82.31%3A12345%2Fcom.dubbo.example.DemoService
复制代码
The second parameter is mainly determined ZK node type URL examples taken from the main object dynamic
parameter value, if not, by default true
, that is, the default will create a temporary node.
zkClient.create
The method will be in the recursive calls, first of all if there is a parent node will be created does not exist until the last node out of the recursive method.
public void create(String path, boolean ephemeral) {
// 创建永久节点之前需要判断是否已存在
if (!ephemeral) {
if (checkExists(path)) {
return;
}
}
// 判断是否存在父节点
int i = path.lastIndexOf('/');
if (i > 0) {
// 递归创建父节点
create(path.substring(0, i), false);
}
if (ephemeral) {
// 创建临时节点
createEphemeral(path);
} else {
// 创建永久节点
createPersistent(path);
}
}
复制代码
Finally, createEphemeral
the createPersistent
actual operation will create a node ZK to the client class, here is relatively simple, free to refer to the source code.
ps: dubbo in 2.6.1 zk from the client to use Curator default, previous versions use zkclient. dubbo 2.7.1 start removing zkclient realize, that can only be used Curator.
3.2 Why dubbo service provider node using the temporary node zk
zk zk temporary node will be in the client off automatically deleted. dubbo normal offline service provider, it will automatically delete zk service node.
If abnormal service outages, zk service node can not be deleted properly, which led to the failure of service has always existed on ZK, consumers will call the failed node, resulting in consumers being given. By zk temporary node features to the server zk initiative to remove the failed node, and thus fail the service offline.
Four, doSubscribe: the principle of dynamic discovery of service
4.1 subscribe to the basic principles
Subscription services usually pull and push two ways. a timing pull mode requires the client pull configuration to the registry, the push registry active mode using push data to the client.
dubbo zk registry is using the event notification and the client pull method. The first subscription service time will pull the full amount of the corresponding data in the directory, and then sign up for a subscription node watcher. Once any data changes the directory node, zk will notify the client by watcher. The client receiving the notice, the full amount will be re pulling data in this directory, and re-register watcher. Using this model, dubbo service can be done on the dynamic discovery of services.
4.2 source code parsing
Finished the basic principles of subscription, followed by in-depth source.
doSubscribe
Method requires two arguments, a URL for the instance, and the other is NotifyListener
to change the listener events. The method will be divided into two internal logical interface type according to the URL, the full amount of the subscription service classes with some subscription services.
doSubscribe
Method overall logic Source:
public void doSubscribe(final URL url, final NotifyListener listener) {
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
// 全量订阅逻辑
} else {
// 部分类别订阅逻辑
}
}
复制代码
Service Management Center (dubbo-admin), to subscribe to the full amount of the service interface to the perceived state of each service, so the service will be set up before subscription * to handle all service.
Service consumer or service provider subscription service will go some categories, let the consumer perspective, in-depth follow-up source.
Just at the beginning of the article preached zk directory node there are four types, according to the URL here will be based on category
value, we decided to subscribe to the node path.
Service provider the URL category
value defaults configurators
, and consumers the URL category
value defaults providers,configurators,routers
. If the category
category is *
, four categories will be subscription path, otherwise it will subscribe only to providers
the type of path.
toCategoriesPath
Source as follows:
private String[] toCategoriesPath(URL url) {
String[] categories;
// 如果类别为 *,订阅四种类型的全量数据
if (Constants.ANY_VALUE.equals(url.getParameter(Constants.CATEGORY_KEY))) {
categories = new String[]{Constants.PROVIDERS_CATEGORY, Constants.CONSUMERS_CATEGORY,
Constants.ROUTERS_CATEGORY, Constants.CONFIGURATORS_CATEGORY};
} else {
categories = url.getParameter(Constants.CATEGORY_KEY, new String[]{Constants.DEFAULT_CATEGORY});
}
// 返回路径数组
String[] paths = new String[categories.length];
for (int i = 0; i < categories.length; i++) {
paths[i] = toServicePath(url) + Constants.PATH_SEPARATOR + categories[i];
}
return paths;
}
复制代码
Subsequently the circulation path array, the loop will cache node listener, to improve performance.
// 循环路径数组
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
// listeners 缓存为空,创建缓存
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
// zkListener 缓存为空则创建缓存
if (zkListener == null) {
listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
zkListener = listeners.get(listener);
}
// 创建订阅节点
zkClient.create(path, false);
// 使用 ZK 客户端订阅节点
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
// 存储全量需要通知的 URL
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
// 回调 NotifyListener
notify(url, listener, urls);
复制代码
The final will be used CuratorClient.getChildren().usingWatcher(listener).forPath(path)
in the registration ZK node watcher, and get all the child nodes of the data directory node.
Here watcher Curator use interfaces CuratorWatcher
, will take place once the node ZK change would callback CuratorWatcher#process
method.
CuratorWatcher#process
Source method as follows:
public void process(WatchedEvent event) throws Exception {
if (childListener != null) {
String path = event.getPath() == null ? "" : event.getPath();
childListener.childChanged(path,
// 重新设置 watcher,并获取节点下所有子节点
StringUtils.isNotEmpty(path)
? client.getChildren().usingWatcher(this).forPath(path)
: Collections.<String>emptyList());
}
}
复制代码
Consumers subscribe to the timing diagram as follows:
4.3 listener diagram
Subscribe method, we encountered a number of listener
classes, just beginning to understand the time may be a bit messy. Diagram refer to the following reason clearly the relationship between them.
listener
The relationship is as follows:
Relationship shown in FIG callback:
4.4 ZK subscription module problems
ZK first subscription will receive all child node node directory, any subsequent child nodes changes will decimal callback notification by watcher. Callback notification will once again pull the whole amount of all child nodes under the node directory. In this way the whole amount will have to pull a limitation, when more service nodes will cause great pressure on the network.
Dubbo after the introduction of version 2.7 Metadata centers around the problem, Details can be found, Ali Detailed technical experts Dubbo practice, evolution and future planning .
One solution cited herein below:
to sum up
This paper describes the data structure dubbo zk, followed by in-depth research ZookeeperRegistry
related to implementation source code. By understanding the principles of service registration and subscription to understand Dubbo dynamic service discovery implementation.