Zookeeper在哪些系统中使用,又是怎么用的呢?
zookeeper作为一个开源的分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。
这里不再具体提zk的选主逻辑,paxos协议什么的,只开始讲一些用法。
kazoo是一个封装了zookeeper操作的python库,其中除了提供底层的zookeeper接口外,还提供了一些更高级别的封装。
zookeeper的基本操作
zookeeper主要操作分以下几种:
[if !supportLists]1. [endif]创建节点
[if !supportLists]2. [endif]读节点数据
[if !supportLists]3. [endif]更新节点数据
[if !supportLists]4. [endif]删除节点
[if !supportLists]5. [endif]监控节点变化
其中节点被组织成目录树的形式,每个节点下面都可以有一些子节点。
节点可以是以下四种类型:
PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
EPHEMERAL_SEQUENTIAL:临时自动编号节点。
监控节点变化时,可以监控一个节点的变化,也可以监控一个节点所有子节点的变化。
ZK应用统一命名服务,在分布式系统中,经常需要给一个资源生成一个唯一的ID,在没有中心管理结点的情况下生成这个ID并不是一件很容易的事儿。zk就提供了这样一个命名服务。一般是使用create方法,创建一个自动编号的节点。
配置管理
主要用于多个结点共享配置,并且在配置发生更新时,利用zk可以让这些使用了这些配置的结点获得通知,进行重新加载等操作。
集群管理
主要有两个方面:一是集群选主,二是资源定位。
集群选主是当一个集群会启动一主一备两个服务单元时,可以使用zk来选出一个主服务单元。 具体方法就是在一个节点下创建一个自动编号的临时结点,然后watch父节点,如果该临时节点成为父节点下编号最小的节点,则认为其成为了主服务单元。
在kazoo中,提供了 election相关的封装,使用极其简单。
zk = KazooClient(hosts='127.0.0.1:2181')
zk.start(10)
election = zk.Election("/electionpath", "my-identifier")
# blocks until the election is won, then calls
# my_leader_function()
election.run(my_leader_function)
在实际使用时有时候会遇到一些工作单元watch集群的master结点,当主从切换时,工作单元可能会需要重新连接到新的主节点以使工作能够继续。在kazoo中,暂没发现有方法能够直接使孩子watch Election的状态,暂未试验是否可以直接使用watch接口直接watch electionpath。不过即使不能通过watch electionpath解决,也可以在master切换之后,通过直接去修改另外一个固定位置的结点而工作单元都watch那个结点来解决。
资源定位主要是用于分布式系统中一些服务节点位置或者状态发生变化时,通知一些相关的需要知道的服务节点发生了这些变化,以便于其能够做出一定的响应。比如,一个rpc_server发生了故障迁移,这时就需要client重新能够发现并向新的地址、端口发起请求。
共享锁
实现起来和集群选主基本一致,都是创建一个自动编号的临时结点,然后watch父结点,判断自己是否是最小编号节点。
在kazoo中提供了 lock相关封装:
import os
import sys
import time
from kazoo.client import KazooClient
zk = KazooClient(hosts='127.0.0.1:2181')
zk.start(10)
lock = zk.Lock("/lockpath", "my-identifier")
with lock:
print "got lock"
time.sleep(10)
快速的运行两遍这个程序,会发现第二次运行的程序会等到第一次运行结束之后才会输出“got lock”.
队列管理
...
一些基本用法关于Watch:
前面讲集群资源定位时提到了watch,zk原生的watch都是通过get/get_children/exists等查询接口提供的,用户在查询的时候可以再设置一个watch函数,当有关心的事件触发时,watch函数会被调用。
例如,get方法中设置的watch函数会在数据发生更新或者删除时被触发。exists在节点的存活性发生变化时触发,而get_children则在子节点的存活性发生变化时触发。
另外,当watch函数触发后,用户需要重新重新设置watch函数,不然随后的事件再次发生时,将不会被触发。
仔细思考上述接口会发现,当一次watch事件触发之后,到再次设置watch函数之间,如果发生了watch的事件,这个事件是不会被触发的。虽然表面上这个接口会导致部分watch的事件丢失,但实际上由于设置watch和get/get_children/exists是同一个原子性的操作,故丢失的事件不会影响zookeeper上的数据与client端得到的数据的最终一致性。使用kazoo的DataWatch可以简化watch的使用,但有时候可能还是需要去猜测其watch的实现,并不如想象中那么好用。个人认为,好多时候,我们其实并不真正的需要watch,轮询可能会使代码更为清晰,更少出错