目录
Zookeeper介绍
什么是zookeeper
-
Zookeeper是一个分布式的、高性能的、开源的分布式系统的协调(Coordination)服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的一个重要的组件。
-
它是一个为分布式应用提供一致性服务的软件。
zookeeper应用场景
zookeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能,高可用,且具有严格属性访问控制能力的分布式协调存储服务。
- 维护配置信息
- 分布式锁服务
- 集群管理
- 生成分布式唯一ID
维护配置信息
- java编程经常会遇到配置项,比如数据库的url,schema,user和password等。
- 通常这些配置项会放置在配置文件中,在将配置文件放置在服务器上当需要更改配置的时,需要去服务器上修改对应的配置信息文件。
- 随着分布式系统的兴起,由于许多服务都需要使用到该配置文件,因此有必须保证该配置服务的高可用性和各台服务器上配置数据的一致性。通常会将配置文件部署在一个集群上,此时如果在一台一台服务器逐个的修改配置文件将是非常繁琐的一个操作。因此就需要一种服务,能够高效快速且可靠的完成配置项的更待等操作,并能够保证各个配置项在每一台服务器上的数据一致性。
- zookeeper就可以提供这样一种服务,其使用Zab这种一致性协议来保证一致性。现在有很多开源项目使用zookeeper来维护配置,比如Kafka。
分布式锁服务
一个集群是一个分布式系统,有多台服务器组成。为了提高并发度和可靠性,多台服务器运行着同一种服务。当多个服务在运行时就需要协调各服务的进度,有时候需要保证当某个服务在进行某个操作时,其他的服务都不能进行该操作,即对该操作进行加锁,如果当前机器挂掉后,并释放fail over到其他的机器继续执行该服务。
集群管理
一个集群优势会因为各种软硬件故障或者网络故障,出现某种服务器挂掉而被移除集群,而某些服务器加入到集群中的情况,zookeeper会将这些服务器加入/移出的情况下通知给集群汇总的其他正常工作的服务器,以及时调用存储和计算等任务的分配和执行等。此外zookeeper还会对故障的服务器做出诊断并尝试修复。
生成分布式唯一ID
- 在过去的单库单表型系统中,通常可以使用数据库字段自带的
auto_increment
属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的
auto_increment属性来唯一标识一条记录了。 - 此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。做法如下:每次要生成一个新Id时,创建一个持久顺序节点,创建操作返回的节点序号,即为新Id,然后把比自己节点小的删除即可.
zookeeper的设计目标
zookeeper致力于为分布式应用提供一个高性能,高可用,具有严格顺序访问控制能力的分布式协调服务。
1、高性能
zookeeper将全量数据存储在内存中,并直接服务与客户端的所有非事务请求,尤其适合用于以读为主的应用场景。
2、高可用
zookeeper一般以集群的范式对外提供服务,一般3-5台机器就可以组成一个可用的zookeeper集群,每一台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信。只要集群中超过一旦的机器都在工作,那么这个集群就能够正常对外服务;
3、严格访问数据
对于客户端的每一个更新请求,Zookeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事物操作的先后顺序。
Zookeeper的数据模型
zookeeper数据结构
- Zookeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一颗树,每一个节点称做一个
ZNode
。 - 每一个Znode默认能够存储1MB的数据,每个
ZNode
都可以通过其路径唯一标识。
如何来描述一个ZNode呢?一个znode大体上分为3部分:
- 节点的数据:即znode data(节点path,节点data的关系)就像是map中(key,value)的关系
- 节点的子节点children
- 节点的状态stat:用来描述当前节点的创建,修改记录,包括cZxid、ctime等。
zookeeper节点类型
zookeeper中的节点有两种类型,一种是临时节点和永久节点。节点类型在创建是即被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临
时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的Znode都会绑定到
一个客户端会话,但他们对所有的客户端还是可见的。另外,ZooKeeper的临时节
点不允许拥有子节点。 - 持久化节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作
的时候,他们才能被删除.
zookeeper 单机安装
- 拉取镜像
docker search zookeeper
docker pull zookeeper
docker images //查看下载的本地镜像
docker inspect zookeeper //查看zookeeper详细信息
- 启动
docker run -d -e TZ="Asia/Shanghai" -p 2181:2181 -v /root/docker/zookeeper:/data --name zookeeper --restart always zookeeper
-e TZ="Asia/Shanghai" # 指定上海时区
-d # 后台启动
-p 2181:2181 # 对端口进行映射,将本地2181端口映射到容器内部的2181端口
--name # 设置创建的容器名称
-v # 挂载容器卷
--restart always #始终重新启动zookeeper
- 查看容器
docker ps
- 进入容器
docker exec -it zookeeper bash
- 启动客户端
./bin/zkCli.sh
zookeeper常用的Shell命令
新增节点
create [-s] [-e] path data
# 其中 -s 为有序节点, -e 临时节点
- 创建持久化节点并写入数据:
create /hadoop "123456"
- 创建持久化有序节,此时创建的节点名为指定节点名+自增序号
//创建一个持久化节点
[zk: localhost:2181(CONNECTED) 1] create /hadoop "123456"
//通过get命令获取该节点的值
[zk: localhost:2181(CONNECTED) 2] get /hadoop
//创建一个有序的节点
[zk: localhost:2181(CONNECTED) 4] create -s /a "aa"
Created /a0000000001
//获取路径的值
[zk: localhost:2181(CONNECTED) 5] get /a0000000001
aa
//创建临时节点并获取值
[zk: localhost:2181(CONNECTED) 1] create -e /tmp "tmp"
Created /tmp
[zk: localhost:2181(CONNECTED) 2] get /tmp
tmp
- 创建临时节点,临时节点会在回话过期后被删除;
[zk: localhost:2181(CONNECTED) 6] create -s -e /aa 'aaa'
Created /aa0000000004
[zk: localhost:2181(CONNECTED) 7] create -s -e /bb 'bbb'
Created /bb0000000005
[zk: localhost:2181(CONNECTED) 8] create -s -e /cc 'ccc'
Created /cc0000000006
更新节点
更新节点的命令是 set ,可以直接进行修改,如下:
[zk: localhost:2181(CONNECTED) 3] set /hadoop "345"
cZxid = 0x4
ctime = Thu Dec 12 14:55:53 CST 2019
mZxid = 0x5
mtime = Thu Dec 12 15:01:59 CST 2019
pZxid = 0x4
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
也可以基于版本号来进行更改,此时类似于乐观锁机制,当你传入的数据版本号(dataVersion)和当前节点的数据版本号不符合时,zookeeper会拒绝本次修改:
[zk: localhost:2181(CONNECTED) 10] set /hadoop "3456" 1
version No is not valid : /hadoop
删除节点
删除节点的语法如下:
delete path [version]
和更新节点数据一样,也可以传入版本号,当你传入的数据版本号 (dataVersion)
和当前节点的数据版本号不符合时,zookeeper 不会执行删除操作。
[zk: localhost:2181(CONNECTED) 36] delete /hadoop 0
version No is not valid : /hadoop #无效的版本号
[zk: localhost:2181(CONNECTED) 37] delete /hadoop 1
[zk: localhost:2181(CONNECTED) 38]
要想删除某个节点及其所有后代节点,可以使用递归删除,命令为 rmr path
。
查看节点
get path
[zk: localhost:2181(CONNECTED) 1] get /hadoop
123456
cZxid = 0x4
ctime = Thu Dec 12 14:55:53 CST 2019
mZxid = 0x4
mtime = Thu Dec 12 14:55:53 CST 2019
pZxid = 0x4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
节点各个属性如下表。起哄一个重要的概念是Zxid(ZooKeeper Transaction Id),ZooKeeper节点的每一个更改都具唯一的Zxid,如果Zxid1小于Zxid2,则Zxid1的更改发生在Zxid2更改之前。
状态属性 | 节点说明 |
---|---|
cZxid | 数据节点创建时的事务ID |
ctime | 数据节点创建世的时间 |
mZxid | 数据节点最后一个更新是的事务ID |
mtime | 数据节点最后一个跟新时的时间 |
pZxid | 数据节点的子节点最后一个被修改时的事务ID |
cversion | 子节点的更改次数 |
dataVerion | 节点数据的更改次数 |
aclVersion | 节点ACL的更改次数 |
ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的SeeesionID;如果是持久节点,则该属性值为0 |
dataLength | 数据内容的长度 |
numChildren | 数据节点当前的子节点个数 |
查看节点状态
可以使用 stat 命令查看节点状态,它的返回值和 get
命令类似,但不会返回
节点数据
[zk: localhost:2181(CONNECTED) 2] stat /hadoop
cZxid = 0x4
ctime = Thu Dec 12 14:55:53 CST 2019
mZxid = 0x4
mtime = Thu Dec 12 14:55:53 CST 2019
pZxid = 0x4
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 6
numChildren = 0
查看节点列表
查看节点列表有 ls path
和 ls2 path
两个命令,后者是前者的增强,不仅可
以查看指定路径下的所有节点,还可以查看当前节点的信息
[zk: localhost:2181(CONNECTED) 0] ls /
[cluster, controller_epoch, brokers, storm, zookeeper, admin, ...]
[zk: localhost:2181(CONNECTED) 1] ls2 /
[cluster, controller_epoch, brokers, storm, zookeeper, admin, ....]
cZxid = 0x0
ctime = Thu Jan 01 08:00:00 CST 1970
mZxid = 0x0
mtime = Thu Jan 01 08:00:00 CST 1970
pZxid = 0x130
cversion = 19
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 0
numChildren = 11
监听器get path [watch]
使用 get path [watch]
注册的监听器能够在节点内容发生改变的时候,向客
户端发出通知。需要注意的是 zookeeper 的触发器是一次性的 (One-time trigger),即
触发一次后就会立即失效。
[zk: localhost:2181(CONNECTED) 4] get /hadoop watch
[zk: localhost:2181(CONNECTED) 5] set /hadoop 45678
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点
值改变
监听器stat path [watch]
使用 stat path [watch] 注册的监听器能够在节点状态发生改变的时候,向客
户端发出通知
[zk: localhost:2181(CONNECTED) 7] stat /hadoop watch
[zk: localhost:2181(CONNECTED) 8] set /hadoop 112233
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/hadoop #节点
值改变
监听器ls\ls2 path [watch]
使用 ls path [watch] 或 ls2 path [watch] 注册的监听器能够监听该节点下
所有子节点的增加和删除操作。
[zk: localhost:2181(CONNECTED) 9] ls /hadoop watch
[]
[zk: localhost:2181(CONNECTED) 10] create /hadoop/yarn "aaa"
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hadoop
zookeeper权限控制
zookeeper类似于文件系统,client可以创建节点,更新节点,删除节点,那么如何做到节点的权限的控制呢?zookeeper的access control list 访问控制列表可以自耦到这一点。
acl权限控制,使用scheme:id:permission来表示,主要涵盖3个方面:
-
权限模式(scheme):授权的策略
-
授权对象(id):授权的对象
-
权限(permission):授予的权限
其特性如下:
- zookeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
- 每个znode支持设置多种权限控制方案和多个权限
- 子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
例如:
setAcl /test2 ip:192.168.60.130:crwda // 将节点权限设置为Ip:192.168.60.130
的客户端可以对节点进行增、删、改、查、管理权限
权限模式
采用何种方式授权
授权的对象
-
给谁授权
-
授权对象ID是指,权限赋予的实体例如:ip地址或用户。
授予的权限
-
授予什么权限
-
create、delete、read、writer、admin也就是增、删、改、查、管理权限,这5中权限简写为cdrwa、注意:这5中权限中,delete是指对子节点的删除权限,其他4种权限值对自身节点的权限操作。
权限 | ACL简写 | 描述 |
---|---|---|
create | c | 可以创建子节点 |
delete | d | 可以删除子节点(仅下一级节点) |
read | r | 可以读取节点数据及显示子节点列表 |
write | w | 可以设置节点数据 |
admin | a | 可以设置节点访问控制列表权限 |
授权的相关命令
案例
world授权模式
命令:
setAcl <path> world:anyone:<acl>
操作:
[zk: localhost:2181(CONNECTED) 1] create /node1 "node1"
Created /node1
[zk: localhost:2181(CONNECTED) 2] getAcl /node1
'world,'anyone #world方式对所有用户进行授权
: cdrwa #增、删、改、查、管理
[zk: localhost:2181(CONNECTED) 3] setAcl /node1 world:anyone:cdrwa
cZxid = 0x2
ctime = Fri Dec 13 22:25:24 CST 2019
mZxid = 0x2
mtime = Fri Dec 13 22:25:24 CST 2019
pZxid = 0x2
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
IP授权模式:
命令
setAcl <path> ip:<ip>:<acl>
操作
[zk: localhost:2181(CONNECTED) 18] create /node2 "node2"
Created /node2
[zk: localhost:2181(CONNECTED) 23] setAcl /node2
ip:192.168.60.129:cdrwa
cZxid = 0xe
ctime = Fri Dec 13 22:30:29 CST 2019
mZxid = 0x10
mtime = Fri Dec 13 22:33:36 CST 2019
pZxid = 0xe
cversion = 0
dataVersion = 2
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 20
numChildren = 0
[zk: localhost:2181(CONNECTED) 25] getAcl /node2
'ip,'192.168.60.129
: cdrwa
#使用IP非 192.168.60.129 的机器
[zk: localhost:2181(CONNECTED) 0] get /node2
Authentication is not valid : /node2 #没有权限
注意:远程登录zookeeper命令:./zkCli.sh -server ip
Auth授权模式:
命令
addauth digest <user>:<password> #添加认证用户
setAcl <path> auth:<user>:<acl>
案例
[zk: localhost:2181(CONNECTED) 2] create /node3 "node3"
Created /node3
#添加认证用户
[zk: localhost:2181(CONNECTED) 4] addauth digest itcast:123456
[zk: localhost:2181(CONNECTED) 1] setAcl /node3 auth:itcast:cdrwa
cZxid = 0x15
ctime = Fri Dec 13 22:41:04 CST 2019
mZxid = 0x15
mtime = Fri Dec 13 22:41:04 CST 2019
pZxid = 0x15
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 0] getAcl /node3
'digest,'itcast:673OfZhUE8JEFMcu0l64qI8e5ek=
: cdrwa
#添加认证用户后可以访问
[zk: localhost:2181(CONNECTED) 3] get /node3
node3
cZxid = 0x15
ctime = Fri Dec 13 22:41:04 CST 2019
mZxid = 0x15
mtime = Fri Dec 13 22:41:04 CST 2019
pZxid = 0x15
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
Digest授权模式:
命令
setAcl <path> digest:<user>:<password>:<acl>
这里的密码是经过SHA1及BASE64处理的密文,在SHELL中可以通过以下命令计算:
echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl
base64
先来计算一个密文
echo -n itheima:123456 | openssl dgst -binary -sha1 | openssl base64
案例:
[zk: localhost:2181(CONNECTED) 4] create /node4 "node4"
Created /node4
#使用是上面算好的密文密码添加权限:
[zk: localhost:2181(CONNECTED) 5] setAcl /node4
digest:itheima:qlzQzCLKhBROghkooLvb+Mlwv4A=:cdrwa
cZxid = 0x1c
ctime = Fri Dec 13 22:52:21 CST 2019
mZxid = 0x1c
mtime = Fri Dec 13 22:52:21 CST 2019
pZxid = 0x1c
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
[zk: localhost:2181(CONNECTED) 6] getAcl /node4
'digest,'itheima:qlzQzCLKhBROghkooLvb+Mlwv4A=
: cdrwa
[zk: localhost:2181(CONNECTED) 3] get /node4
Authentication is not valid : /node4 #没有权限
[zk: localhost:2181(CONNECTED) 4] addauth digest itheima:123456 #添加
认证用户
[zk: localhost:2181(CONNECTED) 5] get /node4
1 #成功读取数据
cZxid = 0x1c
ctime = Fri Dec 13 22:52:21 CST 2019
mZxid = 0x1c
mtime = Fri Dec 13 22:52:21 CST 2019
pZxid = 0x1c
cversion = 0
dataVersion = 0
aclVersion = 1
ephemeralOwner = 0x0
多种模式授权:
同一个节点可以同时使用多种模式授权
[zk: localhost:2181(CONNECTED) 0] create /node5 "node5"
Created /node5
[zk: localhost:2181(CONNECTED) 1] addauth digest itcast:123456 #添加认
证用户
[zk: localhost:2181(CONNECTED) 2] setAcl /node5
ip:192.168.60.129:cdra,auth:itcast:cdrwa,digest:itheima:qlzQzCLKhBROgh
kooLvb+Mlwv4A=:cdrwa
acl 超级管理员
- zookeeper的权限管理模式有一种叫做super,该模式提供一个超管可以方便的访问
任何权限的节点 - 假设这个超管是:super:admin,需要先为超管生成密码的密文:
echo -n super:admin | openssl dgst -binary -sha1 | openssl base64
那么打开zookeeper目录下的/bin/zkServer.sh服务器脚本文件,找到如下一行:
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-
Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
这就是脚本中启动zookeeper的命令,默认只有以上两个配置项,我们需要加一个
超管的配置项
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-
Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-
Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBv
st5y6rkB6HQs="\
-cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT"
2>&1 < /dev/null &
之后启动zookeeper,输入如下命令添加权限:
addauth digest super:admin #添加认证用户
go操作zk
安装
原本的https://github.com/samuel/go-zookeeper
已经停止维护了,并且要求转到:
go get github.com/go-zookeeper/zk
操作
增删改查
package main
/**
客户端doc地址:github.com/samuel/go-zookeeper/zk
**/
import (
"fmt"
"time"
zk "github.com/samuel/go-zookeeper/zk"
)
/**
* 获取一个zk连接
* @return {[type]}
*/
func getConnect(zkList []string) (conn *zk.Conn) {
conn, _, err := zk.Connect(zkList, 10*time.Second)
if err != nil {
fmt.Println(err)
}
return
}
/**
* 测试连接
* @return
*/
func test1() {
zkList := []string{"localhost:2181"}
conn := getConnect(zkList)
defer conn.Close()
var flags int32 = 0
//flags有4种取值:
//0:永久,除非手动删除
//zk.FlagEphemeral = 1:短暂,session断开则改节点也被删除
//zk.FlagSequence = 2:会自动在节点后面添加序号
//3:Ephemeral和Sequence,即,短暂且自动添加序号
conn.Create("/go_servers", nil, flags, zk.WorldACL(zk.PermAll)) // zk.WorldACL(zk.PermAll)控制访问权限模式
time.Sleep(20 * time.Second)
}
/*
删改与增不同在于其函数中的version参数,其中version是用于 CAS支持
func (c *Conn) Set(path string, data []byte, version int32) (*Stat, error)
func (c *Conn) Delete(path string, version int32) error
demo:
if err = conn.Delete(migrateLockPath, -1); err != nil {
log.Error("conn.Delete(\"%s\") error(%v)", migrateLockPath, err)
}
*/
/**
* 测试临时节点
* @return {[type]}
*/
func test2() {
zkList := []string{"localhost:2181"}
conn := getConnect(zkList)
defer conn.Close()
conn.Create("/testadaadsasdsaw", nil, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
time.Sleep(20 * time.Second)
}
/**
* 获取所有节点
*/
func test3() {
zkList := []string{"localhost:2181"}
conn := getConnect(zkList)
defer conn.Close()
children, _, err := conn.Children("/go_servers")
if err != nil {
fmt.Println(err)
}
fmt.Printf("%v \n", children)
}
func main() {
test3()
}
zookeeper事件监听机制
watcher概念
- zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对
象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变
等),会实时、主动通知所有订阅者 - zookeeper采用了Watcher机制实现数据的发布/订阅功能。该机制在被订阅对
象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻
了客户端压力。 - watcher机制实际上与观察者模式类似,也可看作是一种观察者模式在分布式场
景下的实现方式。
wathcer架构
Watcher实现由三个部分组成:
- Zookeeper服务端
- Zookeeper客户端
- 客户端的ZKWatchManager对象
客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管
理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,
接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数
据发布/订阅流程。
wahcher特性
watcher接口设计
Watcher是一个接口,任何实现了Watcher接口的类就是一个新的Watcher。
Watcher内部包含了两个枚举类:KeeperState、EventTyp
Watcher通知状态(KeeperState)
KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。路径为
org.apache.zookeeper.Watcher.Event.KeeperState,是一个枚举类,其枚举属性
如下:
Watcher事件类型(EventType)
EventType是数据节点(znode)发生变化时对应的通知类型。EventType变化时
KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时,
EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,
是一个枚举类,枚举属性如下:
注:客户端接收到的相关事件通知中只包含状态及类型等信息,不包括节点变化前后的
具体内容,变化前的数据需业务自身存储,变化后的数据需调用get等方法重新获取;
捕获相应的事件
在zookeeper中采用zk.getChildren(path, watch)、zk.exists(path, watch),zk.getData(path, watcher, stat)这样的方式为某个znode注册监听。
下表以node-x节点为例,说明调用的注册方法和可监听事件间的关系
注册watcher的方法
客服端与服务器的连接状态
KeeperState 通知状态
SyncConnected:客户端与服务器正常连接时
Disconnected:客户端与服务器断开连接时
Expired:会话session失效时
AuthFailed:身份认证失败时
事件类型为:None
检查节点是否存在
// 使用连接对象的监视器
exists(String path, boolean b)
// 自定义监视器
exists(String path, Watcher w)
// NodeCreated:节点创建
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
- path- znode路径。
- b- 是否使用连接对象中注册的监视器。
- w-监视器对象。
查看节点
// 使用连接对象的监视器
getData(String path, boolean b, Stat stat)
// 自定义监视器
getData(String path, Watcher w, Stat stat)
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
- path- znode路径。
- b- 是否使用连接对象中注册的监视器。
- w-监视器对象。
- stat- 返回znode的元数据。
查看子节点
// 使用连接对象的监视器
getChildren(String path, boolean b)
// 自定义监视器
getChildren(String path, Watcher w)
// NodeChildrenChanged:子节点发生变化
// NodeDeleted:节点删除
- path- znode路径。
- b- 是否使用连接对象中注册的监视器。
- w-监视器对象。
配置中心案例
工作中有这样的一个场景: 数据库用户名和密码信息放在一个配置文件中,应用
读取该配置文件,配置文件信息放入缓存。
若数据库的用户名和密码改变时候,还需要重新加载缓存,比较麻烦,通过
ZooKeeper可以轻松完成,当数据库发生变化时自动完成缓存同步。
设计思路:
- 连接zookeeper服务器
- 读取zookeeper中的配置信息,注册watcher监听器,存入本地变量
- 当zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获数据变化事件
- 重新获取配置信息
生成分布式唯一ID
在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment
属性来自动为每条记录生成一个唯一的ID。
但是分库分表后,就无法在依靠数据库的auto_increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环境下生成全局唯一ID。
设计思路:
- 连接zookeeper服务器
- 指定路径生成临时有序节点
- 取序列号及为分布式环境下的唯一ID
unc GetUniqueId() string {
defaultPath := "/_UniqueId"
id, _ := connect.Create(defaultPath, []byte(""), 3, zk.WorldACL(zk.PermAll))
return id
}
分布式锁
分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同
工具ZooKeeper,当然也有着标准的实现方式。下面介绍在zookeeper中如何实现排他
锁。
设计思路:
- 每个客户端往/Locks下创建临时有序节点/Locks/Lock
000000001 - 客户端取得/Locks下子节点,并进行排序,判断排在最前面的是否为自己,如果自己的
锁节点在第一位,代表获取锁成功 - 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点
Lock 000000001 - 当前一位锁节点(Lock
000000002)的逻辑 - 监听客户端重新执行第2步逻辑,判断自己是否获得了锁
zookeeper集群搭建
- 将配置文件也挂载出来:
mkdir -p /mydata/zookeeper/data # 数据挂载目录
mkdir -p /mydata/zookeeper/conf # 配置挂载目录
mkdir -p /mydata/zookeeper/logs # 日志挂载目录
mkdir -p /mydata/zookeeper/datalog
- 添加ZooKeeper配置文件,在挂载配置文件目录(/mydata/zookeeper/conf)下,新增zoo.cfg 配置文件,配置内容如下:
# 三台机器分别执行添加
# 客户端连接端口,通常不做修改
clientPort=2181
# Zookeeper保存数据的目录,默认情况下,Zookeeper将写数据的日志文件也保存在这个目录里
dataDir=/data
# 客户端连接端口,通常不做修改
dataLogDir=/datalog
# 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。tickTime以毫秒为单位
tickTime=2000
# 集群中的follower服务器(F)与leader服务器(L)之间初始连接时能容忍的最多心跳数(tickTime的数量)
initLimit=5
# 集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数(tickTime的数量
syncLimit=2
# 默认值为3,不支持以系统属性方式配置。用于配置Zookeeper在自动清理的时候需要保留的快照数据文件数量和对应的事务日志文件。此参数的最小值为3,如果配置的值小于3会自动调整到3
autopurge.snapRetainCount=3
# 默认值为3,不支持以系统属性方式配置。用于配置Zookeeper在自动清理的时候需要保留的快照数据文件数量和对应的事务日志文件。此参数的最小值为3,如果配置的值小于3会自动调整到3
autopurge.purgeInterval=0
# 默认为60,不支持以系统属性方式配置。从Socket层面限制单个客户端与单台服务器之间的并发连接数,即以ip地址来进行连接数的限制。
# 如果设置为0,表示不做任何限制。仅仅是单台客户端与单个Zookeeper服务器连接数的限制,不能控制所有客户端的连接数总和
maxClientCnxns=60
# 3.5.0中的新功能:当设置为false时,可以在复制模式下启动单个服务器,单个参与者可以使用观察者运行,并且群集可以重新配置为一个节点,并且从一个节点。
# 对于向后兼容性,默认值为true。可以使用QuorumPeerConfig的setStandaloneEnabled方法或通过将“standaloneEnabled = false”或“standaloneEnabled = true”添加到服务器的配置文件来设置它。
standaloneEnabled=false
# 内嵌的管理控制台,停用这个服务
admin.enableServer=false
# 如果连接一直超时可以加这个选项
quorumListenOnAllIPs=true
# 集群中服务的列表
server.1=0.0.0.0:2888:3888;2181
server.2=node2_ip:2888:3888;2181
server.3=node3_ip:2888:3888;2181
重点:3.5版本的zookeeper之后, 其服务器自己本身对应的ip为0.0.0.0。
- 设置myid标识
echo 1 > /mydata/zookeeper/conf/myid
1、2、3 三台机器分别执行
zookeeper选举也会根据myid的大小进行投票master
- 启动容器
docker run -d --name zookeeper --privileged=true -p 2181:2181 -p 2888:2888 -p 3888:3888 -v /zookeeper/data:/data -v /zookeeper/conf:/conf -v /zookeeper/logs:/logs -v /zookeeper/datalog:/datalog zookeeper:3.5.7
- 进入容器内部,验证容器状态,然后退出
# 进入zookeeper 容器内部
docker exec -it zookeeper /bin/bash
# 检查容器状态
docker exec -it zookeeper /bin/bash ./bin/zkServer.sh status
# 进入控制台
docker exec -it zookeeper zkCli.sh
一致性协议:zab协议
zab协议 的全称是 Zookeeper Atomic Broadcast (zookeeper原子广播)。zookeeper 是通过 zab协议来保证分布式事务的最终一致性。
zab广播模式工作原理,通过类似两阶段提交协议的方式解决数据一致性:
- leader从客户端收到一个写请求
- leader生成一个新的事务并为这个事务生成一个唯一的ZXID
- leader将这个事务提议(propose)发送给所有的follows节点
- follower节点将收到的事务请求加入到历史队列(history queue)中,并发送ack给
leader - 当leader收到大多数follower(半数以上节点)的ack消息,leader会发送commit请
求 - 当follower收到commit请求时,从历史队列中将事务请求commit
zookeeper的leader选举
服务器状态
- looking:寻找leader状态。当服务器处于该状态时,它会认为当前集群中没有
- leader,因此需要进入leader选举状态。
- leading: 领导者状态。表明当前服务器角色是leader。
- following: 跟随者状态。表明当前服务器角色是follower。
- observing:观察者状态。表明当前服务器角色是observer。
服务器启动时期的leader选举
在集群初始化阶段,当有一台服务器server1启动时,其单独无法进行和完成
leader选举,当第二台服务器server2启动时,此时两台机器可以相互通信,每台机器都
试图找到leader,于是进入leader选举过程。选举过程如下:
- 每个server发出一个投票。由于是初始情况,server1和server2都会将自己作为
leader服务器来进行投票,每次投票会包含所推举的服务器的myid和zxid,使用
(myid, zxid)来表示,此时server1的投票为(1, 0),server2的投票为(2, 0),然后各自
将这个投票发给集群中其他机器。 - 集群中的每台服务器接收来自集群中各个服务器的投票。
- 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,pk
规则如下- 优先检查zxid。zxid比较大的服务器优先作为leader。
- 如果zxid相同,那么就比较myid。myid较大的服务器作为leader服务器。
- 对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较
两者的zxid,均为0,再比较myid,此时server2的myid最大,于是更新自己的投票
为(2, 0),然后重新投票,对于server2而言,其无须更新自己的投票,只是再次向集
群中所有机器发出上一次投票信息即可。
- 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到
相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受
了(2, 0)的投票信息,此时便认为已经选出了leader - 改变服务器状态。一旦确定了leader,每个服务器就会更新自己的状态,如果是
follower,那么就变更为following,如果是leader,就变更为leading。
服务器运行时期的Leader选举
在zookeeper运行期间,leader与非leader服务器各司其职,即便当有非leader
服务器宕机或新加入,此时也不会影响leader,但是一旦leader服务器挂了,那么整个集
群将暂停对外服务,进入新一轮leader选举,其过程和启动时期的Leader选举过程基本
一致。
假设正在运行的有server1、server2、server3三台服务器,当前leader是
server2,若某一时刻leader挂了,此时便开始Leader选举。选举过程如下:
- 变更状态。leader挂后,余下的服务器都会将自己的服务器状态变更为looking,然
后开始进入leader选举过程。 - 每个server会发出一个投票。在运行期间,每个服务器上的zxid可能不同,此时假定
server1的zxid为122,server3的zxid为122,在第一轮投票中,server1和server3
都会投自己,产生投票(1, 122),(3, 122),然后各自将投票发送给集群中所有机器。 - 接收来自各个服务器的投票。与启动时过程相同
- 处理投票。与启动时过程相同,此时,server3将会成为leader。
- 统计投票。与启动时过程相同。
- 改变服务器的状态。与启动时过程相同。
observer角色及其配置
observer角色特点:
- 不参与集群的leader选举
- 不参与集群中写数据时的ack反馈
为了使用observer角色,在任何想变成observer角色的配置文件中加入如下配
置:
peerType=observer
并在所有server的配置文件中,配置成observer模式的server的那行配置追
加:observer,例如:
server.3=192.168.60.130:2289:3389:observer
zookeeper四字监控命
zooKeeper支持某些特定的四字命令与其的交互。它们大多是查询命令,用来
获取 zooKeeper服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向
zooKeeper提交相应的命令。 zooKeeper常用四字命令见下表 所示: