一文深入理解Apache Dubbo,dubbo注册中心:订阅和发布,快来学

订阅/发布

订阅/发布是整个注册中心的核心功能之一。在传统应用系统中,我们通常会把配置信息写

入一个配置文件,当配置需要变更时会修改配置文件,再通过手动触发内存中的配置重新加载,

如重启服务等。在集群规模较小的场景下,这种方式也能方便地进行运维。当服务节点数量不

断上升的时候,这种管理方式的弊端就会凸显出来。

如果我们使用了注册中心,那么上述的问题就会迎刃而解。当一个已有服务提供者节点下

线,或者一个新的服务提供者节点加入微服务环境时,订阅对应接口的消费者和服务治理中心

都能及时收到注册中心的通知,并更新本地的配置信息。如此一来,后续的服务调用就能避免

调用已经下线的节点,或者能调用到新的节点。整个过程都是自动完成的,不需要人工参与。

Dubbo在上层抽象了这样一个工作流程,但可以有不同的实现。本章主要讲解ZooKeeper

和Redis的实现方式。

ZooKeeper 的实现

1.发布的实现

服务提供者和消费者都需要把自己注册到注册中心。服务提供者的注册是为了让消费者感知服务的存在,从而发起远程调用;也让服务治理中心感知有新的服务提供者上线。消费

者的发布是为了让服务治理中心可以发现自己。ZooKeeper发布代码非常简单,只是调用了

ZooKeeper的客户端库在注册中心上创建一个目录,如代码清单3-2所示。

代码清单3-2 zkClient创建目录源码

zkClient.create(toUrlPath(url)

url.getParameter(Constants.DYNAMIJKEY, true));

取消发布也很简单,只是把ZooKeeper注册中心上对应的路径删除,如代码清单3-3

所示。

代码清单3-3 zkClient删除路径源码

zkClient.delete(toUrlPath(url));

2.订阅的实现

订阅通常有pull和push两种方式,一种是客户端定时轮询注册中心拉取配置,另一种是注

册中心主动推送数据给客户端。这两种方式各有利弊,目前Dubbo采用的是第一次启动拉取方

式,后续接收事件重新拉取数据。

在服务暴露时,服务端会订阅configurators用于监听动态配置,在消费端启动时,消费

端会订阅providers、routers和configuratops这三个目录,分别对应服务提供者、路由和动

态配置变更通知。

Dubbo中有哪些ZooKeeper客户端实现?

无论服务提供者还是消费者,或者是服务治理中心,任何一个节点连接到ZooKeeper注册中心

都需要使用一个客户端,Dubbo在dubbo-remoting-zookeeper模块中实现了 ZooKeeper客户端

的统一封装,定义了统一的Client API,并用两种不同的ZooKeeper开源客户端库实现了这个

接口:

Apache Curator;

zkCliento

用户可以在<dubbo: registry>的client属性中设置curator、zkclient来使用不同的客户端实现

库,如果不设置则默认使用Curator作为实现。

ZooKeeper注册中心采用的是“事件通知” + “客户端拉取”的方式,客户端在第一次连接

上注册中心时,会获取对应目录下全量的数据。并在订阅的节点上注册一个watcher,客户端与

注册中心之间保持TCP长连接,后续每个节点有任何数据变化的时候,注册中心会根据watcher的回调主动通知客户端(事件通知),客户端接到通知后,会把对应节点下的全量数据都拉取过

来(客户端拉取),这一点在NotifyListener#notify List<URL> urls 接口上就有约束的注释

说明。全量拉取有一个局限,当微服务节点较多时会对网络造成很大的压力。

ZooKeeper的每个节点都有一个版本号,当某个节点的数据发生变化(即事务操作)时,

该节点对应的版本号就会发生变化,并触发watcher事件,推送数据给订阅方。版本号强调的

是变更次数,即使该节点的值没有变化,只要有更新操作,依然会使版本号变化。

什么操作会被认为是事务操作?

客户端任何新增、删除、修改、会话创建和失效操作,都会被认为是事物操作,会由ZooKeeper

集群中的leader执行。即使客户端连接的是非leader节点,请求也会被转发给leader执行,以

此来保证所有事物操作的全局时序性。由于每个节点都有一个版本号,因此可以通过CAS操作

比较版本号来保证该节点数据操作的原子性。

客户端第一次连上注册中心,订阅时会获取全量的数据,后续则通过监听器事件进行更新。

服务治理中心会处理所有service层的订阅,service被设置成特殊值*。此外,服务治理中心除

