1 zookeeper介绍
1.1 应用场景
- 分布式协调组件
协调分布系统状态 - 分布式锁
zk可以做到强一致性(顺序一致) - 无状态话的实现
类似于分布式session
1.2 zk配置
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/opt/zookeeper/zkdata
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
2 zk内部数据模型
2.1 zk是如何保存数据的
- 采用树形结构,其节点称之为zNode
- 引用方式为路径引用
[zk: localhost:2181(CONNECTED) 6] create /test2 abc
Created /test2
[zk: localhost:2181(CONNECTED) 7] get /test2
abc
2.2 zk中znode的结构
- data 保存数据
- acl:权限
– c:create创建权限,允许在该节点下创建子节点
– w:write更新权限
– r:read读权限
– d:delete删除权限
– a:admin管理权限,允许对该节点进行acl权限设置 - stat:描述当前znode的元数据
- child:当前节点的子节点
2.3 zk中znode的类型
- 持久节点:创建出的节点在会话结束后依然存在保存数据
- 持久序号节点:创建出的节点根据先后顺序会在节点之后带上一个数值。数值越大越后执行,适用于分布式锁的场景
[zk: localhost:2181(CONNECTED) 12] create -s /test3
Created /test30000000003
- 临时节点
[zk: localhost:2181(CONNECTED) 14] create -e /test5
Created /test5
– 随着会话结束会被删掉
– 应用场景
实现服务注册与发现
- 临时序号节点
– 跟持久序号节点相同、适用于临时的分布式锁 - container节点
– 当容器中没有任何节点,该容器会在60s后被删掉
[zk: localhost:2181(CONNECTED) 15] create -c /myContainer
Created /myContainer
2.4 zk的持久化
- 事务日志
zk把执行的命令以日志的形式保存在dataLogDir的指定路径文件中 - 数据快照
zk会在一定时间内内做一次内存数据的快照,把该时刻数据保存在快照文件中
zk通过两种形式的持久化,再回复时先回复快照文件中的数据到内存,再用日志文件中的数据做增量恢复,这样恢复速度更快
3 zk客户端(zkCli)的使用
3.1 创建
- 持久节点
- 持久顺序节点 -s
- 子节点
- 临时节点 -e
- 临时序号节点 -e -s
- 容器节点 -c
3.2 查询节点
- 普通查询 ls
- 递归查询 ls -R
- 查询数据 get
- 详细信息 -s
[zk: localhost:2181(CONNECTED) 27] get -s /test1
null
//当前节点事务ID
cZxid = 0x2
//创建时间
ctime = Mon Apr 11 19:25:18 CST 2022
//修改事务ID
mZxid = 0x2
//修改事务时间
mtime = Mon Apr 11 19:25:18 CST 2022
//子节点ID
pZxid = 0x3
//版本号
cversion = 1
//节点数据版本
dataVersion = 0
//节点权限版本
aclVersion = 0
//临时节点的SessionId
ephemeralOwner = 0x0
//当前节点的数据长度
dataLength = 0
//子节点个数
numChildren = 1
3.3 删除节点
- 删除空节点 delete
- 删除非空节点 deleteall
- 乐观锁 delete -v [版本号]
3.4 权限设置
对当前session添加权限
[zk: localhost:2181(CONNECTED) 28] addauth digest xiaoming:123456
[zk: localhost:2181(CONNECTED) 29] create /test-node abc auth:xiaoming:123456:cdrwa
Created /test-node
[zk: localhost:2181(CONNECTED) 30] get /test-node
abc
无权限时访问
[zk: localhost:2181(CONNECTED) 0] get /test-node
Insufficient permission : /test-node
添加权限后访问
[zk: localhost:2181(CONNECTED) 2] addauth digest xiaoming:123456
[zk: localhost:2181(CONNECTED) 3] get /test-node
abc
4 Curator客户端的使用
4.1 介绍
- Curator是对zk最好的支持框架
4.2 使用步骤
1、引入依赖
<!--Curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
<!--zooKeeper-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
2、application文件配置
curator:
retryCount: 5
elapsedTimeMs: 5000
connectString: 172.16.253.35:2181
sessionTimeoutMs: 60000
connectionTimeoutMs: 5000
3、注入配置bean
@Configuration
public class CuratorConfig {
@Autowired
WrapperZK wrapperZk;
@Bean(initMethod = "start")
public CuratorFramework curatorFramework() {
return CuratorFrameworkFactory.newClient(
wrapperZk.getConnectString(),
wrapperZk.getSessionTimeoutMs(),
wrapperZk.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZk.getRetryCount(),
wrapperZk.getElapsedTimeMs()));
}
}
4.创建节点
@Test
void contextLoads() throws Exception {
//添加持久节点
String path1 = curatorFramework.create().forPath("/curator-node1");
//持久顺序节点
String path2 = curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/curator-node2", "some-data".getBytes());
//临时节点
String path3 = curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath("/curator-node3", "some-data".getBytes());
//临时序号节点
String path4 = curatorFramework.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/curator-node4", "some-data".getBytes());
//子节点
String path5 = curatorFramework.create().creatingParentsIfNeeded().forPath("/curator-node4/sonNode");
//容器节点
String path6 = curatorFramework.create().withMode(CreateMode.CONTAINER).forPath("/curator-node6", "some-data".getBytes());
}
5.获得节点数据
@Test
public void testGetData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
System.out.println(new String(bytes));
}
6.修改节点数据
@Test
public void testSetData() throws Exception {
curatorFramework.setData().forPath("/curatornode","changed!".getBytes());
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
System.out.println(new String(bytes));
}
7.删除节点
@Test
public void testDelete() throws Exception {
String pathWithParent="/node-parent";
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPa
th(pathWithParent);
}
5 zk实现分布式锁
5.1 zk中锁的种类
- 读锁(共享锁):大家都可以读,前提是之前没上过写锁
- 写锁(排它锁):只有得到写锁才能写,之前没有上过任何锁
5.2 zk如何上读锁
- 创建⼀个临时序号节点,节点的数据是read,表示是读锁
- 获取当前zk中序号⽐⾃⼰⼩的所有节点
- 判断最⼩节点是否是读锁:
– 如果不是读锁的话,则上锁失败,为最⼩节点设置监听。阻塞等待,zk的watch机制
会当最⼩节点发⽣变化时通知当前节点,于是再执⾏第⼆步的流程(不是读锁则后续节点一定都在阻塞)
– 如果是读锁的话,则上锁成功(后续节点不能上写锁,所以写读锁一定成功)
5.3 zk如何上写锁
- 创建一个临时序号节点,节点额数据是write
- 获得zk中所有子节点
- 判断自己是否是最小节点
– 是则写锁成功
– 不是,则说明前面还有锁,上锁失败,监听最小节点
5.4 羊群效应
- 利用上诉上锁方式,若有大量节点监听最小节点时,最小节点释放会触发其他节点的监听事件,对zk压力非常大
- 解决方法:链式监听
6 watch机制
6.1 zkCli客户端使用watch
create /test xxx
get -w /test #⼀次性监听节点
ls -w /test #监听⽬录,创建和删除⼦节点会收到通知。⼦节点中新增节点不会收到通知
ls -R -w /test #对于⼦节点中⼦节点的变化,但内容的变化不会收到通知
6.2Curator实现watch
@Test
public void addNodeListener() throws Exception {
NodeCache nodeCache = new NodeCache(curatorFramework, "/curatornode");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{} path nodeChanged: ", "/curator-node");
printNodeData();
}
});
nodeCache.start();
System.in.read();
}
public void printNodeData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data: {}", new String(bytes));
}
7 zookeeper集群
7.1 集群角色
- Leader:处理集群的所有事务请求,集群中只有一个leader
- Follower:只能处理读请求,参与leader选举
- Observer:只能处理读请求,提升集群读的能力,但不参与leader选举
集群搭建
1、创建4个节点的myid,并设值
/opt/zookeeper/zkdata/zk1# echo 1 > myid
/opt/zookeeper/zkdata/zk2# echo 2 > myid
/opt/zookeeper/zkdata/zk3# echo 3 > myid
/opt/zookeeper/zkdata/zk4# echo 4 > myid
2、编写四个zoo.cfg
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# 修改对应的zk1 zk2 zk3 zk4
dataDir=/opt/zookeeper/zkdata/zk1
# 修改对应的端⼝ 2181 2182 2183 2184
clientPort=2181
# 2001为集群通信端⼝,3001为集群选举端⼝,observer表示不参与集群选举
server.1=127.0.0.1:2001:3001
server.2=127.0.0.1:2002:3002
server.3=127.0.0.1:2003:3003
server.4=127.0.0.1:2004:3004:observer
3、启动四台zookeeper服务器
./bin/zkServer.sh status ./conf/zoo1.cfg
./bin/zkServer.sh status ./conf/zoo2.cfg
./bin/zkServer.sh status ./conf/zoo3.cfg
./bin/zkServer.sh status ./conf/zoo4.cfg
4、连接zookeeper集群
./bin/zkCli.sh -server
127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183
8 ZAB协议
8.1 什么是ZAB协议
- Zookeeper Atomic Broadcast:用于解决zookeeper的崩溃恢复和主从数据同步的问题。
8.2 ZAB协议定义的四种节点状态
- Looking :选举状态。
- Following :Follower 节点(从节点)所处的状态。
- Leading :Leader 节点(主节点)所处状态。
- Observing:观察者节点所处的状态
集群上线时的Leader选举过程
8.3 崩溃恢复时的Leader选举
Leader建⽴完后,Leader周期性地不断向Follower发送⼼跳(ping命令,没有内容的
socket)。当Leader崩溃后,Follower发现socket通道已关闭,于是Follower开始进⼊到
Looking状态,重新回到上⼀节中的Leader选举过程,此时集群不能对外提供服务。
8.4 主从同步
9 CAP理论
9.1 CAP定理
CAP 理论:⼀个分布式系统最多只能同时满⾜⼀致性(Consistency)、可⽤性
(Availability)和分区容错性(Partition tolerance)这三项中的两项。
- ⼀致性(Consistency): “all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同⼀时间的数据完全⼀致。
- 可⽤性(Availability):“Reads and writes always succeed”,即服务⼀直可⽤,⽽且是正常响应时间。
- 分区容错性指:“the system continues to operate despite arbitrary message loss or failure
of part of the system”,即分布式系统在遇到某节点或⽹络分区故障的时候,仍然能够对外
提供满⾜⼀致性或可⽤性的服务。(分布式存储)
9.2 BASE定理
BASE 理论是对 CAP 理论的延伸,核⼼思想是即使⽆法做到强⼀致性(Strong Consistency,CAP 的⼀致性就是强⼀致性),但应⽤可以采⽤适合的⽅式达到最终⼀致性(Eventual Consitency)。
- 基本可⽤(Basically Available):基本可⽤是指分布式系统在出现故障的时候,允许损失部分可⽤性,即保证核⼼可⽤。电商⼤促时,为了应对访问量激增,部分⽤户可能会被引导到降级⻚⾯,服务层也可能只提供降级服务。这就是损失部分可⽤性的体现。
- 软状态(Soft State):软状态是指允许系统存在中间状态,⽽该中间状态不会影响系统整体可⽤性。分布式存储中⼀般⼀份数据⾄少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mySql replication 的异步复制也是⼀种体现。(eg:zk中leader收到半数以上的ack即可提交commit,允许客户端访问)
- 最终⼀致性(Eventual Consistency):最终⼀致性是指系统中的所有数据副本经过⼀定时间后,最终能够达到⼀致的状态。弱⼀致性和强⼀致性相反,最终⼀致性是弱⼀致性的⼀种特殊情况。
9.3 Zookeeper追求的⼀致性
Zookeeper在数据同步时,追求的并不是强⼀致性,⽽是顺序⼀致性(事务id的单调递增)。