那些年我们一起用过的中间件(一):注册中心篇

那些年我们一起用过的中间件(一):注册中心篇

人生苦短,不如养狗


一、背景

  近来工作学习内容逐渐朝中间件方向转移,学习之余准备新开一个系列记录一下近来所学。

  作为系列的开篇,我选择了 注册中心 这一微服务架构中非常重要的组件。通过学习注册中心,我们不仅会学习 服务架构演进服务发现 等架构相关的知识,还会对如何在分布式环境下保证 数据的分布式一致性 进行一次简单的探索。

服务架构演进

  鲁迅先生曾说过:“一切事物发展必有其缘由”。

那些年我们一起用过的中间件(一):注册中心篇

  所以在开始学习注册中心之前,我们需要先了解一下注册中心出现的背景。

  在单机时代,也即 单一应用架构  (All in One) 中,所有的业务逻辑都紧密地耦合在一个应用中,整个系统相对封闭且复杂,很难进行扩展和重构。此时不同的功能只是项目中的一个模块、一个包、一个类甚至于只是一个方法,功能的调用实际就是对项目内的方法进行调用。

那些年我们一起用过的中间件(一):注册中心篇

  随着业务逻辑的膨胀,系统复杂度不断增长,为了控制单体应用的复杂度,于是对单体应用进行了垂直拆分,即对业务进行分层拆分,将原本耦合在一起的业务拆分成多个业务领域,此时服务架构升级为了 垂直应用架构 。在垂直应用架构中,原先较为复杂庞大的单体应用会按照领域范围将业务功能进行垂直切分,切分成数个较小的功能独立的单体应用。这样的处理思路是不是有点熟悉,对,就是算法中的 分治思想 ,即将一个大的问题拆分成多个子问题进行处理。

那些年我们一起用过的中间件(一):注册中心篇

  然而业务不会停止,垂直应用不断的增加。在应用数量不断扩张的过程中,一些较为核心和基础的应用不可避免需要被其他应用反复调用,此时为了更好的服务各个业务系统,提升系统的复用性、灵活性,于是将这些较为核心的业务抽离为服务中心提供统一的服务。在这一过程中,应用系统被抽象为一个个独立的 服务 ,构建松耦合服务架构,也即 面向服务的架构(SOA)

那些年我们一起用过的中间件(一):注册中心篇

  至此,在架构演进过程中第一次出现了 服务 的概念。但是在SOA架构中服务的粒度通常是粗粒度的,强调接口契约的规范性,通信机制上呈现多样性。为了进一步细化SOA架构中服务的粒度,同时为了解决SOA架构的两种实践 Web ServiceESB 中存在的问题,在SOA架构基础上延展出了微服务架构。

  在服务化架构/微服务架构中,围绕服务诞生了 服务提供者服务消费者 两种角色,服务消费者通过调用服务提供者提供的服务来完成对应的业务。为了调用服务提供者提供的服务,服务消费者就必须获取服务提供者的调用信息比如调用地址、健康状况等。由于现有系统的复杂性不断提高以及容器部署方式的推行,服务消费者的调用信息无法简单地通过静态配置文件来保存,服务发现的工具—— 注册中心 由此诞生。

二、服务发现工具–注册中心

