dubbo 源码学习笔记 (五) —— 注册中心模块

欢迎访问我的个人博客休息的风

dubbo的注册中心模块,封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory,Registry,RegistryService。以下为整个模块的类图。(图片有点小,请在新标签页打开图片查看)

dubbo 源码学习笔记 (五) —— 注册中心模块

我们先来看下,如果要实现一个注册中心,应该怎么做。首先看下RegistryProtocol,这个类是注册中心的入口。在获取registry时,都是通过RegistryFactory工厂类去获取的。我们想自己实现一个注册中心,就要先实现一个RegistryFactory。

@SPI("dubbo")
public interface RegistryFactory {

    /**
     * 连接注册中心.
     * <p>
     * 连接注册中心需处理契约:<br>
     * 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。<br>
     * 2. 支持URL上的username:password权限认证。<br>
     * 3. 支持backup=10.20.153.10备选注册中心集群地址。<br>
     * 4. 支持file=registry.cache本地磁盘文件缓存。<br>
     * 5. 支持timeout=1000请求超时设置。<br>
     * 6. 支持session=60000会话超时或过期设置。<br>
     *
     * @param url 注册中心地址,不允许为空
     * @return 注册中心引用,总不返回空
     */
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}
在实现RegistryFactory接口时,dubbo提供了AbstractRegistryFactory的抽象类,如果要自已实现注册中心,可以实现RegistryFactory接口,并且继承AbstractRegistryFactory。这个父类获取Registry过程已经定义好,我们只需实现createRegistry抽象方法即可。

public Registry getRegistry(URL url) {
    url = url.setPath(RegistryService.class.getName())
            .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName())
            .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY);
    String key = url.toServiceString();
    // 锁定注册中心获取过程,保证注册中心单一实例
    LOCK.lock();
    try {
        Registry registry = REGISTRIES.get(key);
        if (registry != null) {
            return registry;
        }
        registry = createRegistry(url);
        if (registry == null) {
            throw new IllegalStateException("Can not create registry " + url);
        }
        REGISTRIES.put(key, registry);
        return registry;
    } finally {
        // 释放锁
        LOCK.unlock();
    }
}

protected abstract Registry createRegistry(URL url);

这个抽象方法需要返回一个Registry。Registry继承Node和RegistryService接口。我们来看下RegistryService接口:

public interface RegistryService {

    /**
     * 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
     * <p>
     * 注册需处理契约:<br>
     * 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。<br>
     * 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。<br>
     * 3. 当URL设置了category=routers时,表示分类存储,缺省类别为providers,可按分类部分通知数据。<br>
     * 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。<br>
     * 5. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
     *
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     */
    void register(URL url);

    /**
     * 取消注册.
     * <p>
     * 取消注册需处理契约:<br>
     * 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。<br>
     * 2. 按全URL匹配取消注册。<br>
     *
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     */
    void unregister(URL url);

    /**
     * 订阅符合条件的已注册数据,当有注册数据变更时自动推送.
     * <p>
     * 订阅需处理契约:<br>
     * 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。<br>
     * 2. 当URL设置了category=routers,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。<br>
     * 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0<br>
     * 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*<br>
     * 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。<br>
     * 6. 允许URI相同但参数不同的URL并存,不能覆盖。<br>
     * 7. 必须阻塞订阅过程,等第一次通知完后再返回。<br>
     *
     * @param url      订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */
    void subscribe(URL url, NotifyListener listener);

    /**
     * 取消订阅.
     * <p>
     * 取消订阅需处理契约:<br>
     * 1. 如果没有订阅,直接忽略。<br>
     * 2. 按全URL匹配取消订阅。<br>
     *
     * @param url      订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */
    void unsubscribe(URL url, NotifyListener listener);

    /**
     * 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
     *
     * @param url 查询条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @return 已注册信息列表,可能为空,含义同{@link com.alibaba.dubbo.registry.NotifyListener#notify(List<URL>)}的参数。
     * @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
     */
    List<URL> lookup(URL url);

}
也就是说我们在createRegistry方法返回的Registry至少需要register,unregister,subscribe,unsubscribe,lookup这几个功能。关于实现Registry接口,dubbo也为我们提供了FailbackRegistry抽象类。需要自己实现Registry,可以继承这个抽象类,并实现抽象类里面的模板方法。

