zookeeper简介
顾名思义,zoo动物,keeper管理员,合称动物园管理员
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务
等。
Zookeeper是hadoop的一个子项目,其发展历程无需赘述。在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在
某些应用中使用,因此需要有一种可靠的、可扩展的、分布式的、可配置的协调机制来统一系统的状态。Zookeeper的目的就在于此。
主要作用
- 中间件,提供协调服务
- 作用于分布式系统,发挥其优势,可以为大数据服务
- 支持java,提供java和c语言的客户端api
什么是分布式系统
- 很多计算机组成一个整体,一个整体一致对外并且处理同一请求
- 内部的每台计算机都可以相互通信(rest/rpc远程调用)
- 客户端到服务端的一次请求到响应结束会经历多台计算机
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SB4RPHtd-1574130425483)(assets/1569209106613.png)]
订单系统的案例…
分布式系统的瓶颈
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJnUUXcR-1574130425486)(assets/timg.jpg)]
zookeeper的特性(面试的高频题 事务…)
**一致性:**数据一致性,数据按照顺序分批入库。
**原子性:**事务要么成功要么失败,不会局部化。
**单一视图:**客户端连接集群中任一zk节点,数据都是一致的。
**可靠性:**每次对zk的操作状态都会保存在服务端。
**实时性:**客户端可以读取到zk服务器的最新数据。
ZooKeeper安装
JDK的安装
- 下载Linux的jdk1.8.tar,上传至Linux的服务器
- 解压缩jdk,配置jdk
- 测试:java -version 显示版本号
zookeeper下载、安装以及配置环境变量
JAVA_HOME=/usr/jdk8(java的安装位置)
ZOOKEEPER_HOME=/usr/local/zookeeper(zookeeper的安装位置)
CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
PATH=
P
A
T
H
:
PATH:
PATH:JAVA_HOME/bin:
J
A
V
A
H
O
M
E
/
j
r
e
/
b
i
n
:
JAVA_HOME/jre/bin:
JAVAHOME/jre/bin:ZOOKEEPER_HOME/jre/bin
export PATH CLASSPATH JAVA_HOME ZOOKEEPER_HOME
zookeeper文件夹主要目录介绍
bin:主要的一些运行命令
conf:存放配置文件,其中我们需要修改zk.cfg(zoo_sample.cfg主配置文件)
contrib:附加的一些功能(能够使用python操作zk,能够实现远程rest调用)
dist-maven:mvn编译后的目录
docs:文档
lib:平时依赖的jar包
recipes:案例demo代码
src:源码
zookeeper配置文件介绍,运行zk
tickTime:用于计算的单元时间。比如session超时:N*tickTime
initLimit:用于集群,允许从节点连接并同步到master节点的初始化连接时间,以tickTime的倍数来表示
syncLimit:用于集群,master主节点与从节点之间发送消息,请求和应答的时间长度(心跳机制)
dataDir:必须配置(存储相关数据,事务文件…数据)
dataDir=/usr/local/zookeeper/dataDir(必须手动创建)
dataLogDir:日志目录,如果不配置和dataDir公用
dataLogDir=/usr/local/zookeeper/dataLogDir(必须手动创建)
clientPort:连接服务器的端口,默认2181(在伪分布式的环境下是要变的)
ZooKeeper基本数据模型
- 是一个树形结构,类似于前端开发中的tree.js组件
- zk的数据模型可以理解为linux/unix的文件目录:/usr/local
- 每一个节点都称之为znode,它可以有子节点,也可以有数据
- 每个节点分为临时节点和永久节点,临时节点在客户端断开之后消失
- 每个zk节点都有各自的版本号,可以通过命令行来显示节点的信息
- 每个节点数据发生变化,那么该节点的版本号会累加(乐观锁),类似于svn的版本号
- 删除或者修改过时的节点,版本号不匹配则会报错(乐观锁的存在)
- 每个zk节点存储的数据不宜过大,几k即可,最大1M
- 节点可以设置权限acl(权限控制列表),可以通过权限来限制用户的访问
-
zk客户端连接关闭服务端查看znode
客户端连接
./zkServer.sh restart 先要启动zk服务器
./zkCli.sh 启动客户端
查看znode结构
help 查看所有的命令
关闭客户端连接
ctrl+c
zookeeper的作用体现
节点选举
Master节点,主节点挂了之后,从节点就会接手工作
并且,保证这个节点是唯一的,这就是首脑模式,从而保证集群的高可用
统一配置文件管理
只需要部署一台服务器则可以把相同的配置文件,同步更新到其他所有服务器,
此操作在云计算中,用的特别多。比如,修改了redis统一配置
发布与订阅
类似消息队列MQ、amq、rmq,dubbo发布者把数据存在znode节点上,订阅者会读取这个数据
提供分布式锁
分布式环境中,不同进程之间争夺资源类似于多线程中的的锁
集群管理
集群中,保证数据的一致性
ZK基本特性与基于Linux的ZK客户端命令行学习
zookeeper常用命令行操作
-
通过./zkCli.sh打开zk的客户端进行命令行后台
-
ls与ls2命令
ls2=ls+stat
-
get与stat命令
get可以获取节点的数据 stat只是查看节点的状态
-
create命令的使用
create /ddm ange 创建持久节点
create -e /ddm/temp pika 创建临时节点
create -s /ddm/sort sort 创建顺序节点
-
set命令(乐观锁)
-
delete命令(乐观锁)
watcher事件
特性 | 说明 |
---|---|
一次性 | Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
客户端顺序回调 | Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容; |
时效性 | Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知; |
-
创建父节点触发:NodeCreated
stat /ddm watch ------> create /ddm
-
修改父节点数据触发:NodeDataChanged
get /ddm watch -----> set /ddm 123
-
删除父节点触发:NodeDeleted
get /ddm watch -----> delete /ddm
-
创建和删除子节点触发:NodeChildrenChanged(修改子节点不会触发事件,需要把子节点当作父节点)
ls /ddm watch —> create /ddm/temp 123
ls /ddm watch —> delete /ddm/temp
watcher的使用场景
- 统一资源配置
ACL(access control lists)权限控制
- 针对不同的节点可以设置相关的读写等权限,目的为了保障数据安全性
- 权限permissions可以指定不同的权限范围以及角色
ACL命令行
getAcl:获取某个节点的acl权限信息
setAcl:设置某个节点的acl权限信息
addauth:输入认证权限信息,注册时输入明文密码(登录),但在zk系统中,密码是以加密的形式存在的
ACL的构成
zk的acl通过[scheme🆔permissions]来构成权限列表
scheme:代表采用的某种权限机制
id: 代表允许访问的用户
permissions: 权限组合字符串
身份的认证有4种方式(scheme):
world:默认方式,相当于全世界都能访问
auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest:即用户名:密码这种方式认证,这也是业务系统中最常用的
ip:使用Ip地址认证
super:超级管理员权限
auth与digest的区别
前者是明文,后者是密文
setAcl /path auth:lee:lee:cdrwd与
setAcl /path digest:lee:BASE64(SHA1(password))cdrwa
是等价的,在通过addauth digest lee:lee 后都能操作指定节点的权限
权限字符串缩写 crdwa
create: 创建节点
read: 获取节点/子节点
write: 设置节点数据
delete: 删除子节点
admin: 设置权限
getAcl /ddm
setAcl /ddm world:anyone:crwa
delete /ddm/abc
通过auth设置
create /names ange
getAcl /names
setAcl /names auth:ange:ange:cdrwa
addauth digest ange:ange
setAcl /names auth:ange:ange:cdrwa
setAcl /names auth:rock:rock:cdrwa
getAcl /names
通过digest设置
setAcl /names digest:ange:密文:cdrwa
通过ip设置
setAcl /names/ip ip:127.0.0.1:cdrwa
ACL的应用场景
- 开发/测试环境分离,开发者无权操作测试库的节点,只能看
- 生产环境上控制指定的IP的服务可以访问相关节点,防止混乱
ZK四字命令
需要在zk3.4.6之后才能使用
具体参考:https://blog.csdn.net/u013673976/article/details/47279707
选举模式和ZooKeeper的集群安装
单机伪分布式安装zookeeper集群
zookeeper集群搭建注意点:
配置数据文件myid1/2/3对应server.1/2/3
通过zkCli.sh -server [ip]:[port]检测集群是否配置成功。
第一步:首先我们将我们之前已经搭建好的单机版的zk虚拟机进行克隆。
第二步:修改克隆后的虚拟机配置:https://www.cnblogs.com/haoliyou/p/9461844.html
第三步:复制zk
执行命令:cp zookeeper zookeeper02 -rf
第四步:修改拷贝的zk配置
server.1=192.168.1.85:2888:3888
server.2=192.168.1.85:2889:3889
server.3=192.168.1.85:2890:3890
ip记得修改成自己的主机
(选举模式需要该配置)
然后需要到:dataDir目录下创建myid文件 ,在里边输入1。
以此创建其他的机器,但是要记得修改配置信息
server.A=B:C:D
A:表示集群中服务器的序号,需要唯一
B:表示服务器的IP
C:和集群中Leader交换数据的端口
D:进行Leader选举的端口
举例子
server.1=192.168.135.11:8881:8891
server.2=192.168.135.12:8881:8891
server.3=192.168.135.13:8881:8891
在这个集群中有三个服务器
序号分别为1、2、3;
IP分别为192.168.135.11,192.168.135.12,192.168.135.13,
和Leader通讯的端口均为8881,当然这里的通讯端口也可以不一样
进行Leader选举的端口均为8891,这里的选举端口也可以不一样
第五步:启动三个服务节点
./zkServer.sh restart
查看节点的模式和状态
第六步:启动客户端
执行: ./zkCli.sh -server localhost:2181 命令,尝试连接第一个节点
创建一个文件,看是否能够实现同步
完全分布式集群搭建
参考伪分布式搭建的方式(注意一定要关闭防火墙,今天困扰我三次了)
查看防火墙状态:systemctl status firewalld
关闭防火墙:systemctl stop firewalld
开启防火墙:systemctl start firewalld
查看默认防火墙状态(关闭后显示notrunning,开启后显示running)
systemctl status firewalld.service
关闭防火墙:
systemctl stop firewalld.service
开启防火墙:
systemctl start firewalld.service
永久关闭防火墙(可防止系统关机重启后防火墙自启动):
systemctl disable firewalld.service
永久开启防火墙:
systemctl enable firewalld.service
在连接客户端远程工具的时候记得启动zk的服务
使用ZooKeeper原生Java API进行客户端开发
# Configure logging for testing: optionally with log file
log4j.rootLogger=WARN, stdout
# log4j.rootLogger=WARN, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
<dependencies>
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<!--<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.8</version>
</dependency>-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
public class Test01 {
private static final String HOST = "192.168.126.129";
private static final int TIME_OUT = 2000;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
ZooKeeper zooKeeper = new ZooKeeper(HOST, TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
Event.KeeperState state = watchedEvent.getState();
System.out.println(state);
if (state == Event.KeeperState.SyncConnected) {
System.out.println("123");
countDownLatch.countDown();
}
}
});
System.out.println("正在等待连接.....");
countDownLatch.await();
System.out.println("开始创建节点.....");
String s = zooKeeper.create("/xintu", "duli".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(s);
//Thread.sleep(5000);
zooKeeper.close();
/**
* 节点的四种类型:
* 1.持久节点 PERSISTENT
* 2.临时节点 会话关闭会自动删除
* 3.持久有序节点
* 4.临时有序节点
*
* 注意事项:不能递归创建节点
*/
}
}
权限的设置
public class Test02 {
private static final String HOST = "192.168.126.129";
private static final int TIME_OUT = 2000;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException, NoSuchAlgorithmException {
ZooKeeper zooKeeper = new ZooKeeper(HOST, TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
Event.KeeperState state = watchedEvent.getState();
System.out.println(state);
if (state == Event.KeeperState.SyncConnected) {
System.out.println("123");
countDownLatch.countDown();
}
}
});
System.out.println("正在等待连接.....");
countDownLatch.await();
System.out.println("开始创建节点.....");
//创建权限
Id id1 = new Id("digest", DigestAuthenticationProvider.generateDigest("admin:admin123"));
ACL acl1 = new ACL(ZooDefs.Perms.ALL,id1);
Id id2 = new Id("digest", DigestAuthenticationProvider.generateDigest("guest:guest123"));
ACL acl2 = new ACL(ZooDefs.Perms.ALL,id2);
ArrayList<ACL> acls = new ArrayList<>();
acls.add(acl1);
acls.add(acl2);
String s = zooKeeper.create("/xintu", "duli".getBytes(), acls, CreateMode.PERSISTENT);
System.out.println(s);
//Thread.sleep(5000);
zooKeeper.close();
/**
* 节点的四种类型:
* 1.持久节点 PERSISTENT
* 2.临时节点 会话关闭会自动删除
* 3.持久有序节点
* 4.临时有序节点
*
* 注意事项:不能递归创建节点
*/
}
}
权限的访问
public class Test03 {
private static final String HOST = "192.168.126.129";
private static final int TIME_OUT = 2000;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException, NoSuchAlgorithmException {
ZooKeeper zooKeeper = new ZooKeeper(HOST, TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
Event.KeeperState state = watchedEvent.getState();
System.out.println(state);
if (state == Event.KeeperState.SyncConnected) {
System.out.println("123");
countDownLatch.countDown();
}
}
});
zooKeeper.addAuthInfo("digest","guest:guest123".getBytes());
byte[] data = zooKeeper.getData("/xintu", null, new Stat());
System.out.println(new String(data));
}
}
使用zookeeper实现服务注册
package com.ddmzx.springboot_zk;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.CountDownLatch;
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
private static final String HOST = "192.168.126.129";
private static final int TIME_OUT = 2000;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
@Value("${server.port}")
private String port;
@Override
public void run(ApplicationArguments args) throws Exception {
ZooKeeper zooKeeper = new ZooKeeper(HOST, TIME_OUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
Event.KeeperState state = watchedEvent.getState();
System.out.println(state);
if (state == Event.KeeperState.SyncConnected) {
System.out.println("123");
countDownLatch.countDown();
}
}
});
System.out.println("正在等待连接.....");
countDownLatch.await();
System.out.println("开始创建节点.....");
String parentPath = "/ddm";
Stat exists = zooKeeper.exists(parentPath, null);
if(exists==null){
zooKeeper.create(parentPath,"/ddm".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
String path = "127.0.0.1:"+port;
zooKeeper.create(parentPath+"/"+port,path.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println("服务注册成功");
}
}