zookeeper原理及应用
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google
的Chubby
一个开源的实现。
Apache ZooKeeper is an effort to develop and maintain an open-source server which enables highly reliable distributed coordination.
原理介绍和说明
- 一致性算法
ZooKeeper以Fast Paxos
(帕克索斯)算法为基础,让集群中的每个zk实例数据保持一致。一般部署集群,机器数设置为奇数个,更容易满足>N/2的投票条件。
- 存储模型
/ ├── app1 │ ├── p_1 │ ├── p_2 │ └── p_3 └── app2
类似操作系统文件夹的树型模型,与文件夹的区别在于,每个节点上可存储数据(不超过1M
),每个节点上的数据可带版本号。
zNode
节点类型
2
个划分维度:按节点是否可持久化存储,分为持久节点与临时节点;按节点序号是否可顺序递增(类似mysql
的auto_increment
),分为顺序节点及非顺序节点,
注:3.6.0版本以后,还新增了Container Nodes
(容器节点),该节点的特点是,如果其下的所有子节点都被删除,该节点也会在将来某个时间被删除。
ZooKeeper has the notion of container nodes. Container nodes are special purpose nodes useful for recipes such as leader, lock, etc. When the last child of a container is deleted, the container becomes a candidate to be deleted by the server at some point in the future.
持久节点 (client
创建持久节点后,就算与zk
断开,节点仍然保存在zk
中)
持久顺序节点 (eg: /order/quartz/wm000001 , /order/quartz/wm000002, /order/quartz/wm000003...)
临时节点 (client
与zk
断开连接后,节点自动删除)
临时顺序节点
- 事件监听
Created event
(节点创建事件)Deleted event
(节点删除事件)Changed event
(节点的数据变化事件)-zkClient.subscribeChildChanges();
//订阅子节点的变化zkClient.subscribeDataChanges();
//订阅某节点的数据变化(包括数据被删除)事件zkClient.subscribeStateChanges();
//订单状态变化(状态包括:连接,断开,认证失败等等)
以上为常用事件,其它事件请参考官方文档。实际使用中,很少用原生写法来监听事件,而是借助一些第三方的开源zk客户端,比如zkClient来监听事件。
ACL(Access Control List)
权限控制- 每个节点有5种操作权限:
Create、Read、Write、Delete、Admin
简称crwda
。其中:Delete
是指对子节点是否具有删除权限,其它4种权限指对自身节点的操作权限。 - 身份认证方式:
world:
默认方式,无限制,全世界均能访问。auth:
在上下文中添加授权用户。digest:
用户名/密码认证ip: ip
地址认证
- 每个节点有5种操作权限:
应用场景
- 应用场景1:分布式配置
要点:配置信息保存在db
与zk
中(保险起见,数据安全性更高),弄一个后台管理界面,对配置修改后,先保存到db
,然后同步写入zk
的节点中。应用启动时,先连到zk
上读取节点中的配置,同时监听节点的数据变化,当配置变化时,得到实时通知。
- 应用场景2:消除单点故障
(Single Point of Failure,SPOF)
└── ./OrderNoService ├── A0000001 10.0.0.1:8001 └── A0000002 10.0.0.2:8001
多个服务实例,启动时在zk
上临时顺序节点,服务的调用方约定取最小节点为Master
,当Master
挂掉后,节点自动删除,调用方得到事件通知,取新的最小节点来调用(
相当于slave
提升为master)
- 应用场景3:去中心化
上图中,左边为传统中心化的架构,缺点是每次有新的服务实例加入或下线,都要调整nginx
中心节点的配置(
不管是人工,还是借助工具自动)
,不利于云时代的动态弹性调整,而且整体的可用性强依赖于中心节点,一旦中心节点(
中心集群)
全挂掉,系统就不可用了。
- 应用场景4:分布式锁
Order └── 3456890 ├── 000001 ├── 000002 └── 000003
原理:以多个程序运行实例同时在处理订单3456890
为例,每个程序运行实例处理前,创建一个临时顺序节点,然后检查自己创建的节点是否为最小,如果不是,表明没抢到锁,如果是,表示抢到了锁,抢到锁的程序处理完以后,删除该节点表示释放锁。同时,其它处于等候状态的程序,为了实时得到锁的释放通知,均监听父节点Order/3456890
的子节点变化,发现子节点变化时,重复刚才的检测过程,直到自己创建的节点变成最小为止。与redis SETNX
之类的分布式锁相比,zk
的分布式锁,还能实现解决Top N
之类的有限资源竞争问题(类似并发中的信号量)。比如:一堆程序要打印,但是只有2台打印机(或者打印队列的长度只有2,最多同时只能允许2个程序提交打印任务), 类似刚才的思路 ,可以检测最小的前2个节点,只有创建最小前2个节点的程序,才认为是拿到了信号,允许提交打印任务。
- 应用场景5:分布式队列
Queue └── Queue1 ├── 000001 ├── 000002 └── 000003
如上图,创建Queue/Queue1
做为一个队列(或Topic
),然后每创建一个顺序节点,视为一条消息(节点存储的数据即为消息内容),生产者每次创建一个新节点,做为消息发送,消费者监听Queue1
的子节点变化(或定时轮询),每次取最小节点当做消费消息,处理完后,删除该节点。相当于实现了一个FIFO
(先进先出)的队列。注:zk
框架强制的是CP
(一致性),而非专为高并发、高性能场景设计的,如果在高并发,qps
很高的情况下,分布式队列需酌情考虑。
- 应用场景7:生成分布式唯一
id
Order └── OrderId ├── 000001 ├── 000002 └── 000003
思路:每次要生成一个新id
时,创建一个持久顺序节点,创建操作返回的节点序号,即为新id
,然后把比自己节点小的删除即可。
终篇
目前很多开源项目,几乎都是或多或少依赖zookeeper
,比如:dubbo,disconf,kafka,...
分布式环境中,zk
能用于什么场景,基本上取决于开发人员的想象力!