// ==== 模板方法 ====

protected abstract void doRegister(URL url);

protected abstract void doUnregister(URL url);

protected abstract void doSubscribe(URL url, NotifyListener listener);

protected abstract void doUnsubscribe(URL url, NotifyListener listener);

到这里,我们知道可以通过继承AbstractRegistryFactory来实现RegistaryFacotry接口;通过继承FailbackRegistry来实现Registry接口。接下来,我们用比较熟悉的zookeeper为例子,来分析注册中心的工作原理,其实大部分的过程也还是在FailbackRegistry这个类中。

首先,先分析FailbackRegistry中retry,register,unregister,subscribe,unsubscribe这几个方法:

// 重试失败的动作
protected void retry() {
    //对失败的注册动作进行重试直至成功
    if (!failedRegistered.isEmpty()) {
        //省略代码。。
                        doRegister(url);
                        failedRegistered.remove(url);
        //省略代码。。
    }
    //对失败的反注册动作进行重试直至成功
    if (!failedUnregistered.isEmpty()) {
        //省略代码。。
                        doUnregister(url);
                        failedUnregistered.remove(url);
        //省略代码。。
    }
    //对失败的订阅动作进行重试直至成功
    if (!failedSubscribed.isEmpty()) {
        //省略代码。。
                            doSubscribe(url, listener);
                            listeners.remove(listener);
        //省略代码。。
    }
    //对失败的反订阅动作进行重试直至成功
    if (!failedUnsubscribed.isEmpty()) {
        //省略代码。。
                            doUnsubscribe(url, listener);
                            listeners.remove(listener);
        //省略代码。。
    }
    //对失败的通知进行重试,直至成功
    if (!failedNotified.isEmpty()) {
        //省略代码。。
                for (Map<NotifyListener, List<URL>> values : failed.values()) {
                    for (Map.Entry<NotifyListener, List<URL>> entry : values.entrySet()) {
                        try {
                            NotifyListener listener = entry.getKey();
                            List<URL> urls = entry.getValue();
                            listener.notify(urls);
                            values.remove(listener);
                        } catch (Throwable t) { // 忽略所有异常,等待下次重试
                            logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                }
            } 
            //省略代码。。
    }
}
retry方法的重试,在构建方法中会创建线程池,定期执行

public FailbackRegistry(URL url) {
    super(url);
    int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
    this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
        public void run() {
            // 检测并连接注册中心
            try {
                retry();
            } catch (Throwable t) { // 防御性容错
                logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
            }
        }
    }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
}
register方法中,会定义好注册的过程,具体注册的动作,由子类实现的doRegistry方法实现
public void register(URL url) {
    if (destroyed.get()){
        return;
    }
    //这里调用父类方法,把url加入registered中
    super.register(url);
    failedRegistered.remove(url);
    failedUnregistered.remove(url);
    try {
        // 向服务器端发送注册请求
        doRegister(url);
    } catch (Exception e) {
        Throwable t = e;

        // 如果开启了启动时检测,则直接抛出异常
        boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                && url.getParameter(Constants.CHECK_KEY, true)
                && !Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
        boolean skipFailback = t instanceof SkipFailbackWrapperException;
        if (check || skipFailback) {
            if (skipFailback) {
                t = t.getCause();
            }
            throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
        } else {
            logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
        }

        // 将失败的注册请求记录到失败列表,定时重试
        failedRegistered.add(url);
    }
}

其他unregister,subscribe,unsubscribe这几个方法跟register方法类似,这里不再展示。在ZookeeperRegistry中,会调用具体的注册动作:

protected 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);
    }
}
至此,注册中心模块的大概组织情况我们也了解了。这里再总结一下。首先RegistryProtocol为注册中心的入口,这里会用RegistryFactory去创建一个Registry。dubbo帮助我们实现了AbstractRegistryFactory去实现RegistryFactory接口来获取Registry的过程;FailbackRegistry去实现Registry接口来定义注册,反注册,订阅,反订阅操作过程,并且会进行定时失败重试。具体的创建注册器及注册,反注册,订阅,反订阅操作,由具体的注册中心(比如zookeeper,Multicast,Dubbo等)来实现。