1. 什么是注册中心

  从上面架构演进的过程中可以了解到,注册中心最大的使命就是为了让服务消费者更好地调用服务提供者提供的服务。也就是说,注册中心本质上是一个存储系统,用于存储服务消费者所关心的服务调用信息——即服务提供者的节点IP、服务元数据、服务健康状况等。下面我们分别从服务提供者和服务消费者的角度来看一看注册中心应该提供哪些能力。

  从服务提供者的角度来看,注册中心需要提供以下能力:

  • 服务注册 :当服务提供者上线时,服务提供者可以通过注册中心提供的注册接口将所提供的服务的调用地址和元数据等注册到注册中心上,用于服务消费者进行服务调用信息查询;
  • 服务注销 :当服务提供者下线时,服务提供者可以通过注册中心提供的注销接口将所提供的服务的调用地址和元数据等注销;
  • 健康状况监测 :健康状况监测的方式一般有以下两种:
    • 一种是 服务自主上报方式 ,即服务提供者定时通过注册中心提供的心跳上报接口将心跳上报给注册中心,注册中心通过监测心跳时间来判断该服务提供者是否健康,如果长时间没有收到心跳则认为该服务提供者处于故障状态,变更其服务状态为无服务(或故障中);
    • 另一种是 注册中心主动探测方式 ,即服务提供者提供健康监测接口用于注册中心定时触发,用以确定当前服务提供是否处于健康状态;

  从服务消费者的角度来看,注册中心需要提供以下能力:

  • 获取服务列表 :服务消费者可以通过注册中心提供的服务查询接口获取对应服务的调用信息;
  • 服务状态变更事件推送 :当服务提供者的服务集群有新的实例上线,或服务提供者实例下线,亦或者服务提供者处于故障状态不可用时,应当将对应的服务状态信息通知到服务消费者;

  经过上面的分析,我们大致可以得出下面注册中心与服务提供者和服务消费者之间的链路关系图。

那些年我们一起用过的中间件(一):注册中心篇

2. 数据一致性

  诚然,注册中心很好地完成了解耦服务提供者和服务消费者以及服务发现的功能,但是作为一个服务治理中重要的一环,必须考虑到容灾性和可用性的问题,单机版的注册中心明显无法达到这样的要求(单点故障引发的系统不可用),因此,必然会采取多节点部署的方案。除此之外,区别于一般的应用,注册中心是一个 有状态 的应用,需要进行 数据同步 操作,而数据同步的需求又带来了一个新的问题—— 数据的分布式一致性问题

  所谓数据的分布式一致性问题是指在分布式环境下进行数据同步,不同节点之间可能会出现通过现有程序无法解决的数据不一致问题。也就是说无法保证对于每个节点的更新都是相同的,或者无法保证能够更新集群中的每个节点。

  在这种情况下,期望有一套能够满足ACID特性的分布式事务是完全不现实的。为了寻找在可用性和一致性之间的那个平衡点,诞生了CAP和BASE这样的分布式系统经典理论。下面我们来简单了解一下 CAPBASE 理论。

  CAP理论大致说明了这样一个道理:一个分布式系统无法同时满足 一致性(Consistency)可用性(Availability) 以及 分区容错性(Partition tolerance) ,最多只能满足其中的两条。也就是说江山、美人你不能都要,除非你是暴君,哈哈哈。

那些年我们一起用过的中间件(一):注册中心篇

  这里单独看下分区容错性的概念:

分布式系统在遇到任何 网络分区故障 的时候,仍然需要保证能够对外提供一致性和可用性的服务,除非整个网络环境都发生了故障。


  这就意味着,无论进行何种的排列组合,都必须满足分区容错性这样一个基本要求,所以目前市面上大部分的分布式存储系统大多是AP或者CP系统。

  但大佬们总是不会轻易满足,eBay架构师Dan Pritchett提出的BASE理论就是对一致性和可用性之间平衡点的进一步探索。其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来达到最终一致性。下面我们来看下BASE理论的三个组成部分:

  • 基本可用(Basically Acailable) :分布式环境下,在发生不可预知的故障时,允许损失部分可用性;
  • 软状态(Soft state) :允许系统中的数据存在中间状态,即允许数据同步的过程中存在延迟的情况;
  • 最终一致性(Eventually consistent) :这里强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达成一致状态,无须保证实时的强一致性;

  可以看到,为了寻求平衡点,BASE理论在可用性和一致性上都作出相应的让步。当然,无论是CAP理论还是BASE理论,在实际的使用场景中都会根据不同业务单元的一致性和可用性要求作出相应的调整。

三、常见的注册中心

  目前市面上比较牌面的注册中心有这样几个:Zookeeper、Eureka、Consul以及Nacos。这里我们主要了解一下Zookeeper和Eureka的部分实现逻辑,至于为什么不学习一下后两个,别问,问就是学不动…

