高性能rpc框架-Dubbo注册中心

Dubbo微服务体系中,注册中心是核心组件之一,dubbo通过注册中心实现服务注册与发现。

注册中心有哪些作用?

  • 动态加入: 服务提供者通过注册中心动态的把自己暴露给其他服务消费者,无需服务提供者逐个更新配置文件。
  • 动态发现: 动态感知新的配置、路由规则、新服务不需要重启服务使之生效。
  • 动态调整: 动态调整参数,自动同步到所有相关的服务节点。
  • 统一配置: 避免了本地配置导致每个服务的配置不一致。

Dubbo 2.6.x版本中主要包含四种注册中心: Zookerper、Redis、Simple、Multicast。
Dubbo 2.7.7最新版本中注册中心增加了:Sofa、Nacos、Eureka、etcd3、Consul。

  • Eureka: 是SpringCloud Netfilx中的重要组件,主要作用是做服务注册和发现,但是现在已经闭源(2.0版本以后)。
  • Nacos: 是一个更易于构建原生态应用的动态服务发现,配置管理和服务管理平台,它是Spring Cloud Alibaba组件之一,负责服务注册与发现和服务配置,可以认为nacos=enueka+config。
  • Consul: 是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册,服务发现和配置管理的功能。
  • Sofa: 蚂蚁金服开源的具有承载海量服务注册和订阅能力的、高可用的服务注册中心。

Dubbo 2.6.x官方推荐使用的注册中心是Zookerper,而Nacos现在使用的也越来越多。

Nacos和Zookerper有什么不同?

  • CAP理论

    一致性(Consistency) :所有节点在同一时间具有相同的数据
    可用性(Availability) :保证每个请求不管成功或者失败都有响应
    分区容忍性(Partition tolerance) :系统中任意信息的丢失或失败不会影响系统的继续运作

  • Zookerper

    • CAP理论选择CP强一致性,如果Master节点挂了,zookeeper集群需要重新选举,这个是时候不能提供服务的。
    • 健康检查支持Keep Alive。
  • Nacos

    • Nacos同时支持AP和CP两种模式,具体选择根据服务注册选择临时和永久来决定走AP模式还是CP模式。
    • Nacos=配置管理+注册中心
    • Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。

注册中心工作流程
高性能rpc框架-Dubbo注册中心_第1张图片

  • 服务提供者启动时,会向注册中心写入自己的元信息,同时会订阅配置元数据信息 。
  • 消费者启动时,向注册中心写入自己的元信息,并订阅服务提供者、路由和配置元信息。
  • 服务治理中心(dubbo-admin)启动时,会订阅所有消费者、服务提供者、路由和配置元数据信息。
  • 服务提供者离开或者有新服务加入,注册中心服务提供者目录会发生变化,变化信息会动态通知消费者,服务治理中心。
  • 消费者发起服务调用时,会异步调用,统计信息等上报监控中心(dubbo-admin-simple)
    Zookeeper原理
    Zookeper是树形结构的注册中心,每个节点的类型分为持久节点、持久顺序节点、临时节点、临时顺序节点。

    1.持久节点:服务注册后保证节点不丢失,注册中心重启也会存在。
    2.持久顺序节点:在持久节点特性基础上增加节点先后顺序的能力。
    3.临时节点:服务注册后连接丢失或者Session超时,注册的节点会自动被移除。
    4.临时顺序节点:在临时节点基础上增加了节点的先后顺序能力。

Dubbo使用Zookerper注册中心,只是用了持久节点和临时节点,对创建的顺序并没有要求。

 + /dubbo
 + --service
        +-- providers
        +-- consumers
        +-- routers
        +-- configurators

