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、用户自定义)的健康检查。
注册中心工作流程
- 服务提供者启动时,会向注册中心写入自己的元信息,同时会订阅配置元数据信息 。
- 消费者启动时,向注册中心写入自己的元信息,并订阅服务提供者、路由和配置元信息。
- 服务治理中心(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对象里面,并加载到内存:
Properties对象保存了所有服务提供者的URL,使用URL#serviceKey作为key,提供服务列表、路由规则、配置规则列表,当vlaue是列表存在多个时,使用空格隔开,还有一个特殊的key.register保存所有注册中心的地址,如果应用在重启过程中,无法连接注册中心或者宕机,Dubbo会自动通过本地缓存加载Invokers。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 { ........ } } }
- 缓存保存于更新
缓存保存有同步和异步两种方式,异步会使用线程保存,如果线程池在执行过程中出现异常,则会再次调用线程池进行重试。
AbstractRegistry#notify(List)实现更新内存缓存和更新文件缓存,当客户端第一订阅全量数据或者后续由于订阅得到的数据,都会走该逻辑进行更新。if (syncSaveFile) { // 同步保存 doSaveProperties(version); } else { // 异步保存,放入线程池,会传入一个AtomicLong、version版本号,保证数据是最新的 registryCacheExecutor.execute(new SaveProperties(version)); }
- 缓存加载
-
重试机制
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中,以供定时任务去重试。