1. 基于ZooKeeper实现的注册中心

  Zookeeper作为一个针对大型分布式系统的 高可用高性能具有一致性 的开源 协调服务 ,在分布式系统中有着诸多实践应用,比较常见的就是我们要学习的服务发现功能,诸如Dubbo、kafaka都是基于Zookeeper实现了一套注册中心。下面我们围绕 数据一致性健康状况检测通知风暴 这三个方面了解一下Zookeeper的实现方式。

数据一致性

  在Zookeeper当中主要依赖 ZAB协议 来实现实现分布式数据一致性。基于这一协议,Zookeeper实现了一种 主备模式 的系统架构来保持集群中各副本之间数据的一致性。

  这里简单介绍一下ZAB协议,ZAB协议是一种支持崩溃恢复的原子广播协议,主要包含两个方面的功能,一个是 消息广播 ,一个是 崩溃恢复 。前者使用的是一个原子广播协议(类似于二阶段提交过程),后者则用于主节点崩溃或服务集群重启时进行重新选主。

  Zookeeper有两种模式,一种是 独立模式 ,一种是 仲裁模式 ,分布式场景下基本选择仲裁模式。在仲裁模式中会存在一个Leader节点(即主节点),其余节点为Follower节点(即备节点)。ZAB协议规定,所有的数据变更请求必须转发给Leader节点,然后通过主节点来发起实际的数据更新流程,这里需要注意的是,和一般的二阶段提交不同,在ZAB协议中的二阶段提交只需要足量的Follower返回确认信息即可提交事务,不需要等待集群中所有的Follower都返回确认信息。在简化二阶段提交的同时,为了应对可能出现的Leader崩溃情况,ZAB协议还设计了一套在崩溃情况下的重新选主策略,有兴趣的同学可以取了解一下这一选主过程。

那些年我们一起用过的中间件(一):注册中心篇

健康状况检测

  在Zookeeper当中,数据是按照树形结构(或者文件目录结构)进行存储的。在树形结构的每个节点中进行数据的存储,其中有以下两种类型的节点:

  • 临时节点 :解释临时节点之前,需要首先了解一个重要的概念—— 会话(session) 。在Zookeeper当中客户端和服务端之间的操作都是基于会话进行的,而会话实际上就是客户端和服务端之间建立的一个 TCP连接 。从临时节点这个名称来看就可以了解到,这类节点是存在 生存时间(TTL) 的 ,其生存时间和会话存活时间息息相关,当客户端和服务端会话断开后,该临时节点也会随之删除。除此以外,临时节点无法拥有孩子节点,即临时节点就是 叶子节点
  • 永久节点 :与临时节点相对,只要不显式地调用删除节点接口,永久节点就会一直存在;

  经过上面的介绍应该不难发现,可以通过临时节点来对服务健康状况进行监控。服务提供者向Zookeeper进行服务注册时会建立一个会话,此时服务提供者的一些节点信息就会存在与这个会话相关联的临时节点中。为了维持会话的活性,服务提供者需要定时向Zookeeper服务器发送心跳,心跳的形式可以是一个新的请求或者是显式的PING信息。

  当长时间没有接收到会话的心跳信息时,Zookeeper会关闭对应的会话,同时与会话相关联的临时节点也会失效删除。

那些年我们一起用过的中间件(一):注册中心篇

