I do not know how to implement dynamic discovery of services? Take a look at how Dubbo is done

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=xxthe temporary default node node. dubbo ZK internal configuration example of a tree:

image.png

ZK Internal Service Specific examples are as follows:

Snipaste20190811170204.png

Two, RegistryFactory achieve

Dubbo registry can specify in the configuration file may be used dubbo.registry.protocolto specify the specific type of registry may be provided dubbo.registry.addressto specify. Registration center related to implementation will use RegistryFactoryto create a factory class.

RegistryFactory Interface source code as follows:

@SPI("dubbo")
public interface RegistryFactory {
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);
}
复制代码

RegistryFactoryInterface method using @Adaptiveannotations, will be used herein Dubbo SPI mechanism automatically generates the code implementing logic. Here will be based on the URL protocolproperty, to call and ultimately sub-class.

RegistryFactory As shown in FIG implementation subclass:

RegistryFactory.png

AbstractRegistryFactoryWe will implement the interface getRegistrymethods, mainly to complete the lock, and calls the abstract template method createRegistryto 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 ZookeeperRegistryFactorythe 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 ZookeeperTransporterimplantation may refer to: Dubbo extension point loading

Three, zk module source code parsing

Finished registry instance creation process, the following in-depth ZookeeperRegistryimplementation source code.

ZookeeperRegistryInherit FailbackRegistrythe abstract class, so it needs to achieve its parent class abstract template method, mainly to understand the following doRegisterwith doSubscribesource 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 toUrlPaththe 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 dynamicparameter 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, createEphemeralthe createPersistentactual 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.

doSubscribeMethod requires two arguments, a URL for the instance, and the other is NotifyListenerto 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 categoryvalue, we decided to subscribe to the node path.

Service provider the URL categoryvalue defaults configurators, and consumers the URL categoryvalue defaults providers,configurators,routers. If the categorycategory is *, four categories will be subscription path, otherwise it will subscribe only to providersthe 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#processmethod.

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:

dubbo subscription 1.png

4.3 listener diagram

Subscribe method, we encountered a number of listenerclasses, 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:

dubbodoSubscribelistener diagram 1.png

Relationship shown in FIG callback:

listener callback relationship 3.png

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:

e2a0160ed30947eb93f321877b1005cf.png

ef9d4e895a454669ab3a5d878c76b5ab.png

to sum up

This paper describes the data structure dubbo zk, followed by in-depth research ZookeeperRegistryrelated to implementation source code. By understanding the principles of service registration and subscription to understand Dubbo dynamic service discovery implementation.

Other platforms .png

Guess you like

Origin juejin.im/post/5d8f2ab46fb9a04e4126a3af