Zookeeper的原理和分布式锁

Zookeeper介绍

Zookeeper是一个高性能的分布式一致系统,在分布式系统中有着广泛的应用。基于它,可以实现分布式同步、配置管理、命名空间管理等众多功能,是分布式系统中常见的基础系统。

Zookeeper主要用来解决分布式集群中应用系统的一致性问题,它有着树状结构的节点,每个节点均可存储少量的数据。同时,用户可以修改和订阅节点中的内容。分布式系统中的节点通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。

从设计模式角度来看,Zookeeper是基于观察者模式实现的。可以把它作为一个信息的中心。使用该服务的生产者和消费者都以Zookeeper中的数据为基准。即:

  1. 生产者可以改变Zookeeper的节点,或者节点上的数据
  2. 消费者通过订阅Zookeeper节点,从而能够在节点变动时收到通知

基于这样的机制,将Zookeeper作为信息中心,便可以实现分布式系统中节点状态的最终一致性。

Zookeeper具有以下特点

  1. 最终一致性:客户端不论连接到哪个Zookeeper的哪一个节点,都会收到同一份状态。这是zookeeper最重要的性能。
  2. 可靠性:Zookeeper集群具有简单、健壮、良好的性能,如果消息m被到一台server接受,那么它将被所有的server接受。
  3. 实时性:Zookeeper保证client将在一个时间间隔范围内获得server的更新信息,或者server失效的信息。但由于网络延时等原因,Zookeeper不能保证两个client能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
  4. 等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
  5. 原子性:更新只能成功或者失败,没有中间状态。
  6. 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台server上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。

zookeeper应用场景

  1. 数据发布与订阅:应用配置集中到节点上,应用启动时主动获取,并在节点上注册一个watcher,每次配置更新都会通知到应用。

  2. 命名服务:分布式命名服务,创建一个节点后,节点的路径就是全局唯一的,这个路径可以作为全局名称使用。一些分布式服务框架(RPC,RMI)中的服务地址列表,通过使用命名服务,客户端应该能够根据特定的命中来获取资源的实体、服务地址和提供者信息等

  3. 分布式通知/协调:不同的系统都监听同一个节点,一旦有了更新,另一个系统能够收到通知。

  4. 分布式锁:Zookeeper能保证数据的强一致性,用户任何时候都可以相信集群中每个节点的数据都是相同的。一个用户创建一个节点作为锁,另一个用户检测该节点,如果存在,代表别的用户已经锁住,如果不存在,则可以创建一个节点,代表拥有一个锁。

  5. 集群管理:包括集群监控和集群控制,就是监控集群机器状态,剔除集群和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,一旦发现有机器挂了,该机器就会与zk断开连接,对应的临时目录节点会被删除,其他所有机器都收到通知。新机器加入类似,如图:

  6. 配置管理:在分布式应用环境中很常见,例如同一个应用系统需要多台节点运行,但是它们运行的应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的.properties或者xml配置,这样非常麻烦而且容易出错。

    像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个目录节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,利用watcher通知给各个客户端,从而修改配置。如图:

ZK的数据模型和节点类型

zk采用事务日志及快照文件的方案来落盘数据,保障数据在不丢失的情况下能快速恢复。

Znode

Znode兼具文件和目录两种特点,可以做路径标识,也可以存储数据,并可以具有子Znode。具有增删改查等操作。

Znode具有原子性操作,读操作一次性获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。每一个节点都有自己的访问控制表,这个表规定了用户的权限。

Znode存储数据大小有限制。每个Znode数据大小之多1M。

Znode通过路径引用,如同Unix中的文件路径。路径必须是绝对的,唯一的。

Znode节点类型

  1. 持久节点:一旦创建,该数据节点会一直存储在zk服务器上,即使创建该节点的客户端与服务端的会话关闭了节点也不会删除
  2. 临时节点:当创建该节点的客户端会话因超时或发生异常而关闭时,该节点也相应的在zk上被删除。
  3. 有序节点:不是一种单纯种类的节点,而是在持久节点和临时节点的基础上,增加了一个节点有序的性质。

Zookeeper watch机制

客户端,可以通过在Znode上设置watch,实现实时监听znode的变化

watch事件是一个一次性的触发器,当被设置了watch的数据发生了变化的时候,则服务器将这个改变发送给设置了watch的客户端

  1. 父节点的创建、修改、删除都会出发watcher事件
  2. 子节点的创建,删除会触发watcher事件

一次性:一旦被触发就会移除,再次使用需要重新注册,因为每次变动都需要通知所有客户端

轻量:只通知发生了事件,不告知事件内容,减轻服务器和带宽压力

三个角色

watcher机制包括三个角色:客户端线程、客户端的watch manager以及zookeeper服务器

  1. 客户端向zookeeper服务器注册一个watcher监听
  2. 把这个监听信息存储到客户端的WatchManager中
  3. 当zookeeper中的节点发生变化时,会通知客户端,客户端会调用相应watcher对象中的回调方法。watch回调时串行同步的(FIFO)

ZAB协议

ZAB协议是为Zookeeper专门设计的一种支持崩溃恢复的原子广播协议,实现分布式数据一致性。

所有客户端的请求都写入到Leader进程中,然后,由Leader同步到其他节点Follower。

ZAB协议包括两种基本模式:崩溃恢复和消息广播

