一、Zookeeper内部的数据模型
1.zk如何保存数据?
在zookeeper安装启动,通过命令ls -R /
查看/的根节点和其子节点。每个节点就是znode,许多znode共同,其结构就像一棵树,通过路径可以找到具体的znode。
graph TD
/ --> test
/ --> zookeeper
zookeeper --> conf
zookeeper --> quota
2.znode
是什么结构?
znode
包括四个部分,分别是data、acl、stat和child。
- data:保存数据。
- acl:保存用户的权限信息。
- c:当前节点下,允许创建节点
- w:允许更新节点数据
- r:允许读取当前节点和子节点的信息
- d:允许删除该节点的子节点
- a:管理员权限,允许对此节点进行acl的设置
- stat:znode的元数据。
- child:当前节点的子节点信息。
查看/test
的详细信息get -s /test
:
[zk: localhost:2181(CONNECTED) 0] get -s /test
hello # 数据部分
cZxid = 0xd # 创建节点的事务ID
ctime = Sat Sep 23 18:20:50 CST 2023 # 创建时间
mZxid = 0x18 # 修改节点的事务ID
mtime = Sun Sep 24 17:19:20 CST 2023 # 修改时间
pZxid = 0xd # 添加或删除子节点的事务ID
cversion = 0 # 对子节点的更改版本,更新一次 + 1
dataVersion = 1 # 节点内部的数据版本,更新一次 + 1
aclVersion = 0 # 节点的权限版本
ephemeralOwner = 0x0 # 如果是临时节点,表示当前节点的所有者;否则为 0
dataLength = 5 # 数据长度
numChildren = 0 # 子节点的个数
3.znode
有哪些类型?
znode
的节点类型包括:持久节点
,持久序号节点
,临时节点
,临时序号节点
,Container节点
和TTL节点
。
持久节点
创建方式:create /test
。
特征:创建之后,回话断开,数据仍然存在。
使用场景:保存数据
持久序号节点
创建方式:create -s /test
特征:节点名称 = 输入的节点名称 + 序号。请求增多,序号单调递增。
使用场景:分布式锁
临时节点
创建方式:create -e /test
特征:通过不断地会话,续约存活时间,超出指定时间没有会话,节点被删除
使用场景:服务的注册与发现
时序号节点
创建方式:create -e -s /test
特征:节点名称 = 输入的节点名称 + 序号。请求增多,序号单调递增。通过不断地会话,续约存活时间,超出指定时间没有会话,节点被删除
使用场景:临时的分布式锁
Container节点
创建方式:create -c /test
特征:创建之后,如果没有字节点,当前节点被定期 60 s 删除。
TTL节点
创建方式:开启配置zookeeper.extendedTypesEnabled=true
,使用create -t /test
特征:创建之后,到达指定时间,数据被删除。
4.zookeeper
持久化
两种持久化机制:
事务快照:
zookeeper把执行命令以日志的形式保存到日志文件中。
数据快照:
zookeeper定时把内存的数据做一次快照保存到快照文件中。
zookeeper先恢复快照文件中数据,再用日志文件文件作为增量更新。加快恢复速度。
二、SpringBoot整合zookeeper
1.maven配置
org.apache.curator
curator-framework
5.5.0
org.apache.curator
curator-recipes
5.5.0
org.apache.zookeeper
zookeeper
3.8.2
2.通过 config 注入配置参数
2.1 配置文件
curator:
retryCount: 5
elapsedTimeMs: 5000
connectString: 127.0.0.1:2181
sessionTimeoutMs: 6000
connectionTimeoutMs: 5000
2.2 注册参数
@Component
@Data
@ConfigurationProperties(prefix = "curator")
public class WrapperZK {
private int retryCount;
private int elapsedTimeMs;
private String connectString;
private int sessionTimeoutMs;
private int connectionTimeoutMs;
}
2.3 客户端配置
@Configuration
public class CuratorConfig {
@Bean(initMethod = "start")
public CuratorFramework curatorFramework(WrapperZK wrapperZK) {
return CuratorFrameworkFactory.newClient(
wrapperZK.getConnectString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZK.getRetryCount(),wrapperZK.getElapsedTimeMs())
);
}
}
3.客户端API
创建节点
public class CreateNodeTest extends AppTest {
@Resource
private CuratorFramework curatorFramework;
// 持久节点
@Test
void createSNode() throws Exception {
String path = curatorFramework.create().forPath("/s-node");
System.out.println(path);
}
// 创建临时节点
@Test
void createENode() throws Exception {
String path = curatorFramework.create().withMode(
CreateMode.EPHEMERAL
).forPath("/e-node");
System.out.println(path);
}
// 创建持久序号节点
@Test
void createPSNode() throws Exception {
String path = curatorFramework.create().withMode(
CreateMode.PERSISTENT_SEQUENTIAL
).forPath("/ps-node");
System.out.println(path);
}
// 创建临时序号节点
@Test
void createESNode() throws Exception {
String path = curatorFramework.create().withMode(
CreateMode.EPHEMERAL_SEQUENTIAL
).forPath("/es-node");
System.out.println(path);
}
// 创建节点,父节点不存在
@Test
void creatingParentsIfNeeded() throws Exception {
String path = curatorFramework.create().creatingParentsIfNeeded().forPath("/parent/child");
}
}
获取节点
byte[] bytes = curatorFramework.getData().forPath("/test");
设置节点
// 设置节点的值
@Test
void setData() throws Exception {
Stat stat = curatorFramework.setData().forPath("/set-node", "hello".getBytes(StandardCharsets.UTF_8));
}
删除节点
// 删除节点,以及所有子节点
@Test
void deleteNode() throws Exception {
curatorFramework.delete().deletingChildrenIfNeeded().forPath("/parent/child");
}
三、zookeeper实现分布式锁
1.zk中锁的种类
- 读锁:都可以读取;加锁的前提是:之前没有添加写锁。
- 写锁:只有得到写锁才能写;加锁的前提是:之前没有任何锁。
2.zk如何上读锁
流程示意图
编码实现
3.zk如何上写锁
流程示意图
4.Curator实现监听
@Test
void addListener() throws Exception {
NodeCache nodeCache = new NodeCache(curatorFramework,"/curator-node");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
log.info("{} path nodeChange:","/curator-node");
printNodeData();
}
});
nodeCache.start();
System.in.read();
}
private void printNodeData() throws Exception {
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data: {}",new String(bytes));
}
5.羊群效应
如果同时有一定数量的并发,同时加写锁,那么只要被监听的节点发生变化,就会触发所有的监听事件,造成触发的时间执行无意义的性能消耗,因此,应该采用链式监听。
链式监听,后一个节点监听上一个节点。
6.编码实现读/写锁
读锁
@Test
void getReadLock() throws Exception {
// 读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(
curatorFramework,
"/lock1"
);
// 获取读锁对象
InterProcessReadWriteLock.ReadLock readLock = interProcessReadWriteLock.readLock();
log.info("开始获取读锁. . .");
// 获取锁
readLock.acquire();
for (int i = 0; i < 101; i++) {
TimeUnit.SECONDS.sleep(1);
log.info("index: {}",i);
}
// 释放锁
readLock.release();
}
写锁
@Test
void getWriteLock() throws Exception {
// 读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(
curatorFramework,
"/lock1"
);
// 获取读锁对象
InterProcessReadWriteLock.WriteLock writeLock = interProcessReadWriteLock.writeLock();
log.info("开始获取读锁. . .");
// 获取锁
writeLock.acquire();
for (int i = 0; i < 101; i++) {
TimeUnit.SECONDS.sleep(3);
log.info("write: {}",i);
}
// 释放锁
writeLock.release();
}
四、分布式集群
1.服务的角色
- Leader:处理集群的所有事务请求,集群中只有一个事务。
- Follwer:只能处理读请求,参与Leader选举。
- Observer:只能处理读请求,提升集群读的性能,不参与Leader选举。
2.ZAB协议
zookeeper的集群中的主机使用了ZAB协议解决崩溃恢复和数据同步问题。
ZAB协议定义了服务的四种状态:
- Looking:启动服务之后,所有节点处于选举状态。
- Following:Follwer节点所处的状态。
- Leading:Leader节点所处的状态。
- Observing:Obverser节点所处的状态
五、CAP理论
CAP理论认为,在分布式系统中最多只能满足一致性(C)、可用性(A)和分区容错性(P)中的两项。
- 一致性(C):更新操作完成之后,所有的节点同一时间的数据完全一致。
- 可用性(A):服务一直可用,响应正常。
- 分区容错性(P):冗余部署,当部分服务故障时,仍然能够对外提供服务。
1.BASE理论
BASE理论是对CAP理论的延伸,核心思想是无法做到强一致性,但应该采用合适的方法做到最终一致性。
- 基本可用:允许损失部分可用,保证和核心可用。
- 软状态:系统的中间状态,就是多个节点的数据同步存在延时所处的状态。
- 最终一致性:所有的节点经过一定的时间之后,数据都是一致的。
zookeeper集群之间的数据同步采用两阶段提交,先写入文件,在写入内存。在从节点写入文件之后,返回ack,当返回的ack的数量到达集群的半数之后,就写入内存。如何还有其他从节点的数据没有同步,访问从节点仍然获取不到数据。但是,当数据最终同步,还是可以获取到数据,满足了CP。