Dubbo 注册中心源码

127 阅读8分钟

工作流程

注册中心工作流程.png 服务容器负责启动,加载,运行服务提供者。

  1. 服务提供者在启动时,向注册中心注册自己提供的服务。
  2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

整体架构

image.png

注册中心实现类都是FailbackRegistery子类,

各类作用

Node接口

可视为客户端视角的注册中心节点

public interface Node {

    /**
     * get url.
     *
     * @return url.
     */
    URL getUrl();

    /**
     * is available.
     *
     * @return available.
     */
    boolean isAvailable();

    /**
     * destroy.
     */
    void destroy();

}

RegistryService接口

抽象出注册方法,注册中心具体实现类需要重写该接口中方法

public interface RegistryService {

    /*
    注册数据,如:提供者服务、使用者地址、路由规则、覆盖规则等数据。
    为支持合同,需要进行注册:
         1. 当URL设置check=false参数时。当注册失败时,异常不会抛出,并在后台重试。否则,将抛出异常。
         2. URL设置dynamic=false参数时,需要持久化存储,否则,当注册人异常退出时,需要自动删除。
         3.当URL设置category=路由器时,表示分类存储,默认类别是提供商,数据可以通过分类部分通知。
         4. 注册表重启时,网络抖动,数据不能丢失,包括自动删除虚线中的数据。
         5. 允许URL相同但参数不同的URL共存,它们不能相互覆盖。
    Params: url -注册信息,不允许为空,例如:dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
 */
    void register(URL url);

    /*
    为支持本合同,需要注销:
    1. 如果它是dynamic=false的持久存储数据,则找不到注册数据,则抛出IllegalStateException,否则将忽略它。
    2. Unregister according to the full url match.
    Params: url -注册信息,不允许为空,例如:dubbo://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
    */
    void unregister(URL url);


    /*
    订阅符合条件的注册数据,并在注册数据更改时自动推送。
    订阅需要支持契约:
    1. 当URL设置check=false参数时。当注册失败时,异常不会抛出,并在后台重试
    2. 当URL设置category=路由器时,只通知指定的分类数据。多个分类由逗号分隔,并允许星号匹配,这表示订阅了所有分类数据。
    3.允许接口、组、版本和分类器作为条件查询,例如:interface=org.apache.dubbo.foo.BarService&version=1.0.0
    4. 查询条件允许匹配星号,订阅所有接口的所有报文的所有版本,例如:interface=*&group=*&version=*&classifier=*
    5。当注册表重新启动并且网络抖动时,有必要自动恢复订阅请求。
    6. 允许URL相同但参数不同的URL共存,它们不能相互覆盖。
    7. 当第一个通知完成并返回时,必须阻塞订阅过程。
    Params: url -订阅条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
            listener -更改事件的监听器,不允许为空
    */
    void subscribe(URL url, NotifyListener listener);

    /*
    为了支持合同,需要:
    1.如果不订阅,直接忽略它。
    2. 按完全URL匹配取消订阅。
    Params:
      url -订阅条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
      listener -更改事件的监听器,不允许为空
    */
    void unsubscribe(URL url, NotifyListener listener);

    /*
    查询符合条件的注册数据。对应于订阅的推送模式,这是拉取模式,只返回一个结果
    Params: 
      url—查询条件,不允许为空,例如consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
    返回:
      注册信息列表,可以为空,含义与NotifyListener.notify(list)参数相同。
    */
    List<URL> lookup(URL url);

}

Registry 接口

dubbo2.7中没有定义任何方法,dubbo3.2中定义了四个抽象方法,不是重要的核心方法

AbstractRegistry抽象类

注册类的抽象实现,对上面三个接口中部分方法提供了简单实现,并在内部缓存注册信息,加载存储在磁盘的缓存数据

public abstract class AbstractRegistry implements Registry {

    // URL地址分隔符,用于文件缓存、服务提供者URL分隔
    private static final char URL_SEPARATOR = ' ';
    // URL地址分离的正则表达式,用于解析文件缓存中的服务提供者URL列表
    private static final String URL_SPLIT = "\s+";
    // 重试将属性保存到本地缓存文件的最大次数
    private static final int MAX_RETRY_TIMES_SAVE_PROPERTIES = 3;
    // 缓存信息
    private final Properties properties = new Properties();
    // 是否同步保存缓存信息到本地磁盘
    private final boolean syncSaveFile;
    //当前缓存版本号
    private final AtomicLong lastCacheChanged = new AtomicLong();
    private final AtomicInteger savePropertiesRetryTimes = new AtomicInteger();
    //已经注册的服务
    private final Set<URL> registered = new ConcurrentHashSet<>();
    //订阅服务的监听器列表
    private final ConcurrentMap<URL, Set<NotifyListener>> subscribed = new ConcurrentHashMap<>();
    //订阅服务的提供者列表
    private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
    //注册中心地址
    private URL registryUrl;
    //本地磁盘缓存文件
    private File file;
}

notified

内存中的服务缓存对象,notified的结构是CurrentHashMap内嵌一个Map,外层Map的Key为消费者的URL(包含组、方法、版本等),内层key是分类,包含providers、consumers、routes、configurations四种,value对应服务列表,对于没有服务提供者提供服务的URL,他会以特殊的empty://前缀开头

