zookeeper学习

基础

1.zNode

node是树形结构
请添加图片描述

ls /
create -e /xjj 1 //创建临时结点
create -s -e /xjj 2 //创建临时顺序结点
set /xjj 3
get /xjj
stat /xjj

对于临时结点,当客户端断掉之后zk自动删除
对于永久结点,zk不会主动删除,除非客户端删除掉。

2.session

zookeeper是基于tcp长连接,连接会设置超时时间,在超时时间内如果客户端没有发送ping进行确认,zk将自动关闭连接。
– 心跳检测机制

3.watcher

使用get -w /xjj监听结点
在这里插入图片描述
其他结点修改结点的时候会返回消息
在这里插入图片描述
但只能单次监听。

具体流程分为四个步骤,首先客户端使用函数getData、exists、getChildren时,向服务端发送请求。服务端接收后存储到一个HashMap中。结点修改时从hashmap读取,如果命中,就返回给客户端。

4.acl

zookeeper 的 acl 通过 [scheme:id:permissions] 来构成权限列表。

1、scheme:代表采用的某种权限机制,包括 world、auth、digest、ip、super 几种。
2、id:代表允许访问的用户。
3、permissions:权限组合字符串,由 cdrwa 组成,其中每个字母代表支持不同权限, 创建权限 create©、删除权限 delete(d)、读权限 read®、写权限 write(w)、管理权限admin(a)。

查看默认权限:
getAcl /xjj
在这里插入图片描述
去掉删除权限后无法删除结点的子节点,但是可以删除/xjj结点
world:anyone:crwa,对于任何人开放,访问权限是crwa(创建,读,写,管理)
在这里插入图片描述
为用户添加密码addauth digest user1:123456

ZAB协议

ZAB 协议全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)

ZAB协议四个机制:
1.zk中的快速领导者选举
2.过半机制
3.2PC --> 两阶段提交(预提交,Ack,提交)
4.数据同步

广播

请添加图片描述

zab中存在两种结点,1是领导者leader,2是跟随者follower,其中leader只有一个,follower可以有多个。下面是广播的具体做法

  1. 在一个follower接收到写命令的时候,follower先把命令提交给leader。
    leader将命令封装为事务Proposal,发送到各个follower的消息队列中,在发送之前会为propose生成一个唯一单调递增id,即zxid,存入propose中。propose的处理严格按照递增id,这样保证了有序性
  2. follower接受到事务消息之后先写入日志,然后返回ack给leader。
  3. leader接收到ack超过总结点数的一半(不包含leader)就发送commit操作到follower,同时发送一份Proposal 给observer结点。如果ack数量小于一半则进行崩溃恢复,重新选择leader。
  4. follower接收到commit后将日志中的事务正式更新到本地,同时observer直接更新。
  5. follower和observer结点更新之后返回一份ack给leader。

zookeeper 采用 Zab 协议的核心,就是只要有一台服务器提交了 Proposal,就要确保所有的服务器最终都能正确提交 Proposal。这也是 CAP/BASE 实现最终一致性的一个体现。

崩溃恢复

崩溃恢复分为两部分,Leader选举 和 数据恢复。

1.领导者选举

一个正常的组织结构分为三种状态:
looking状态:也就是观望状态,这时候是由于组织出现内部问题,那就停下来,做一些其他的事。
following状态:自身是一个组织成员,做自己的事。
leading状态:自身是一个组织老大,做自己的事。
observing状态:观察状态,同步leader状态,不参与投票。

当leader挂掉之后所有的follower都由following状态变成了looking状态,准备选举leader。