通知风暴

  对于注册中心来说, 服务状态变更事件通知 是一个非常重要的功能,其重要性就在于会将服务健康状况实时通知给服务消费者,保证服务消费者每次发起的请求都能发送到健康的服务提供者节点上。

  Zookeeper通过 监视点(Watch) 很好的实现服务状态变更事件通知这一功能。监视点是由客户端设置在对应znode节点上用于监视某个事件的触发器,当znode上对应事件(这里一般是更新事件)发生便会触发一个单次的通知。

  总体上来看,这样的方案应该没有什么问题,但是分布式场景下总会存在着许多不稳定的因素,当服务集中发布或者出现网络抖动等网络环境不稳定的情况,此时服务提供者在进行服务注册和心跳发送的时候就会有极大的可能出现失败的情况。此时,从注册中心的角度来看,服务提供者在健康与不健康的状态之间反复横跳,无法自拔。在这种情况下,注册中心就会频繁地更新服务的可用节点信息和状态信息,同时不停的通知服务消费者。对于Zookeeper服务端内部来说,所有数据的更新操作都需要由Leader节点发起,如此频繁的更新请求极容易将Zookeeper的带宽打满,导致服务消费者进行服务提供者服务调用信息查询速度非常缓慢。而对于服务消费者而言,频繁的服务状态变更事件通知也会将服务消费者的带宽打满,从而引发服务的 “雪崩”现象

  对于上面的问题Zookeeper本身并没有什么比较好的解决方案,更多是需要从注册中心架构设计上寻找突破。较为常见的一个做法是 增量更新 ,即只推送那些发生变化的节点,而不是按照服务接口维度进行全量推送,这样可以一定程度上减缓通知风暴的风险。除此以外,美团技术团队在 美团命名服务的挑战与演进[1] 中针对这一问题提出了自己的见解,有兴趣的同学可以看一看,这里就不铺开细说了。

2. 基于Eureka实现的注册中心

  作为SpringCloud原生支持的注册中心,Eureka的地位和重要性可谓不言而喻,虽然现在好像官宣不再维护了。具体可以参考下图:

那些年我们一起用过的中间件(一):注册中心篇

  区别于Zookeeper主备模式的服务架构,Eureka采取的是去中心化的架构,架构图如下:

那些年我们一起用过的中间件(一):注册中心篇

  从上面的架构图中可以看到,Eureka同样也是分为服务端可客户端两个部分。服务提供者通过Eureka Client首先会向处在同一个可用区域的Eureka Server发起注册请求,如果出现网络抖动等异常情况,此时Eureka Client会将注册请求路由到其他可用区域的Eureka Server上。Eureka Server接收到注册请求后,会采取类似“异步多写”的AP协议(同样也是一种消息广播模式),将注册请求转发给所有可以转发到的其他Eureka Server上(Eureka Server之间会相互注册,同时定时发送心跳来刷新服务状态),如果本次请求转发失败,则会在下一次心跳时发起重试。其他Eureka Server收到请求后,会将注册请求在本地重放,达成Eureka Server之间状态的最终一致性。

  在服务状态变更事件通知上,Eureka采取的方式和Zookeeper刚好相反,服务消费者需要通过Eureka Client定时从Eureka Server上拉取服务列表,这里就会带来一个问题:拉取服务列表的频率应该怎么设定?同时,在Eureka中服务列表的拉取不是指定服务信息的拉取,而是全量数据的拉取。这一情况下就更容易出现上面所说的通知风暴问题。不过这两点问题在Eureka2.x中得到了优化处理,将pull模式变更为了push模式,同时在进行服务状态变更事件通知时,将全量数据通知变更为增量数据通知。

四、总结

  零零碎碎的写了一堆,注册中心的一个大概介绍应该是写完了,当然未必全面,也未必都对。对于更具体的实践由于闲鱼经验尚浅,可能还是需要诸位自己在开发过程自行去探索,当然也欢迎大家将自己的一些实践或者采坑经历写到留言一起分享一下。

  在本文编写过程中借鉴以下文章或书籍,有兴趣的同学可以阅读以下:ZooKeeper 源码和实践揭秘[2]Dubbo 服务导出[3] 以及 《从Paxos到Zookeeper 分布式一致性原理与事件》

  最后,疫情还在继续,愿诸位身体健康,好好学习,早日升职加薪~~

参考资料参考资料

[1]

美团命名服务的挑战与演进: https://tech.meituan.com/2020/05/14/octo-mns-2.0.html

[2]

ZooKeeper 源码和实践揭秘: https://mp.weixin.qq.com/s/DigeE4YxuT55cSeyb1q1tQ

[3]

Dubbo 服务导出: http://dubbo.apache.org/zh/docs/v2.7/dev/source/export-service/#224-%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C


原文始发于微信公众号(Brucebat的伪技术鱼塘):那些年我们一起用过的中间件(一):注册中心篇

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/90143.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!