zookeeper

zookeeper是一种为分布式应用设计的高可用,高性能且一致的开源协调服务,他提供了一项基本服务:分布式锁服务,还有配置维护,组维护,分布式消息队列,分布式通知/协调。一致性、可用性和容错性,使用的是Zab协议,数据结构Znode,原语、通知机制–watch机制。

数据模型

zk拥有一个层次的命名空间,如下:

  • Znode通过路径引用,就像Unix中的文件路径,路径必须是绝对的,因为他们必须由斜杠字符来开头,还必须是唯一的,/zookeeper用来保存管理信息,比如关键配额信息。
  • Znode兼具文件和目录两种特点,每个znode由三部分组成,首先是stat,表示状态信息,描述该节点的版本,权限等信息,然后是data,这个是当前节点存储的数据,最后是children,表示Znode下的子节点。
    节点存储的信息也是很小的,最大只能是1M,通常情况下要比这个小的多,存储的都是配置信息,状态信息,汇集位置等等,管理调度数据。
  • 数据访问,存储的数据要被原子性操作,要么完成要么失败,每个节点还有自己的ACL,规定了用户权限。
  • 节点类型,临时节点,该节点就是存活在回话还没结束的情况下,一旦回话结束,临时节点就会被自动删除,临时节点不允许有子节点。还有就是顺序节点,除非用户删除,不然会一直存在。顺序节点,路径结尾加一个递增的计数,这个计数对于此节点父节点是唯一的,计数值大于一定值时,计数器会溢出。
  • watch,客户端可以在节点上设置watch,这就是监视器,当该节点的状态发生变化时,将会触发watch对应的操作,然后会向客户端发送一条通知,然后watch失效,客户端需要重新注册。

    zk中的时间

  • Zxid,使得zk节点状态改变的每一个操作将使节点接收到一个Zxid格式的时间戳,时间戳全局有序,每个节点维护三个Zxid值,分别为:cZxid、mZxid、pZxid,分别是节点创建的时间戳,节点修改时间,第三个不知道是啥。Zxid是一个64位的数字,高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,低32位是个递增数。版本号,对节点每一个操作都致使这个节点版本号增加,每个节点维护着三个版本号,首先是version节点数据版本号,cversion节点版本号,aversion节点所拥有的acl版本号。
    节点属性如下:

zk服务中的操作

更新ZooKeeper操作是有限制的。delete或setData必须明确要更新的Znode的版本号,我们可以调用exists找到。如果版本号不匹配,更新将会失败。

更新ZooKeeper操作是非阻塞式的。因此客户端如果失去了一个更新(由于另一个进程在同时更新这个Znode),他可以在不阻塞其他进程执行的情况下,选择重新尝试或进行其他操作

watch

ZooKeeper可以为所有的读操作设置watch,这些读操作包括:exists()、getChildren()及getData()。watch事件是一次性的触发器,当watch的对象状态发生改变时,将会触发此对象上watch所对应的事件。watch事件将被异步地发送给客户端,并且ZooKeeper为watch机制提供了有序的一致性保证。理论上,客户端接收watch事件的时间要快于其看到watch对象状态变化的时间。

① 一个成功的setData操作将触发Znode的数据watch

② 一个成功的create操作将触发Znode的数据watch以及孩子watch

③ 一个成功的delete操作将触发Znode的数据watch以及孩子watch

ZooKeeper所管理的watch可以分为两类:

① 数据watch(data watches):getData和exists负责设置数据watch
② 孩子watch(child watches):getChildren负责设置孩子watch

Master选举

在分布式锁服务中,有一种最典型应用场景,就是通过对集群进行Master选举,来解决分布式系统中的单点故障。什么是分布式系统中的单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作单点故障。

传统方式是使用备用主机,不断向master发送ping包,来确定master是否存活,如果没收到ack就认为master挂了,自己会启动一个新实例,但是这样会存在一个问题,那就是网络问题,其实master没有挂,但是由于网络问题,备用主机没有收到master的ack,这时候自己会启动实例,这就会导致服务存在双master,导致系统混乱。