每当用户提交一个请求写日志之后结点的zxid都会+1,自然,如果还没有同步的情况下结点就挂掉了,则zxid最大的结点数据最新,更有希望被选举为leader。
在这里插入图片描述
但是如果zxid都相同,则应该选择myid最大的结点。两个结点比较的情况如下图所示。zk2发送他的投票给zk1
图2
zk1比较zxid相同于是,比较myid,发现zk2比较大。于是更改当前投票为zk2,修改投票箱中自己的投票,同时将zk2的票加入投票箱
在这里插入图片描述
zk1再将修改后的自己的票发送给其他人,zk2接收到后存入自己的投票箱中
在这里插入图片描述
选票数量大于总结点数的一半,则zk1和zk2都认为zk2为leader。

其实真实情况下传递的票据是(leader, Serverid,Zxid,Epoch)这四个值,其中epoch的解释如下:

在 Zab 的事务编号 zxid 设计中,zxid是一个64位的数字。其中低32位可以看成一个简单的单增计数器,针对客户端每一个事务请求,Leader 在产生新的 Proposal
事务时,都会对该计数器加1。而高32位则代表了 Leader 周期的 epoch 编号。 epoch
编号可以理解为当前集群所处的年代,或者周期。每次Leader变更之后都会在 epoch 的基础上加1,这样旧的 Leader
崩溃恢复之后,其他Follower 也不会听它的了,因为 Follower 只服从epoch最高的 Leader 命令。
每当选举产生一个新的 Leader ,就会从这个 Leader 服务器上取出本地事务日志充最大编号 Proposal 的 zxid,并从
zxid 中解析得到对应的 epoch 编号,然后再对其加1,之后该编号就作为新的 epoch
值,并将低32位数字归零,由0开始重新生成zxid。

1、如果服务器B接收到服务器A的数据(服务器A处于选举状态(LOOKING 状态)

1)首先,判断逻辑时钟值:

a)如果发送过来的逻辑时钟Epoch大于目前的逻辑时钟。首先,更新本逻辑时钟Epoch,同时清空本轮逻辑时钟收集到的来自其他server的选举数据。然后,判断是否需要更新当前自己的选举leader Serverid。判断规则rules judging:保存的zxid最大值和leader Serverid来进行判断的。先看数据zxid,数据zxid大者胜出;其次再判断leader Serverid,leader Serverid大者胜出;然后再将自身最新的选举结果(也就是上面提到的三种数据(leader Serverid,Zxid,Epoch)广播给其他server)

b)如果发送过来的逻辑时钟Epoch小于目前的逻辑时钟。说明对方server在一个相对较早的Epoch中,这里只需要将本机的三种数据(leader Serverid,Zxid,Epoch)发送过去就行。

c)如果发送过来的逻辑时钟Epoch等于目前的逻辑时钟。再根据上述判断规则rules judging来选举leader ,然后再将自身最新的选举结果(也就是上面提到的三种数据(leader Serverid,Zxid,Epoch)广播给其他server)。

2)其次,判断服务器是不是已经收集到了所有服务器的选举状态:若是,根据选举结果设置自己的角色(FOLLOWING还是LEADER),退出选举过程就是了。

最后,若没有收集到所有服务器的选举状态:也可以判断一下根据以上过程之后最新的选举leader是不是得到了超过半数以上服务器的支持,如果是,那么尝试在200ms内接收一下数据,如果没有新的数据到来,说明大家都已经默认了这个结果,同样也设置角色退出选举过程。

请添加图片描述

2.数据同步

领导者选举出的leader还不是真正的leader,只有经过初始化同步之后才会成为真正的leader。

既然要恢复,有些场景是不能恢复的,ZAB协议崩溃恢复要求满足如下2个要求: 第一: 确保已经被leader提交的proposal必须最终被所有的follower服务器提交。 第二:确保丢弃已经被leader出的但是没有被提交的proposal。这也是zk保证数据一致性必须要解决的问题

好了,现在开始进行恢复。

第一步:选取当前取出最大的ZXID,代表当前的事件是最新的。

第二步:新leader把这个事件proposal提交给其他的follower节点

第三步:follower节点会根据leader的消息进行回退或者是数据同步操作。最终目的要保证集群中所有节点的数据副本保持一致。

