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

来源:互联网 发布:手机淘宝6.3.2 旧版本 编辑:程序博客网 时间:2024/05/16 08:19

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

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


我们先来看下,如果要实现一个注册中心,应该怎么做。首先看下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等)来实现。



阅读全文
0 0
原创粉丝点击