看看zk的解决方式,虽然不能解决网络问题,但是能保证每个时刻只存在一个master

选取编号最小的节点为主节点,主节点挂掉之后会重新触发一次选举。

下面是配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# The number of milliseconds of each tick
tickTime=2000

# The number of ticks that the initial
# synchronization phase can take

initLimit=10
follower和leader连接同步到leader的初始化连接时间。

# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
leader和follower之间发送消息时,请求和应答时间长度
# the directory where the snapshot is stored.
dataDir=D:/softinstall/zk/zookeeper-3/temp/zookeeper/
这个目录下需要创建myid存储服务器编号
# the port at which the clients will connect
clientPort=2183
zk监听端口
#the location of the log file
dataLogDir=D:/softinstall/zk/zookeeper-3/temp/log/
日志
server.0=localhost:2287:3387
server.1=localhost:2288:3388
server.2=localhost:2289:3389

server.A=B:C:D
A:其中 A 是一个数字,表示这个是服务器的编号
B:是这个服务器的 ip 地址;
C:Leader选举的端口;
D:Zookeeper服务器之间的通信端口。

zookeeper不支持递归的删除操作,因此在删除父节点之前必须先删除子节点,delete操作要先比较版本号,乐观锁机制,delete的第二个参数设置为-1可以绕过这个版本检测机制。

zk异常

  • InterruptedException异常
    如果操作被中断,就会抛出这个异常,在Java语言中取消阻塞方法会调用interrupt()方法,如果成功取消阻塞方法的话就会抛出这个异常,通常使用这种方法来取消zookeeper操作。
  • KeeperException异常
    Zookeeper服务器发出一个错误信号或者服务器存在通信问题,抛出的是这个异常。

这个异常通常非为3类:

  1. 状态异常,操作不能作用于znode树而导致失败,通常是同一时间有另外一个进程正在修改znode,根据版本号这个操作就会失败。
  2. 可恢复异常,能够在同一个zookeeper回话中恢复的异常,对于这种异常的处理,要分情况,幂等操作和非幂等操作,幂等是指多次执行不会有影响,比如读请求和更新值,这种就简单重试就行,非幂等操作就不行,因为他们一次操作的结果和多次操作的结果可能不一样。
  3. 不可恢复异常,比如会话失效,这种只能重新连接。

    分布式锁

    同一台机器上,在相同jvm情况下,锁是靠jvm内部机制实现,但是在分布式系统中,多个进程分布在不同的机器上,这就需要新的锁机制了,zk就有这种功能,分布式锁可以在大型分布式系统中实现领导者选举(这个选举和zk自己的领导选举不一样,主要是为了与主进程保持一致的分布式系统设计),持有锁的那个进程就是系统的领导者。
  • 一般情况下分布式锁的实现,首先是创建一个作为锁的znode(永久),然后希望获得锁的客户端创建临时顺序子节点作为这个节点的子节点,在任何时间都是顺序号最小的客户端持有锁。
  • 上诉方法会导致羊群效应,什么是羊群效应呢,就是当客户端数量比较大的情况下,每客户端都会在那个节点下创建子节点,并在锁znode上设置观察,每次锁被释放的时候,观察会被触发,zk需要向所有客户端发出通知,但是实际上只又很少一部分需要处理这一事件,最后只有一个客户端成功获得锁,zk发送大量通知会产生峰值流量,这会对zk服务器造成压力,解决这个问题方法是只有前一个顺序号的子节点消失时才需要通知下一个客户端,而不是删除或者创建任何子节点时都需要通知。就是每个子节点只观察自己上一个znode。
  • 这个还有一个问题就是,不能处理因连接丢失而导致的creat操作失败。就是我们创建过程中,连接丢失了,但是我们又不知道是否创建成功,这时候不能简单的重试,因为如果第一次创建成功,重试的话,就会导致有一个节点根本没有进程和他对应,这个节点也就不会被删除,除非会话结束,最后导致死锁,这个解决方法就是在子节点名称中加入ID,自己可以分辨的ID,比如zk提供了获取当前会话的id,这个可以加到里面,这个id是全局唯一的,这样就可以在创建之前检查一下了。

    zookeeper实现共享锁