这就是整个恢复的过程,其实就是相当于有个日志一样的东西,记录每一次操作,然后把出事前的最新操作恢复,然后进行同步即可。

https://baijiahao.baidu.com/s?id=1666465070459184658&wfr=spider&for=pc
请添加图片描述

Zookeeper之基于Observer部署架构

在早期的 ZooKeeper 集群服务运行过程中,只有 Leader 服务器和 Follow 服务器。

不过随着 ZooKeeper 在分布式环境下的广泛应用,早期模式的设计缺点也随之产生,主要带来的问题有如下几点:

随着集群规模的变大,集群处理写入的性能反而下降。

ZooKeeper 集群无法做到跨域部署

其中最主要的问题在于,当 ZooKeeper 集群的规模变大,集群中 Follow 服务器数量逐渐增多的时候,ZooKeeper 处理创建数据节点等事务性请求操作的性能就会逐渐下降。这是因为 ZooKeeper 集群在处理事务性请求操作时,要在 ZooKeeper 集群中对该事务性的请求发起投票,只有超过半数的 Follow 服务器投票一致,才会执行该条写入操作。

正因如此,随着集群中 Follow 服务器的数量越来越多,一次写入等相关操作的投票也就变得越来越复杂,并且 Follow 服务器之间彼此的网络通信也变得越来越耗时,导致随着 Follow 服务器数量的逐步增加,事务性的处理性能反而变得越来越低。

为了解决这一问题, ZooKeeper 集群中创建了一种新的服务器角色,即 Observer——观察者角色服务器。Observer 可以处理 ZooKeeper 集群中的非事务性请求,并且不参与 Leader 节点等投票相关的操作。这样既保证了 ZooKeeper 集群性能的扩展性,又避免了因为过多的服务器参与投票相关的操作而影响 ZooKeeper 集群处理事务性会话请求的能力。

Observer 不会接收来自 Leader 服务器提交的投票请求,且不会接收网络中的 Proposal 请求信息,只会从网络中接收 INFORM 类型的信息包。

而 INFORM 信息的内部只包含已经被 Commit 操作过的投票信息,因为 Observer 服务器只接收已经被提交处理的 Proposal 请求,不会接收未被提交的会话请求。这样就隔离了 Observer 参与投票操作,进而使 Observer 只负责查询等相关非事务性操作,保证扩展多个 Observer 服务器时不会对 ZooKeeper 集群写入操作的性能产生影响。

https://blog.csdn.net/yangshangwei/article/details/111658771

分布式锁

一个分布式商城购买的逻辑,其中使用到了curator,interprocessmutex,之后再进行学习吧。

    @Override
    public String buy(String commodityId, Integer number) throws Exception {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
        // 启动客户端
        client.start();
        InterProcessMutex mutex = new InterProcessMutex(client, "/locks");
        String ret = "";
        try {
            if (mutex.acquire(3, TimeUnit.SECONDS)) {
                ret = purchaseCommodityInfo(commodityId, number);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            mutex.release();
            client.close();
        }
        return ret;
    }

学习zk分布式锁原理:
每一个尝试获取锁的结点都会创建一个临时有序结点zkClient.createEphemeralSequential(ROOT_NODE + "/", "lock");
有序节点就是创建时,会自动在节点后面加一个序号,基于这特性,对于节点创建时,可以使用同名节点名, 因为真正创建时,会自动加上序号。
使用临时结点是考虑到客户端获取锁后突然宕机无法释放锁产生的死锁问题。
使用有序结点为了提高性能,每个结点只监听上一个结点是否释放,而不是所有结点监听这一个结点。

当一个结点调用lock(),先尝试获取锁,失败就进入阻塞,监听上一个结点。上一个被删除唤醒当前结点。

public class ZkLock implements Lock {

    //计数器,用于加锁失败时,阻塞
    private static CountDownLatch cdl = new CountDownLatch(1);

    //ZooKeeper服务器的IP端口
    private static final String IP_PORT = "127.0.0.1:2181";
    //锁的根路径
    private static final String ROOT_NODE = "/Lock";

    //上一个节点的路径
    private volatile String beforePath;

    //当前上锁的节点路径
    private volatile String currPath;

    private ZkClient zkClient = new ZkClient(IP_PORT);

    public ZkLock() {
        //判断是否存在根节点
        if (!zkClient.exists(ROOT_NODE)) {
            //不存在则创建
            zkClient.createPersistent(ROOT_NODE);
        }
    }

    public void lock() {
        if (tryLock()) {
            System.out.println("加锁成功!!");
        } else {
            // 尝试加锁失败,进入等待 监听
            waitForLock();
            // 再次尝试加锁
            lock();
        }
    }

    public synchronized boolean tryLock() {
        // 第一次就进来创建自己的临时节点
        if (StringUtils.isBlank(currPath)) {
       		//临时有序结点
            //有序节点就是创建时,会自动在节点后面加一个序号,基于这特性,对于节点创建时,可以使用同名节点名, 因为真正创建时,会自动加上序号
            currPath = zkClient.createEphemeralSequential(ROOT_NODE + "/", "lock");
        }
        // 对节点排序
        List<String> children = zkClient.getChildren(ROOT_NODE);
        Collections.sort(children);

        // 当前的是最小节点就返回加锁成功
        if (currPath.equals(ROOT_NODE + "/" + children.get(0))) {
            return true;
        } else {
            // 不是最小节点 就找到自己的前一个 依次类推 释放也是一样
            int beforePathIndex = Collections.binarySearch(children, currPath.substring(ROOT_NODE.length() + 1)) - 1;
            beforePath = ROOT_NODE + "/" + children.get(beforePathIndex);
            //返回加锁失败
            return false;
        }
    }

    public void unlock() {
        //删除节点并关闭客户端
        zkClient.delete(currPath);
        zkClient.close();
    }

    private void waitForLock() {
        IZkDataListener listener = new IZkDataListener() {
            //监听节点更新事件
            public void handleDataChange(String s, Object o) throws Exception {
            }

            //监听节点被删除事件
            public void handleDataDeleted(String s) throws Exception {
                //解除阻塞
                cdl.countDown();
            }
        };
        // 监听上一个节点
        this.zkClient.subscribeDataChanges(beforePath, listener);
        //判断上一个节点是否存在
        if (zkClient.exists(beforePath)) {
            //上一个节点存在
            try {
                System.out.println("加锁失败 等待");
                //加锁失败,阻塞等待
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 释放监听
        zkClient.unsubscribeDataChanges(beforePath, listener);
    }

    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    public void lockInterruptibly() throws InterruptedException {

    }

    public Condition newCondition() {
        return null;
    }
}

https://zhuanlan.zhihu.com/p/126345341

zk在cap理论中的位置/eureka

cap:一致性,可用性,分区容错性

一致性:集群中多个结点,当一个结点发生变化,其他结点应当同步,一致。分为强一致性,弱一致性,最终一致性,zk是最终一致性。
可用性:当向集群中突然挂掉的结点获取数据的时候,不会发生错误
分区容错性:结点之间的网络由于意外原因断开,每个分区具有容错性,

其中分区容错性是必须的,当发生网络故障的时候必须选择一致性和可用性其中之一。
zookeeper属于一致性(cp) -->领导者选举数据恢复,但是这个过程中不可用
eureka属于可用性(ap) -->https://www.cnblogs.com/jichi/p/12797557.html

再看

待学习

一致性协议算法-2PC、3PC、Paxos、Raft、ZAB、NWR超详细解析

  1. 数据发布/订阅
  2. 负载均衡
  3. 分布式协调/通知
  4. 集群管理
  5. 集群管理
  6. master 管理
  7. 分布式队列

https://www.jianshu.com/p/7f5c550f7185
https://www.runoob.com/w3cnote_genre/zookeeper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值