Dubbo在启动时会创建4个目录,在providers和consumers目录中分别存储服务提供方,消费方元数据信息,主要包括IP、端口、权重和用户名等数据,routers用于消费路由策略,URL元数据信息,configurators服务动态配置元数据信息。

  • Zookeeper发布实现

    服务提供提供者注册是为了让消费者感知服务的存在,从而发起远程调用,也让服务治理中心感知有新的服务提供者上线,Zookeeper发布就是调用Zookeeper的客户端在注册中心创建一个目录,取消发布只要把对应的路径删除。

  • Zookeeper订阅实现
    订阅通常有pull和push两种形式:

    pull 客户端定时轮询注册中心拉取配信息。
    push:注册中心主动推送数据给客户端。

    Dubbo采用第一次启动拉取方式,后续接收接收事件重新拉取(“事件通知” + “客户端拉取”),第一次连接上注册中心,获取对应目录下面的全量数据,并在订阅的节点上注册一个watcher,客户端与注册中心之间保持长连接,后续节点有任何数据变化,注册中心会根据watcher的回调通知客户端(事件通知),客户端接收到通知,会把对应节点的全量数据拉取过来,全量拉取存在一个局限,当节点数过多时会对网络造成很大的压力。

  • 缓存机制
    缓存就是用空间换时间,如果每次调用都要先从注册中心获取一次可调用的服务列表,这样注册中心会承受巨大的流量压力,同时额外的网络请求也会让系统性能下降,Dubbo实现了通用的缓存机制。
    dubbo-register-api模块中 AbstractRegistry定义:

     private final Properties properties = new Properties();
     private File file;
     private final ConcurrentMap<URL, Map<String, List<URL>>> notified = new ConcurrentHashMap<>();
    

    消费者或服务治理中心获取注册中心会先本地缓存一份,内存中会有一份保存在Properties 对象里,磁盘也会持久化一份,通过file对象引用。
    内存中的notified 是一个Map里面有嵌套一个Map,外层Map的key是消费者的URL,内存Map的key是分类,包含providers、consumers、routers、configurators信息,value则是对应的服务列表,对于没有服务提供者的服务的RUL,以特殊的empty://前缀开头。

    • 缓存加载
      服务初始化,AbstractRegistry构造方法会调用loadProperties(),从本地磁盘文件中把持久化的注册数据读到Properties对象里面,并加载到内存:
      private void loadProperties() {
           if (file != null && file.exists()) {
                InputStream in = null;
                try {
                   // 读取磁盘上的文件
                    in = new FileInputStream(file);
                    properties.load(in);
                    if (logger.isInfoEnabled()) {
                         ogger.info("Load registry cache file " + file + ", data: " + properties);
                    }
                } catch (Throwable e) {
                    logger.warn("Failed to load registry cache file " + file, e);
                } finally {
                       ........
                   }
              }
          }
      
      Properties对象保存了所有服务提供者的URL,使用URL#serviceKey作为key,提供服务列表、路由规则、配置规则列表,当vlaue是列表存在多个时,使用空格隔开,还有一个特殊的key.register保存所有注册中心的地址,如果应用在重启过程中,无法连接注册中心或者宕机,Dubbo会自动通过本地缓存加载Invokers。
    • 缓存保存于更新
      缓存保存有同步和异步两种方式,异步会使用线程保存,如果线程池在执行过程中出现异常,则会再次调用线程池进行重试。
      if (syncSaveFile) {
          // 同步保存
          doSaveProperties(version);
      } else {
          // 异步保存,放入线程池,会传入一个AtomicLong、version版本号,保证数据是最新的
          registryCacheExecutor.execute(new SaveProperties(version));
      }
      
      AbstractRegistry#notify(List)实现更新内存缓存和更新文件缓存,当客户端第一订阅全量数据或者后续由于订阅得到的数据,都会走该逻辑进行更新。
  • 重试机制

    FailbackRegistry继承了AbstractRegistry,并在该基础上增加了重试机制作为抽象能力。
    需要重试的Map集合:

    /*  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>();
    

    FailedNotifiedTask、FailedRegisteredTask、FailedSubscribedTask、FailedUnregisteredTask、FailedUnsubscribedTask重试都继承抽象的AbstractRetryTask,AbstractRetryTask定时任务线程调用doRetry(url, registry, timeout),会走到对应失败重试的doRetry(URL url, FailbackRegistry registry, Timeout timeout)方法进行重试。
    同时FailbackRegistry提供了一些模板方法:

    // ==== 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);
    

    FailbackRegistry实现了unsubscribe、subscribe、unregister、register等通用的方法,里面调用了未实现的模板通用方法,该方法会有子类去实现,通过方法会调用这些模板方法,如果捕获到异常,则会把URL添加到对应的重试Map中,以供定时任务去重试。

你可能感兴趣的:(Dubbo,zookeeper,分布式,rpc,java)