properties

用于本地磁盘缓存,HashTable子类,Properties保存了所有服务提供者的URL,使用{group}/{interfaceName}:{version}作为key,URL列表作为value。当value包含多个值时,使用空格隔开。
还有一个特殊的key.registies保存所有的注册中心的地址。
如果应用中心无法连接,则使用properties中的值

FailbackRegistry抽象类

public abstract class FailbackRegistry extends AbstractRegistry {

    /*  retry task map */

    private final ConcurrentMap<URL, FailedRegisteredTask> failedRegistered = new ConcurrentHashMap<URL, FailedRegisteredTask>();

    private final ConcurrentMap<URL, FailedUnregisteredTask> failedUnregistered = new ConcurrentHashMap<URL, FailedUnregisteredTask>();

    private final ConcurrentMap<Holder, FailedSubscribedTask> failedSubscribed = new ConcurrentHashMap<Holder, FailedSubscribedTask>();

    private final ConcurrentMap<Holder, FailedUnsubscribedTask> failedUnsubscribed = new ConcurrentHashMap<Holder, FailedUnsubscribedTask>();

    private final ConcurrentMap<Holder, FailedNotifiedTask> failedNotified = new ConcurrentHashMap<Holder, FailedNotifiedTask>();
    
    // ==== Template method ====

    public abstract void doRegister(URL url);

    public abstract void doUnregister(URL url);

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

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


}

注册类的抽象实现,对上面三个接口中部分方法提供了简单实现
定义了自动重试能力的注册中心服务的模板实现,提供给四个抽象方法用于定义具体实现

RegistryFactory接口

工厂模式

@SPI("dubbo")
public interface RegistryFactory {

    /*
    连接到注册表
    连接注册表需要支持契约:
    1. 当设置check=false时,不检查连接,否则在断开连接
    2时抛出异常。支持URL上的用户名和密码权限认证。
    3.支持备份=10.20.153.10候选注册表集群地址。
    4. 支持文件=注册表。缓存本地磁盘文件缓存。
    5. 支持timeout=1000请求超时设置。
    6. 支持session=60000会话超时或过期设置。
    参数:
        url -注册表地址,不允许为空
    返回:
        注册表引用,从不返回空值
    */
    @Adaptive({"protocol"})
    Registry getRegistry(URL url);

}

NotifyListener接口

监听注册中心变更,用于推送模式,设定上与拉模式RegistryService.lookup(URL)对应

public interface NotifyListener {

    /*
     当收到服务变更通知时触发。
     Notify需要支持契约:
     1. 始终在服务接口和数据类型的维度上通知。也就是说,不会通知属于一个服务的同类型数据的一部分。用户不需要比较前一个通知的结果。
     2. 订阅时的第一个通知必须是服务所有类型数据的完整通知。
     3.在更改时,允许分别通知不同类型的数据,例如:提供者、消费者、路由器、覆盖。它只允许通知这些类型中的一种,但这种类型的数据必须是完整的,而不是增量的。
     4. 如果一个数据类型
     Params:
       urls –列表中注册的信息,始终不是空的。其含义与org.apache.dubbo.registry.RegistryService.lookup(URL)的返回值相同。
    */
    void notify(List<URL> urls);

}

服务

注册

流程概括

  1. 将当前服务的URL添加到已注册集合中,将当前URL的历史注册、取消注册失败重试任务删除
  2. 调用子类中实现的实际注册方法
  3. 如果开启了失败检测,取消注册失败则报错
    否则则为当前URL添加失败重试任务,定期重试

取消注册

流程概括

  1. 将当前URL从已注册集合中移除,将当前URL的历史注册、取消注册失败充实任务删除
  2. 调用子类中实现的实际取消注册方法
  3. 如果开启了失败检测,注册失败则报错
    否则则为当前URL添加失败重试任务,定期重试

服务订阅(推送模式)

订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉取配置,另一种是注册中心主动推送数据给客户端。目前dubbo采用的是第一次启动拉取方式,后续接收事件重新拉取数据。
上述两种方式需要在子类中实现,dubbo中提供的抽象方法只负责记录和重试。

流程概括

  1. ConcurrentMap<URL, Set<NotifyListener>> subscribed为当前服务添加一个监听器
  2. 将当前服务从订阅失败、取消订阅失败、notify失败集合中移除
  3. 调用子类中实现的实际订阅方法,拉取相关url列表,调用notify方法,缓存结果,结束
  4. 如果失败,使用 url中构造{group}/{interfaceName}:{version}格式的key,从磁盘缓存中拿到提供者URL列表
  5. 如果从磁盘中拿到的URL列表为空,且开启了检测,报错
  6. 如果不为空,对URL进行分类,分类notify,缓存结果

取消订阅

流程概括

  1. 将URL对应的Listener集合清空
  2. 将当前服务从订阅失败、取消订阅失败、notify失败集合中移除
  3. 调用子类实现的方法
  4. 调用失败,如果开启了检测,报错
    将失败的注册请求记录到失败列表中,定期重试

缓存

加载

保存