Zookeeper应用原理分析及其CAP理论

一、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

shell 复制代码
[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配置

xml 复制代码
<dependencies>
<!-- zookeeper客户端 -->
    <dependency>  
        <groupId>org.apache.curator</groupId>  
        <artifactId>curator-framework</artifactId>  
        <version>5.5.0</version>  
    </dependency>  
    <dependency>  
        <groupId>org.apache.curator</groupId>  
        <artifactId>curator-recipes</artifactId>  
        <version>5.5.0</version>  
    </dependency>  
<!-- zookeeper -->  
    <dependency>  
        <groupId>org.apache.zookeeper</groupId>  
        <artifactId>zookeeper</artifactId>  
        <version>3.8.2</version>  
    </dependency>  
</dependencies>

2.通过 config 注入配置参数

2.1 配置文件

yml 复制代码
curator:  
  retryCount: 5  
  elapsedTimeMs: 5000  
  connectString: 127.0.0.1:2181  
  sessionTimeoutMs: 6000  
  connectionTimeoutMs: 5000

2.2 注册参数

java 复制代码
@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 客户端配置

java 复制代码
@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

创建节点

java 复制代码
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"); 
    }
}

获取节点

java 复制代码
byte[] bytes = curatorFramework.getData().forPath("/test");

设置节点

java 复制代码
// 设置节点的值  
@Test  
void setData() throws Exception {  
    Stat stat = curatorFramework.setData().forPath("/set-node", "hello".getBytes(StandardCharsets.UTF_8));  
}  

删除节点

java 复制代码
// 删除节点,以及所有子节点  
@Test  
void deleteNode() throws Exception {  
    curatorFramework.delete().deletingChildrenIfNeeded().forPath("/parent/child");  
}

三、zookeeper实现分布式锁

1.zk中锁的种类

  • 读锁:都可以读取;加锁的前提是:之前没有添加写锁。
  • 写锁:只有得到写锁才能写;加锁的前提是:之前没有任何锁。 <img src<img src="=""" alt="" width="30%" /> alt="" width="50%" />

2.zk如何上读锁

流程示意图

编码实现

3.zk如何上写锁

流程示意图

4.Curator实现监听

java 复制代码
@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.编码实现读/写锁

读锁

java 复制代码
@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();  
}

写锁

java 复制代码
@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。

相关推荐
工业甲酰苯胺2 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
bjzhang753 小时前
SpringBoot开发——集成Tess4j实现OCR图像文字识别
spring boot·ocr·tess4j
flying jiang3 小时前
Spring Boot 入门面试五道题
spring boot
小菜yh3 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存
爱上语文5 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
荆州克莱5 小时前
springcloud整合nacos、sentinal、springcloud-gateway,springboot security、oauth2总结
spring boot·spring·spring cloud·css3·技术
serve the people5 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政10 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
Lill_bin13 小时前
深入理解ElasticSearch集群:架构、高可用性与数据一致性
大数据·分布式·elasticsearch·搜索引擎·zookeeper·架构·全文检索
Java小白笔记13 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis