likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 20

一、Zookeeper内部的数据模型

1.zk如何保存数据?

在zookeeper安装启动,通过命令ls -R /查看/的根节点和其子节点。每个节点就是znode,许多znode共同,其结构就像一棵树,通过路径可以找到具体的znode。

/
test
zookeeper
conf
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配置

<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 配置文件

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中锁的种类

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

2.zk如何上读锁

流程示意图

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

编码实现

3.zk如何上写锁

流程示意图

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

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。

转载自:https://juejin.cn/post/7282232046521024571
评论
请登录