消息广播

集群中所有事务请求都由Leader节点处理,Leader将客户端的事务请求转换为事务Proposal,并分发给集群中的所有Follower。

完成广播后,Leader等Follower反馈,当有过半数的Follower反馈消息后,Leader将再次向Follower广播Commit信息,确认之前的Proposal提交。(有点像2PC)

Leader节点的写入是一个两步操作,第一步是广播事务操作,第二步时广播提交操作,其中过半数指的是反馈节点数 >= N/2 + 1,N是全部Follower节点数。

崩溃恢复

以下状态进入崩溃恢复:

  1. 初始化集群,刚刚启动的时候
  2. Leader崩溃,故障宕机
  3. Leader失去了半数机器支持,与集群中超过一半的节点断连

此时开启新一轮Leader选举,选举产生的Leader会与过半的Follower进行同步,强一致性(CP),当与过半机器同步完成后,推出恢复模式,进入消息广布模式

整个Zookeeper集群的一致性保证都是在以上两个状态之间切换。

ZAB节点的三种状态

following:服从leader的命令(从节点,负责读)

leading:负责协调事务(主节点)

election/looking:选举状态

Zxid

ZAB协议的一个事务编号,一次事务意味着出现了一次选举,Zxid是一个64位的数字,其中低32位是一个简单的单调递增计数器,针对客户端每一个事务请求,计数器加1;而高32位则代表Leader周期年代编号。

Leader周期(epoch),每当一个新的leader选举出来,会从这个leader服务器上取出本地日志中最大事务的Zxid并从中读取epoch值,然后加1作为新的周期ID。

高32位代表每代Leader的唯一性,低32位代表了每代Leader中事务的唯一性

分布式锁的流程

分布式锁的应用上面已经讲到了,具体流程如下:

假如说有n个分布式的工作点,定义为a1,a2,a3,a4……

当某个分布式点做一件事时,先去zk某个固定位置判断(例如:/aTask/sync目录下)是否存在一个特定节点(例如节点名:lock):

  1. 如果没有,则创建该节点,即声明自己持有该锁。然后进行分布式任务,任务结束后。去删除该节点,表明释放该锁。
  2. 如果有,表明其他工作点持有了锁,不能开展分布式任务。

分布式锁的应用

举一个最简单的例子:

1、假如有一个系统,能够提供报表查看功能——>于是我们开发了一个单节点应用

2、该系统访量非常大,系统难以支撑——>我们部署了多个节点(加入为5个),分担访问请求。于是成了分布式应用

3、老板要求增加一个每天晚上向指定邮件发送该日统计报表的功能—–>应用中增加个定时线程,每天晚上半夜跑报表,发邮件

这个时候,出问题了,老板每天晚上会收到5封邮件!因为同样的程序我们部署了5个节点!怎么解决呢?

方案一:有一个节点和其他节点不一样,具有每日汇总功能。

优点:开发简单。

缺点:这样,一个应用便成了两个应用。我们必须维护两份代码。一份是不具有每日汇总功能的,一份是具有每日汇总功能的。

方案二:增加分布式锁。这样,5个节点中,只有获取到锁的节点才会发出邮件。

优点:代码还是只有一份,维护简单。扩展性强,可以继续扩展类似的需要锁的功能(例如某个业务流程只允许处理一次,还可以继续复用相关逻辑)。

缺点:开发稍微复杂一点。

因此,方案二是最好的选择。

基于ZooKeeper的三种分布式锁实现

https://www.cnblogs.com/codestory/p/11387116.html

zookeeper安装

# 版本信息:3.5.8
# 路径:/home/rndii/projects/zookeeper/apache-zookeeper-3.5.8
# 服务器节点:10.0.1.104   10.0.1.117    10.0.1.127
# 操作系统用户:rndii

# 1.修改zoo.conf文件,如果该文件不存在,则cp zoo.simple.cnf zoo.cnf
# zoo.cnf 如下修改:
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/home/rndii/projects/zookeeper/apache-zookeeper-3.5.8/data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
server.1=10.0.1.104:2888:3888
server.2=10.0.1.117:2888:3888
server.3=10.0.1.127:2888:3888
# 配置文件到此结束


# 2.在data文件夹中新增myid文本文件,内容为对应ip的节点id。对应zoo.cnf下的server.x=10.0.1.104:2888:3888的x

# 3.如果未配置全局环境变量可修改zoo下的bin/zkServer.sh文件增加JAVA_HOME=xx

# 4. 常规命令
# 启动
bin/zkServer.sh start
# 停止
bin/zkServer.sh stop
# 查看zookeeper的状态
bin/zkServer.sh status

   转载规则


《Zookeeper的原理和分布式锁》 锦泉 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Elasticsearch 集群架构学习 Elasticsearch 集群架构学习
Elasticsearch是一个实时的分布式搜索和分析引擎,它可以帮助我们用很快的速度去处理大规模数据,可以用于全文检索、结构化检索、推荐、分析以及统计聚合等多种场景。
2021-02-25
下一篇 
RefreshScope注解和Spring bean scope RefreshScope注解和Spring bean scope
用过Spring Cloud的朋友都知道在使用动态配置刷新的我们要配置一个@RefreshScope 在类上才可以实现对象属性的的动态更新。
2021-02-23
  目录