简介
ZooKeeper
是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次它们被实现时,都会有大量的工作来修复不可避免的错误和竞争条件。由于实现这些服务的困难,应用程序最初通常会略过这些服务,这使得它们在出现更改时变得脆弱,并且难以管理。即使正确地执行了这些服务,在部署应用程序时,这些服务的不同实现也会导致管理复杂性。
应用场景
zookeepepr
是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调存储服务。
1.维护配置信息
- 随着分布式系统的兴起,由于许多服务器都需要更改配置项,因此必须保证配置服务的高可用性和各台服务器上数据配置文件的数据一致性,通常会将配置文件部署在一个集群上,因此需要一种能够高速且可靠的完成配置项的更改操作,并能够保证各配置项在每台服务器上的数据一致性。
- Zookeeper可提供此种服务,其使用Zab这种一致性协议保证一致性。先有很多开源项目使用Zookeeper维护配置,比如hbase中客户端连接Zookeeper,获取hbase配置信息。kafka中使用Zookeeper维护broker信息,dubbo中广泛使用Zookeeper管理配置。
2.提供分布式锁
- 为了提高并发度和可靠性,多台服务器运行着同一种服务。当多个服务运行时需要协调各服务进度,有时候需要保证某个服务进行某个操作时其他服务不能进行该操作。如果当前机器宕掉,释放锁并fall over到其他机器上继续执行该服务。
3.集群管理
- 一个集群有时会因为各种软硬件故障或者网络故障,出现某些服务器挂掉而被移除集群,而某些服务器加入到集群的情况。Zookeeper会将这些服务器的加入/移除情况通知其他正常的服务器,以及调整存储和计算等任务的分配和执行等等。此外Zookeeper还会对故障的服务器做出诊断并尝试修复。
4.生成分布式唯一ID
- 单库单表,可以使用数据库自带auto_increment属性自动生成唯一ID。但是在分库分表环境下,无法依靠auto_increment属性来唯一标识一个记录,此时需要Zookeeper在分布式环境下生成全局唯一ID。
- 每次生成一个新ID时创建一个持久顺序节点,创建操作返回的阶段序号即为ID号,然后把比自己小的删除即可。
zookeeper设计目标
zookeeper致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
- 高性能:zookeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其用于以读为主的应用场景
- 高可用:zookeeper一般以集群的方式对外提供服务,一般
3~5
台机器就可以组成一个可用的 zookeeper集群了,每台机器都会在内存中维护当前的服务器状态,井且每台机器之间都相互保持着通信。只要集群中超过一半的机器都能够正常工作,那么整个集群就能够正常对外服务 - 按严格顺序访问:对于来自客户端的每个更新请求,zookeeper都会分配一个全局唯一的递增编号。这个编号反映了所有事务操作的先后顺序。
zookeeper的数据模型
zookeeper的数据结点可以视为树状结构(或目录),树中的各个结点被称为znode
(即zookeeper node
),一个znode
可以由多个子结点。zookeeper结点在结构上表现为树状;
znode,兼具文件和目录两种特点,即像文件一样维护着数据、元信息、ACL、时间戳等数据结构、又像目录一样可以作为路径标识的一部分。
znode的组成:
- 节点数据:即znode data(节点path、节点data)的关系就像是java map中(key,value)的关系
- 节点的子节点
- 节点的状态stat:用来描述当前节点的创建、修改记录、包括cZxid、ctime等
可以在zookeeper shell中使用get命令查看指定路径节点的data、stat信息:
属性说明:
cZxid:数据节点创建时的事务id
ctime:数据节点创建时的时间
mZxid:数据节点最后一次更新时的事务id
mtime:数据节点最后一次更新时的时间
pZxid:数据节点的子节点最后一次被修改时的事务id
cversion:子节点的更改次数
dataVersion:节点数据的更改次数
aclVersion:节点的ACL更改次数
ephemeralOwner:如果结点是临时结点,则表示创建该结点的会话的SessionID;如果是持久结点,该属性值为0
dataLength:数据内容的长度
numChildren:数据结点当前的子结点个数
节点类型
zookeeper中结点有两种,分别为临时节点和永久节点。节点类型在创建时被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于他们的会话,一旦会话结束,临时节点将自动删除,当然可以手动删除。虽然每个临时的znode都会绑定一个客户端会话,但他们对所有客户端是可见的,另外,zookeeper的临时节点不允许拥有子节点。
- 该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
单机安装
- zookeeper:zookeeper-3.4.10.tar.gz
- jdk:jdk-8u131-linux-x64.tar.gz
1.在centos
中使用 root
用户创建 zookeeper
用户,用户名:zookeeper
密码:zookeeper
useradd zookeeper
passwd zookeeper
su zookeeper
2.zookeeper
底层依赖于jdk,zookeeper
用户登录后,根目录下先进行jdk 的安装,jdk使用 jdk-8u131-linux-x64.tar.gz
tar -zxf tar.gz
3.配置jdk 环境变量
vi /etc/profile
JAVA_HOME=/home/zookeeper/jdk1.8.0_131
export JAVA_HOME
PATH=$JAVA_HOME/bin:$PATH
export PATH
souce /etc/profile
4.检测jdk安装
`java -version` // 如果反馈了Java信息,则成功
5.zookeeper
上传解压
`tar -zxf tar.gz`
6.为zookeeper
准备配置文件
# 进入conf目录
cd /home/zookeeper/zookeeper-3.4.10/conf
# 复制配置文件
cp zoo_sampe.cfg zoo.cfg
# zookeeper 根目录下创建data目录
mkdir data
# vi 配置文件中的dataDir
# 此路径用于存储zookeeper中数据的内存快照、及事务日志文件,虽然zookeeper是使用内存的,但是需要持久化一些数据来保证数据的安全,和redis一样
dataDir=/home/zookeeper/zookeeper-3.4.10/data
7.启动zookeeper
# 进入zookeeper的bin目录
cd /home/zookeeper/zookeeper-3.4.10/bin
# 启动zookeeper
./zkServer.sh start
# 启动: zkServer.sh start
# 停止: zkServer.sh stop
# 查看状态:zkServer.sh status
# 进入zookeeper 内部
./zkCli.sh
常用shell命令
[zkshell: 0] help
ZooKeeper host:port cmd args
get path [watch]
ls path [watch]
set path data [version]
delquota [-n|-b] path
quit
printwatches on|off
create path data acl
stat path [watch]
listquota path
history
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
deleteall path
setquota -n|-b val path
1.查询
get /hadoop 查看结点的数据和属性
stat /hadoop 查看结点的属性
2.创建
create [-s] [-e] path data # 其中 -s 为有序结点,-e 临时结点(默认是持久结点)
create /hadoop "123456" # 此时,如果quit退出后再./ZkCient.sh 登入
# 再用输入 get /hadoop 获取,结点依然存在(永久结点)
create -s /a "a" # 创建一个持久化有序结点,创建的时候可以观察到返回的数据带上了一个id get取数据时也要用/a+id才能读取数据
create -s /b "b" # 返回的值,id递增了
create -s -e /aa "aa" # 依然还会返回自增的id,quit后再进来,继续创建,id依然是往后推的
create /aa/xx # 继续创建结点,可以看到pZxid变化了
3.更新
更新结点的命令是set,可以直接进行修改,如下:
set path [version]
set /hadoop "345" # 修改结点值
set /hadoop "hadoop-x" 1 # 也可以基于版本号进行更改,类似于乐观锁,当传入版本号(dataVersion)
# 和当前结点的数据版本号不一致时,zookeeper会拒绝本次修改
4.删除
删除结点的语法如下:
delete path [version] 和 set 方法相似,也可以传入版本号
delete /hadoop # 删除结点
delete /hadoop 1 # 乐观锁机制,与set 方法一致
要想删除某个结点及其所有后代结点,可以使用递归删除,命令为 rmr path
5.查看结点列表
ls /hadoop # 可以查看结点的列表
ls2 /hadoop # 可以查看结点的列表以及目标结点的属性
ls / # 根节点
6.监听器get path [watch] | stat path [watch]
使用get path [watch]
注册的监听器能够在结点内容发生改变的时候,向客户端发出通知。需要注意的是zookeeper
的触发器是一次性的(One-time trigger
),即触发一次后就会立即失效
get /hadoop watch # get 的时候添加监听器,当值改变的时候,监听器返回消息
set /hadoop 45678 # 测试
7.ls\ls2 path [watch]
使用 ls path [watch] 或 ls2 path [watch]
注册的监听器能够监听该结点下所有子节点的增加和删除操作
ls /hadoop watch # 添加监听器
set /hadoop/node "node"
zookeeper的Acl权限控制
zookeeper
的 access control list
访问控制列表可以做到权限控制acl
权限控制,使用scheme:id:permission
来标识,主要涵盖3个方面:
- 权限模式(
scheme
):授权的策略 - 授权对象(
id
):授权的对象 - 权限(
permission
):授予的权限
其特性如下:
zookeeper
的权限控制是基于每个znode
结点的,需要对每个结点设置权限- 每个
znode
支持多种权限控制方案和多个权限 - 子结点不会继承父结点的权限,客户端无权访问某结点,但可能可以访问它的子结点
1.权限模式
- 采用何种方式授权
方案 | 描述 |
world | 只有一个用户: |
ip | 对客户端使用IP地址认证 |
auth | 使用已添加认证的用户认证 |
digest | 使用"用户名:密码"方式认证 |
2.授权对象
- 给谁授予权限
- 授权对象ID是指,权限赋予的实体,例如:IP地址或用户
3.授权的权限
- 授予什么权限
-
create、delete、read、writer、admin
也就是 增、删、查、改、管理权限,这5种权限简写为 c d r w a 。 - 这五种权限中,有的权限并不是对结点自身操作的例如:delete是指对子结点的删除权限
- 可以试图删除父结点,但是子结点必须删除干净,所以
delete
的权限也是很有用的
权限 | ACL简写 | 描述 |
create | c | 可以创建子结点 |
delete | d | 可以删除子结点(仅下一级结点) |
read | r | 可以读取结点数据以及显示子结点列表 |
write | w | 可以设置结点数据 |
admin | a | 可以设置结点访问控制权限列表 |
4.授权的命令
命令 | 使用方式 | 描述 |
getAcl | getAcl | 读取ACL权限 |
setAcl | setAcl | 设置ACL权限 |
addauth | addauth | 添加认证用户 |
./zkServer.sh -server ip 可以远程登录
5.world权限模式案例
getAcl /node // 读取权限信息
setAcl /node world:anyone:drwa // 设置权限(禁用创建子结点的权限)
6.ip模式
setAcl /hadoop ip:192.168.133.133:drwa
如果在两台不同的虚拟机中,另一台用远程连接的模式,进行上面这条命令,
那么只会有一台被授权,需要两台虚拟机一起授权的话需要用逗号将授权列表隔开:
setAcl /hadoop ip:192.168.133.133:cdrwa,ip:192.168.133.132:cdrwa
7.auth认证用户模式
addauth digest <user>:<password>
setAcl <path> auth:<user>:<acl>
create /hadoop "hadoop" # 初始化测试用的结点
addauth digest hsw:123456 # 添加认证用户
setAcl /hadoop auth:hsw:cdrwa # 设置认证用户
quit # 退出后再./zkCli.sh 进入
get /hadoop # 这个时候就没有权限了,需要再次认证
addauth digest hsw:123456 # 认证,密码错了的话 zookeeper 不会报错,但是不能认证
get /hadoop
8.Digest授权模式
setAcl <path> digest:<user>:<password>:<acl>
这里的密码是经过`SHA1`以及`BASE64`处理的密文,在shell 中可以通过以下命令计算:
echo -n <user>:<password> | openssl dgst -binary -sha1 | openssl base64
# 计算密码
echo -n hsw:12345 | openssl dgst -binary -sha1 | openssl base64
# 获取密码,设置权限列表
setAcl /hadoop digest:hsw:qUFSHxJjItUW/93UHFXFVGlvryY=:cdrwa
# 现在想要get /hadoop 需要登录了
addauth digest hsw:12345
get /hadoop
9.多种授权模式
setAcl /hadoop
ip:192.168.133.132:cdrwa,
auth:hadoop:cdrwa,
digest:itcast:673OfZhUE8JEFMcu0l64qI8e5ek=:cdrwa
acl 超级管理员
zookeeper
的权限管理模式有一种叫做super
,该模式提供一个超管,可以方便的访问任何权限的节点- 假设这个超管是
supper:admin
,需要为超管生产密码的密文echo -n super:admin | openssl dgst -binary -sha1 | openssl base64
- 那么打开
zookeeper
目录下/bin/zkServer.sh
服务器脚本文件,找到如下一行:
/nohup # 快速查找,可以看到如下
nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}"
"-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
- 这个就算脚本中启动
zookeeper
的命令,默认只有以上两个配置项,我们需要添加一个超管的配置项
"-Dzookeeper.DigestAuthenticationProvider.superDigest=
super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
- 修改后命令变成如下
nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}"
"-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
# 重起后,现在随便对任意节点添加权限限制
setAcl /hadoop ip:192.168.1.1:cdrwa # 这个ip并非本机
# 现在当前用户没有权限了
getAcl /hadoop
# 登录超管
addauth digest super:admin
# 强行操作节点
get /hadoop
zookeeper的 JavaAPI
zonde
是 zookeeper
集合的核心组件,zookeeper API
提供了一小组使用 zookeeper
集群来操作znode
的所有细节
客户端应该遵循以下步骤,与zookeeper
服务器进行清晰和干净的交互
- 连接到
zookeeper
服务器。zookeeper
服务器为客户端分配会话ID
- 定期向服务器发送心跳。否则,
zookeeper
服务器将过期会话ID
,客户端需要重新连接 - 只要会话
Id
处于活动状态,就可以获取/设置znode
- 所有任务完成后,断开与
zookeeper
服务器连接,如果客户端长时间不活动,则zookeeper
服务器将自动断开客户端
1.连接Zookeeper
Zookeeper(String connectionString, int sessionTimeout, watcher watcher)
-
connectionString
-zookeeper
主机 -
sessionTimeout
- 会话超时 -
watcher
- 实现"监听器" 对象。zookeeper
集合通过监视器对象返回连接状态
public static void main(String[] args) throws IOException, InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
ZooKeeper zookeeper = new ZooKeeper("192.168.133.133:2181", 5000, (WatchedEvent x) -> {
if (x.getState() == Watcher.Event.KeeperState.SyncConnected) {
System.out.println("连接成功");
countDownLatch.countDown();
}
});
countDownLatch.await();
System.out.println(zookeeper.getSessionId());
zookeeper.close();
}
2.新增节点
// 同步
create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
// 异步
create(String path, byte[] data, List<ACL> acl, CreateMode createMode,
AsynCallback.StringCallback callBack, Object ctx)
参数 | 解释 |
|
|
| 数据 |
| 要创建的节点的访问控制列表。 |
| 节点的类型,这是一个枚举 |
| 异步回调接口 |
| 传递上下文参数 |
示例:
// 枚举的方式
public static void createTest1() throws Exception{
String str = "node";
String s = zookeeper.create("/node", str.getBytes(),
//ZooDefs.Ids.READ_ACL_UNSAFE:world:anyone:r
ZooDefs.Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(s);
}
// 自定义的方式
public static void createTest2() throws Exception{
ArrayList<ACL> acls = new ArrayList<>();
Id id = new Id("ip","192.168.133.133");
acls.add(new ACL(ZooDefs.Perms.ALL,id));
zookeeper.create("/create/node4","node4".getBytes(),acls,CreateMode.PERSISTENT);
}
// auth
public static void createTest3() throws Exception{
zookeeper.addAuthInfo("digest","itcast:12345".getBytes());
zookeeper.create("/node5","node5".getBytes(),
ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.PERSISTENT);
}
// 自定义的方式
public static void createTest3() throws Exception{
// zookeeper.addAuthInfo("digest","itcast:12345".getBytes());
// zookeeper.create("/node5","node5".getBytes(),
// ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.PERSISTENT);
zookeeper.addAuthInfo("digest","itcast:12345".getBytes());
List<ACL> acls = new ArrayList<>();
Id id = new Id("auth","itcast");
acls.add(new ACL(ZooDefs.Perms.READ,id));
zookeeper.create("/create/node6","node6".getBytes(),
acls,CreateMode.PERSISTENT);
}
// digest
public static void createTest3() throws Exception{
List<ACL> acls = new ArrayList<>();
Id id = new Id("digest","itcast:qUFSHxJjItUW/93UHFXFVGlvryY=");
acls.add(new ACL(ZooDefs.Perms.READ,id));
zookeeper.create("/create/node7","node7".getBytes(),
acls,CreateMode.PERSISTENT);
}
// 异步
public static void createTest4() throws Exception{
zookeeper.create("/node12", "node12".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncCallback.StringCallback(){
/**
* @param rc 状态,0 则为成功,以下的所有示例都是如此
* @param path 路径
* @param ctx 上下文参数
* @param name 路径
*/
public void processResult(int rc, String path, Object ctx, String name){
System.out.println(rc + " " + path + " " + name + " " + ctx);
}
}, "I am context");
TimeUnit.SECONDS.sleep(1);
System.out.println("结束");
}
3.修改节点
// 同步
setData(String path, byte[] data, int version)
// 异步
setData(String path, byte[] data, int version, StatCallback callBack, Object ctx)
参数 | 解释 |
| 节点路径 |
| 数据 |
| 数据的版本号, - |
| 异步回调 |
| 传递上下文参数 |
public static void setData1() throws Exception{
// arg1:节点的路径
// arg2:修改的数据
// arg3:数据的版本号 -1 代表版本号不参与更新
Stat stat = zookeeper.setData("/hadoop","hadoop-1".getBytes(),-1);
}
public static void setData2() throws Exception{
zookeeper.setData("/hadoop", "hadoop-1".getBytes(), 3 ,new AsyncCallback.StatCallback(){
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
// 讲道理,要判空
System.out.println(rc + " " + path + " " + stat.getVersion() + " " + ctx);
}
}, "I am context");
}
4.删除节点
// 同步
delete(String path, int version)
// 异步
delete(String path, int version, AsyncCallback.VoidCallback callBack, Object ctx)
参数 | 解释 |
| 节点路径 |
| 版本 |
| 数据的版本号, - |
| 传递上下文参数 |
public static void deleteData1() throws Exception {
zookeeper.delete("/hadoop", 1);
}
public static void deleteData2() throws Exception {
zookeeper.delete("/hadoop", 1, new AsyncCallback.VoidCallback() {
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println(rc + " " + path + " " + ctx);
}
}, "I am context");
TimeUnit.SECONDS.sleep(1);
}
5.查看节点
// 同步
getData(String path, boolean watch, Stat stat)
getData(String path, Watcher watcher, Stat stat)
// 异步
getData(String path, boolean watch, DataCallback callBack, Object ctx)
getData(String path, Watcher watcher, DataCallback callBack, Object ctx)
参数 | 解释 |
| 节点路径 |
| 是否使用连接对象中注册的监听器 |
| 元数据 |
| 异步回调接口,可以获得状态和数据 |
| 传递上下文参数 |
public static void getData1() throws Exception {
Stat stat = new Stat();
byte[] data = zookeeper.getData("/hadoop", false, stat);
System.out.println(new String(data));
// 判空
System.out.println(stat.getCtime());
}
public static void getData2() throws Exception {
zookeeper.getData("/hadoop", false, new AsyncCallback.DataCallback() {
@Override
public void processResult(int rc, String path, Object ctx, byte[] bytes, Stat stat) {
// 判空
System.out.println(rc + " " + path
+ " " + ctx + " " + new String(bytes) + " " +
stat.getCzxid());
}
}, "I am context");
TimeUnit.SECONDS.sleep(3);
}
6.查看子节点
// 同步
getChildren(String path, boolean watch)
getChildren(String path, Watcher watcher)
getChildren(String path, boolean watch, Stat stat)
getChildren(String path, Watcher watcher, Stat stat)
// 异步
getChildren(String path, boolean watch, ChildrenCallback callBack, Object ctx)
getChildren(String path, Watcher watcher, ChildrenCallback callBack, Object ctx)
getChildren(String path, Watcher watcher, Children2Callback callBack, Object ctx)
getChildren(String path, boolean watch, Children2Callback callBack, Object ctx)
参数 | 解释 |
| 节点路径 |
| |
| 异步回调,可以获取节点列表 |
| 传递上下文参数 |
public static void getChildren_1() throws Exception{
List<String> hadoop = zookeeper.getChildren("/hadoop", false);
hadoop.forEach(System.out::println);
}
public static void getChildren_2() throws Exception {
zookeeper.getChildren("/hadoop", false, new AsyncCallback.ChildrenCallback() {
@Override
public void processResult(int rc, String path, Object ctx, List<String> list) {
list.forEach(System.out::println);
System.out.println(rc + " " + path + " " + ctx);
}
}, "I am children");
TimeUnit.SECONDS.sleep(3);
}
7.检查节点是否存在
// 同步
exists(String path, boolean watch)
exists(String path, Watcher watcher)
// 异步
exists(String path, boolean watch, StatCallback cb, Object ctx)
exists(String path, Watcher watcher, StatCallback cb, Object ctx)
参数 | 解释 |
| 节点路径 |
| |
| 异步回调,可以获取节点列表 |
| 传递上下文参数 |
public static void exists1() throws Exception{
Stat exists = zookeeper.exists("/hadoopx", false);
// 判空
System.out.println(exists.getVersion() + "成功");
}
public static void exists2() throws Exception{
zookeeper.exists("/hadoopx", false, new AsyncCallback.StatCallback() {
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
// 判空
System.out.println(rc + " " + path + " " + ctx +" " + stat.getVersion());
}
}, "I am children");
TimeUnit.SECONDS.sleep(1);
}