了订阅当前节点,还会订阅这个节点下的所有子节点,核心代码来自ZookeeperRegistry,如

代码清单3-4所示。

zkListener 为空,说

明是第一次,新建一个

listener

代码清单3-4 ZooKeeper全量订阅服务

从代码清单3-4可以得知,此处主要支持Dubbo服务治理平台(dubbo-admin),平台在启动时会订阅全量接口,它会感知每个服务的状态。

接下来,我们看一下普通消费者的订阅逻辑。首先根据URL的类别得到一组需要订阅的路径。如果类别是*,则会订阅四种类型的路径(providers、routers、consumers> configurators),否则只订阅providers路径,如代码清单3-5所示。

注意,此处会根据URL中的category属性值获取具体的类别:providers、routers、consumers> configurators,然后拉取直接子节点的数据进行通知(notify)。如果是providers类别的数据,则订阅方会更新本地Directory管理的Invoker服务列表;如果是routers分类,则订阅方会更新本地路由规则列表;如果是configuators类别,则订阅方会更新或覆盖本地动态参数列表。

Redis 的实现

1 .总体流程

使用Redis作为注册中心,其订阅发布实现方式与ZooKeeper不同。我们在Redis注册中心的数据结构中已经了解到,Redis订阅发布使用的是过期机制和publish/subscribe通道。服务提供者发布服务,首先会在Redis中创建一个key,然后在通道中发布一条register事件消息。

但服务的key写入Redis后,发布者需要周期性地刷新key过期时间,在RedisRegistry构造方法中会启动一个expireExecutor定时调度线程池,不断调用deferExpired()方法去延续key的超时时间。如果服务提供者服务宕机,没有续期,则key会因为超时而被Redis删除,服务也就会被认定为下线,如代码清单3.6所示。

订阅方首次连接上注册中心,会获取全量数据并缓存在本地内存中。后续的服务列表变化则通过publish/subscribe通道广播,当有服务提供者主动下线的时候,会在通道中广播一条unregister事件消息,订阅方收到后则从注册中心拉取数据,更新本地缓存的服务列表。新服务提供者上线也是通过通道事件触发更新的。

但是,Redis的key超时是不会有动态消息推送的,如果服务提供者宕机而不是主动下线,则造成没有广播unregister事件消息,订阅方是如何知道服务的发布方已经下线了呢?另外,Redis的publish/subscribe通道并不是消息可靠的,如果Dubbo注册中心使用了 failover的集群容错模式,并且消费者订阅了从节点,但是主节点并没有完成数据同步给从节点就宕机,后续订阅方要如何知道服务发布方已经下线呢?

如果使用Redis作为服务注册中心,会依赖于服务治理中心。如果服务治理中心定时调度,则还会触发清理逻辑:获取Redis上所有的key并进行遍历,如果发现key已经超时,则删除Redis上对应的key。清除完后,还会在通道中发起对应key的unregister事件,其他消费者监听到取消注册事件后会删除本地对应服务的数据,从而保证数据的最终一致,如代码清单3.7所示。

由上面的机制可以得出整个Redis注册中心的工作流程,如图3-4所示。

Redis客户端初始化的时候,需要先初始化Redis的连接池jedisPools,此时如果配置注册中心的集群模式为<dubbo:registry cluster=,,replicate"/>,则服务提供者在发布服务的时候,需要同时向Redis集群中所有的节点都写入,是多写的方式。但读取还是从一个节点中读取。

在这种模式下,Redis集群可以不配置数据同步,一致性由客户端的多写来保证。

如果设置为failover或不设置,则只会读取和写入任意一个Redis节点,失败的话再尝试下一个Redis节点。这种模式需要Redis自行配置数据同步。

另外,在初始化阶段,还会初始化一个定时调度线程池expireExecutor,它主要的任务是延长key的过期时间和删除过期的keyo线程池调度的时间间隔是超时时间的一半。

2.发布的实现

服务提供者和消费者都会使用注册功能,Redis注册部分的关键源码如代码清单3-8所示。

3.订阅的实现

服务消费者、服务提供者和服务治理中心都会使用注册中心的订阅功能。在订阅时,如果是首次订阅,则会先创建一个Notifier内部类,这是一个线程类,在启动时会异步进行通道的订阅。在启动Notifier线程的同时,主线程会继续往下执行,全量拉一次注册中心上所有的服务信息。后续注册中心上的信息变更则通过Notifier线程订阅的通道推送事件来实现。下面是Notifier线程中通道订阅的逻辑,如代码清单3-9所示。

代码清单3-9 Redis订阅代码

  • 29
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值