分布式系统与Zookeeper

本文介绍了分布式系统的架构演化,从单机结构到集群结构再到分布式结构,并解释了CAP定理。详细阐述了Zookeeper的基本概念、特性、架构原理及其在配置管理、集群管理、分布式锁等场景中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    随着大型网站的各种高并发访问、海量数据处理等场景越来越多,如何实现网站的高可用、易伸缩、可扩展、安全等目标就显得越来越重要。为了解决这样一系列问题,大型网站的架构也在不断发展。提高大型网站的高可用架构,不得不提的就是分布式。

    这里简单总结一下分布式系统及zookeeper的相关概念和原理。

    一、什么是分布式系统

    1、单机结构

    便

    2、集群结构

    使使

    3、分布式结构

    ”,如用户服务、订单服务等。RPC式同步用或者消息队列异步处理务,比如某一时刻用户购买量暴增,那就扩展订单服务集群的处理能力,而其他服务集群不需要太大的改动使

    4、CAP定理

    CAP定理指的是在一个分布式系统中, Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼而最多取其二。我们先来看看这三个特性的含义。

    一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。
    可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
    分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致
    性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

    通俗的讲,一个分布式系统里面,节点组成的网络本来应该是连通的。然而可能因为一些故障,使得有些节点之间不连通了,整个网络就分成了几块区域。数据就散布在了这些不连通的区域中。这就叫分区。

    当一个数据项只在一个节点中保存,那么分区出现后,和这个节点不连通的部分就访问不到这个数据了。这时分区就是无法容忍的。提高分区容忍性的办法就是一个数据项复制到多个节点上,那么出现分区之后,这一数据项就可能分布到各个区里。容忍性就提高了。

    然而,要把数据复制到多个节点,就会带来一致性的问题,就是多个节点上面的数据可能是不一致的。要保证一致,每次写操作就都要等待全部节点写成功,而这等待又会带来可用性的问题。
    总的来说就是,数据存在的节点越多,分区容忍性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低。

    CAP理论就是说在分布式系统中,我们最多只能实现CAP中的两点。如图。


    值得一提的是,在zookeeper中采用的是一致性和可用性的平衡方案——最终一致性,后面我们会看到。

    二、Zookeeper简介

    要想管理好我们自己的分布式系统,就不能不提到大名鼎鼎的分布式协调框架Zookeeper。

    ZooKeeper是一个开源的分布式协调服务,由雅虎创建,是Google Chubby的开源实现。分布式应用程序可以基于ZooKeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。

    1、为什么选择Zookeeper

    大部分分布式系统都需要一个主控、协调器或控制器来管理物理分布的子进程(如资源、任务分配等)。如果每一个系统都开发私有的协调程序,这样会造成反复编写的浪费,且难以形成通用的、伸缩性好的协调器。ZooKeeper是Google的Chubby一个开源实现,是Hadoop的分布式协调服务。它高效、可靠地解决数据一致性问题,在工业界大型分布式系统中已经得到了广泛的使用和认可。同时,作为一个框架,zookeeper是轻量级的,无论是部署还是调用都非常简单易用。

    让我们来看看Zookeeper的一些特性。

    最终一致性

    这是Zookeeper最重要的特性,最终一致性是C和A的折中方案。当客户端发起写请求,该请求会在Zookeeper集群中所有机器上执行,当有超过一半的机器执行完成,Zookeeper即向客户端返回写入成功。因此,通常我们在部署Zookeeper集群时一般部署奇数台。

    顺序性

    从同一个客户端发起的事务请求,最终会严格按照其发送顺序被应用到Zookeeper中。每个事务都会被分配一个事务ID,该ID由Zookeeper统一管理,全局唯一,且递增。每个事务按照ID顺序保存在事务队列中。

    可靠性

    一旦Zookeeper成功的应用一事务,并完成了客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下去。

    实时性

    因为采用最终一致性,Zookeeper不能保证客户端能得到刚更新的数据。如果需要最新数据,可以在读取数据之前调用sync()接口。

    原子性

    类似于数据库事务操作,一次数据更新要么成功,要么失败

    单一视图

    无论客户端连接到哪个服务器,看到的数据模型都是一致的。也就是,同一客户端无论什么时候连接到哪个服务器上,都不会看到比自己之前看到的数据更早版本的数据。单一视图保证是由ZooKeeper客户端与服务端建立连接请求时的一些校验操作实现的。还记得刚才说的事务ID吗,这个ID会保存在服务器和客户端中。当服务器发现客户端的事务ID高于自己时,会拒绝客户端连接。客户端会自动寻找其他的Zookeeper服务器连接。

    2、Zookeeper架构及原理

    先来看看Zookeeper的整体架构,如图。

    

    

    下面我们依据架构图介绍一下Zookeeper的几个核心概念,这些概念有助于更深入的了解Zookeeper。

    角色
    Zookeeper中包含三种角色:Leader、Follower、Observer。

    Leader主要用来更新系统状态,处理其他服务器发来的事务请求,并负责进行投票的发起和决议。

    Follower用来处理客户端非事务请求并向客户端返回结果,如果是写事务请求则转发给Leader。Follower通过心跳同步Leader的状态,并在选主过程中参与投票。

    Observer用来处理客户端非事务请求并向客户端返回结果,如果是写事务请求则转发给Leader。Observer通过心跳同步Leader状态,但不参与投票过程。显然,Observer的目的是为了扩展系统,提高读取速度。

    Zookeeper读写

    Zookeeper的写入流程,如图。

    

    在Zookeeper集群中,读可以从任意一个Zookeeper Server读。这一点是保证Zookeeper比较好的读性能的关键。我们重点说说写请求。

    首先客户端可以向集群中任一Server提交写请求,如果该Server是Follower而不是Leader,则转发写请求。Leader收到请求后,会为该事务分配一个全局唯一的事务ID,将事务Id与事务请求绑定在一起,组成一个消息体,然后放入队列,发送给Follower。Follower接收到写请求后,先以日志的形式写在本地,然后返回一个确认消息给Leader。当Leader收到一半以上写成功的ACK后,就认为该写成功了,发起一个提交事务通知,通知Follower提交事务。这就是Zookeeper数据写入最终一致性算法——ZAB算法。

    数据模型Znode

    Znode是Zookeeper中特有的数据结构,视图结构类似Linux文件系统,但没有目录和文件的概念。Znode是Zookeeper中数据的最小单元,可以保存数据,但不能太大。Znode通过挂载子节点构成一个树状的层次化命名空间,根由“/”开始。如图。

    

    每个Znode的节点类型包括持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)。通常,每个节点都是由客户端去创建的。持久节点就是永久保存在Zookeeper上的节点,无论客户端离线或宕机都一直存在。而临时节点在客户端与Zookeeper失去联系的时候会被自动清除。值得注意的是,客户端与Zookeeper失去联系包括主动断开连接以及由于网络不稳定而掉线并在规定的超时时间内未重新连上任一Zookeeper Server。临时节点的这个特性非常有用,它可以帮助我们实现集群master选举及分布式锁等功能。顺序节点就是在创建节点时,在节点的名字后面加上一堆逐渐递增的数字,这个数字可以帮助我们识别客户端的访问顺序。

    好,组合一下,我们一共可以生成四种不同的节点类型:持久节点、临时节点、持久顺序节点、临时顺序节点。

    Znode状态

    我们通过命令行get 路径的方式可以查看Znode的状态,如图。

    

    图中已经标注了各个字段的含义。注意到其中有三个version,即cversion、dataVersion、aclVersion,这个version是表示对数据节点数据内容的变更次数,强调的是变更次数,因此就算在修改的时候数据内容的值没有发生变化,version的值也会递增。这里我们可以简单的先了解在数据库技术中,通常提到的“悲观锁”和“乐观锁”。
    悲观锁具有严格的独占和排他特性,能偶有效的避免不同事务在同一数据并发更新而造成的数据一致性问题。实现原理就是:假设A事务正在对数据进行处理,那么在整个处理过程中,都会将数据处于锁定的状态,在这期间,其他事务将无法对这个数据进行更新操作,直到事务A完成对該数据的处理,释放对应的锁。一份数据只会分配一把钥匙,如数据库的表锁或者行锁(for update)。
    乐观锁的具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样那么执行更新操作,反之拒绝。

    Zookeeper的版本作用就是类似于乐观锁机制,用于实现乐观锁机制的“写入校验”,在处理数据更新的时候会去检查版本,保证分布式数据的原子性操作。

    Watcher机制

    在zookeeper中,引入了watcher机制来通知客户端,服务端的节点信息发生了变化。其允许客户端向服务端注册一个watcher监听,当服务端的一些指定事件触发了这个watcher,就会向指定的客户端发送一个事件通知,如图。

    

    

    Watcher的工作机制主要包括三个步骤:客户端注册watcher、服务端处理watcher和客户端回调watcher事件。在具体的流程上,客户端向Zookeeper服务器注册Watcher事件监听的同时,会将Watcher对象存储在客户端WatchManager中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatchManager中取出对应的Watcher对象执行回调逻辑。
    Watcher通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如针对NodeDataChanged事件,ZooKeeper的Watcher只会通知客户指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据,这也是ZooKeeper的Watcher机制的一个非常重要的特性,保证网络传输的高效性。

    三、Zookeeper应用场景

    Zookeeper是一个高可用的分布式系统管理和协调框架,并且能够很好的保证分布式环境中数据的一致性。在越来越多的分布式系统(Hadoop、HBase、Kafka)中,Zookeeper都作为核心组件使用。下面简单介绍一下在日常开发中使用到Zookeeper的一些场景。

    1、配置管理

    在微服务分布式架构中,所有子服务都共享相同的的配置文件。这些配置文件的管理和同步是一个需要解决的问题。任一服务对配置文件修改后,它应该能够快速同步到所有服务上。
    我们将各个配置信息分别写入Zookeeper的Znode上,各个服务节点起一个线程监听这些Znode,一旦Znode中的数据被修改,Zookeeper将负责通知各个服务节点,在回调方法中,各个服务去重新获取配置文件信息。这样借助Zookeeper的watcher机制就实现了配置文件的一致性。

    2、集群管理

    Master选举可以说是ZooKeeper最典型的应用场景了。比如HDFS中Active NameNode的选举、YARN中Active ResourceManager的选举和HBase中Active HMaster的选举等。
    针对Master选举的需求,通常情况下,我们可以选择常见的关系型数据库中的主键特性来实现:希望成为Master的机器都向数据库中插入一条相同主键ID的记录,数据库会帮我们进行主键冲突检查,也就是说,只有一台机器能插入成功——那么,我们就认为向数据库中成功插入数据的客户端机器成为Master。
    依靠关系型数据库的主键特性确实能够很好地保证在集群中选举出唯一的一个Master。但是,如果当前选举出的Master挂了,那么该如何处理?谁来告诉我Master挂了呢?显然,关系型数据库无法通知我们这个事件。

利用ZooKeepr的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即ZooKeeper将会保证客户端无法创建一个已经存在的ZNode。也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行Master选举了。成功创建该节点的客户端所在的机器就成为了Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的Watcher,用于监控当前Master机器是否存活,一旦发现当前的Master挂了,那么其他客户端将会重新进行Master选举。这样利用临时节点和watcher机制就实现了Master的动态选举。

    3、分布式锁

    分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性。假设锁空间的根节点为/lock。客户端连接zookeeper,并在/lock下创建临时顺序节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁。执行业务代码,完成业务流程后,删除对应的子节点释放锁。这样利用临时顺序节点和watcher机制就轻松实现了分布式锁。


    好了,分布式系统和Zookeeper就先说到这里了,后面会再谈谈分布式存储以及分布式队列。欢迎关注。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值