serverA 和 serverB同时访问serverC上的文件,并同时对该文件执行写操作,如果没有锁的话,导致写入的顺序会混乱,这就需要共享锁了。

  • 利用节点的唯一性,比如加锁时就床架某个节点,其它机器尝试获得锁的时候也创建同样的节点,但是由于节点的唯一性导致无法创建成功,解锁的时候,只需要删除这个节点就行,这个在公司做那个定时任务时,由于服务器的集群,要保证同一时刻只有一个定时任务触发,这就需要共享锁了,这个是将自己ip存到那个节点,每次其它机器都是读取节点的内容和自己的ip对比,释放锁的时候是结束回话,那个临时节点会被自动删除。
  • 利用顺序节点来创建共享锁,这个和前面一样啊,就是节点顺序号最小的获得锁,羊群效应

zk简述

zk不是用来存储数据的,他的数据都是在replicateddatabase中,这也是一个内存数据库,一般可想而知就不会很大,zk主要存储的是配置信息元数据,协调功能,主要作用是用来维护和监控存储数据的状态变化,通过监控这些数据状态的变化,可以达到基于数据的集群管理,关心的数据,数据量还不是很大的。HDFS则是存储在磁盘上。

zk作用:

  1. 集群管理:利用临时节点特性,节点关联的是机器的主机名,ip地址等相关信息,集群单点故障也属于该范畴。
  2. 统一命名:利用节点的唯一性和目录节点树结构。
  3. 配置管理:节点关联的是配置信息。
  4. 分布式锁:节点关联的是要竞争的资源。

数据发布和订阅

这个就是所谓的配置管理,配置数据发布到zk上,订阅者动态获取数据,实现配置信息的集中式管理和动态更新,比如全局配置信息,地址列表。集中式配置管理在应用集群中是非常常见的,通过在节点上设置watch,在节点状态发生变化时会通知客户端。

相关应用

  1. 索引信息和集群中机器节点状态存放在zk一些指定节点,供各个客户端订阅使用。
  2. 系统日志(经过处理后)存储,这些日志通常2-3天后被清除
  3. 应用中用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。
  4. 配置信息集中管理,应用其中的时候主动获取一次,并在节点上注册一个watcher。

如下所示:配置信息放到一个节点,其它客户端设置watcher

统一命名服务

这个一般都是在rpc框架中使用的,就是通过一个服务名来获得资源服务的地址和提供者信息。像soa上,自己手动在zk上写入的服务名和服务名下的接口名和响应的ip地址和端口,消费者根据服务名和接口名获得相关的服务提供者的地址和端口

分布通知/协调

zk中特有watcher注册与异步通知机制能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。

相关应用:

  1. 心跳检测机制:检测系统和被检测系统通过zk上某个节点关联。
  2. 系统调度模式:控制台和推送系统,控制台控制推送系统进行相应的推送工作,其实就是使用的watcher
  3. 工作汇报模式:就是子任务启动后,会注册一个临时节点,然后去修改这个节点,管理者可以获取这个节点信息。

集群管理

  1. 集群机器监控
    实时检测集群机器是否存活
    master选举
    相同的业务分布在不同的机器上,有些业务逻辑,例如一些耗时的计算,往往只需要整个集群中的某一台机器进行执行,其余共享这个结果,减少重复劳动,提高性能。

这个master选举和锁一样啊?????????

文章目录
  1. 1. 数据模型
  2. 2. zk中的时间
  3. 3. zk服务中的操作
  4. 4. watch
  5. 5. Master选举
  6. 6. zk异常
  7. 7. 分布式锁
  8. 8. zookeeper实现共享锁
  9. 9. zk简述
    1. 9.1. 数据发布和订阅
    2. 9.2. 统一命名服务
    3. 9.3. 分布通知/协调
    4. 9.4. 集群管理
,