Zookeeper

目录

一、介绍

二、Znode节点

1、节点的类型

2、节点的状态属性

3、节点的特性

三、zookeeper实现分布式锁

1、错误的实现分布式锁方式

2、正确的实现分布式锁方式

3、代码实现

4、总结

四、zookeeper的角色

Leader

Follower

Observer

五、CAP理论

六、BASE理论


一、介绍

zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制,防止出险脏读,例如我们常说的分布式锁。

zookeeper中的数据是存储在内存当中的,因此它的效率十分高效。它内部的存储方式十分类似于文件存储结构,采用了分层存储结构。但是它和文件存储结构的区别是,它的各个节点中是允许存储数据的,需要注意的是zk的每个节点存储数据不能超过1M。它的内存数据结果如下图:

每个节点称做一个ZNode,每个ZNode都可以通过其路径唯一标识

二、Znode节点

1、节点的类型

  • 持久化目录节点(PERSISTENT)

客户端与zookeeper断开连接后,该节点依旧存在。

  • 持久化顺序编号目录节点(PERSISTENT_SEQUENTIAL)

客户端与zookeeper断开连接后,该节点依旧存在,Zookeeper会给该节点按照顺序编号。

  • 临时目录节点(EPHEMERAL)

客户端与zookeeper断开连接后,该节点被删除。

  • 临时顺序编号目录节点(EPHEMERAL_SEQUENTIAL)

客户端与zookeeper断开连接后,该节点被删除,Zookeeper会给该节点按照顺序编号

2、节点的状态属性

属性名作用
cZxid创建节点时的事务ID
ctime创建节点时的时间
mtime最后修改节点时的时间
pZxid

表示该节点的子节点列表最后一次修改的事务ID,添加子节点或删除子节点就会影响子节点列表但是修改子节点的数据内容则不影响该ID(注意,只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid)

cversion子节点版本号,子节点每次修改版本号加1
dataversion数据版本号,数据每次修改该版本号加1
aclversion权限版本号,权限每次修改该版本号加1
ephemeralOwner创建该临时节点的会话的sessionID。(如果该节点是持久节点,那么这个属性值为0)
dataLength该节点的数据长度
numChildren该节点拥有子节点的数量(只统计直接子节点的数量)

3、节点的特性

  • 同一级节点 key 名称是唯一的

  • 创建节点时,必须要带上全路径

  • session 关闭,临时节点清除

  • delete 命令只能一层一层删除(新版本可以通过 deleteall 命令递归删除。)

  • watch 机制,监听节点变化

事件监听机制类似于观察者模式,watch 流程是客户端向服务端某个节点路径上注册一个 watcher,同时客户端也会存储特定的 watcher,当节点数据或子节点发生变化时,服务端通知客户端,客户端进行回调处理。特别注意:监听事件被单次触发后,事件就失效了。

三、zookeeper实现分布式锁

1、错误的实现分布式锁方式

这里我们使用的是​​临时节点​​

锁原理​:

多个客户端同时去创建同一个临时节点,哪个客户端第一个创建成功,就成功的获取锁,其他客户端获取失败。

就像双十一,十万人同时秒杀一个商品,谁手速快谁就能秒杀到。

获取锁的流程:

  • 四个客户端同时创建一个临时节点。
  • 谁第一个创建成功临时节点,就代表持有了这个锁(这里临时节点就代表锁)。
  • 其他红色的客户端判断已经有人创建成功了,就开始监听这个临时节点的变化。

释放锁的流程:

  • 红色线的客户端执行任务完毕,与zookeeper断开了连接。
  • 这时候临时节点会自动被删除掉,因为他是临时的。
  • 其他绿色线的客户端watch监听到临时节点删除了,就会一拥而上去创建临时节点(创建锁)

​存在的问题分析:

当临时节点被删除的时候,其余3个客户端一拥而上抢着创建节点。3个节点比较少,性能上看不出什么问题。

那如果是一千个客户端在监听节点呢?一旦节点被删除了,会唤醒一千个客户端,一千个客户端同时来创建节点。但是只有一个客户端能创建成功,却要让一千个客户端来竞争。

对zookeeper的压力会很大,同时浪费这些客户端的线程资源,其中有999个客户端是白跑一趟的。这就叫做​​惊群​​​现象,也叫​​羊群​​现象。
 

2、正确的实现分布式锁方式

这里用的是顺序临时节点

​锁原理:

多个客户端来竞争锁,各自创建自己的节点,按照顺序创建,谁排在第一个,谁就成功的获取了锁。

就像排队买东西一样,谁排在第一个,谁就先买。

​创建锁的过程:

  • A、B、C、D四个客户端来抢锁
  • A先来了,他创建了000001的临时顺序节点,他发现自己是最小的节点,那么就成功的获取到了锁
  • 然后B来获取锁,他按照顺序创建了000001的临时顺序节点,发现前面有一个比他小的节点,那么就获取锁失败。他开始监听A客户端,看他什么时候能释放锁
  • C和D与B同理

​释放锁的过程: 

  • A客户端执行完任务后,断开了和zookeeper的会话,这时候临时顺序节点自动删除了,也就释放了锁
  • B客户端一直在虎视眈眈的watch监听着A,发现他释放了锁,立马就判断自己是不是最小的节点,如果是就获取锁成功
  • C监听着B,D监听着C。

​合理性分析: 

A释放锁会唤醒B,B获取到锁,对C和D是没有影响的,因为B的节点并没有发生变化。

同时B释放锁,唤醒C,C获取锁,对D是没有影响的,因为C的节点没有变化。

同理D。。。。

释放锁的操作,只会唤醒下一个客户端,不会唤醒所有的客户端。所以这种方案不存在惊群现象

​ps​:创建临时节点 = 创建锁,删除临时节点 = 释放锁。

3、代码实现

pom文件配置:

<dependency>
     <groupId>org.apache.curator</groupId>
     <artifactId>curator-framework</artifactId>
     <version>4.0.1</version>
</dependency>
<dependency>
     <groupId>org.apache.curator</groupId>
     <artifactId>curator-recipes</artifactId>
     <version>4.0.1</version>
</dependency>

​java代码:

/**
 * curator分布式锁测试
 */
public class CuratorDistrLockTest implements Runnable {

    //zookeeper的地址
    private static final String ZK_ADDRESS = "192.168.30.128:2181";
    // 节点的目录
    private static final String ZK_LOCK_PATH = "/zkLock";

    static CuratorFramework client = null;

    static {
        // 连接ZK,如果连接失败,设置每5000毫秒重试一次,最多重试10次
        client = CuratorFrameworkFactory.newClient(ZK_ADDRESS,
                new RetryNTimes(10, 5000));
        client.start();
    }

    @Override
    public void run() {
        InterProcessMutex lock = new InterProcessMutex(client, ZK_LOCK_PATH);
        try {
            if (lock.acquire(6 * 1000, TimeUnit.SECONDS)) {
                System.out.println("====== " + Thread.currentThread().getName() + " 抢到了锁 ======");
                //执行业务逻辑
                Thread.sleep(15000);
                System.out.println(Thread.currentThread().getName() + "任务执行完毕");
            }
        } catch (Exception e) {
            System.out.println("业务异常");
        } finally {
            try {
                lock.release();
            } catch (Exception e) {
                System.out.println("锁释放异常");
            }
        }
    }

    public static void main(String[] args) {
        // 用两个线程,模拟两个客户端
        // 每个线程创建各自的zookeeper连接对象
        new Thread(new CuratorDistrLockTest()).start();
        new Thread(new CuratorDistrLockTest()).start();
    }
    
}

4、总结

为什么不采用持久节点呢,因为持久节点必须要客户端手动删除,否则他会一直存在zookeeper中。

如果我们的客户端获取到了锁,还没释放锁就突然宕机了,那么这个锁会一直存在不被释放。导致其他客户端无法获取锁。

zookeeper实现的锁功能是比较健全的,但是性能上稍微差一些。比如zookeeper要维护集群自身信息的一致性,频繁创建和删除节点等原因。

如果仅仅是为了实现分布式锁而维护一套zookeeper集群,有点浪费了。

如果公司本来就有zookeeper集群,同时并发不是非常大的情况下,可以考虑zookeeper实现分布式锁。

Redis在分布式锁方面的性能要高于zookeeper,同时他也存在他的缺点,我们之后会分析。

四、zookeeper的角色

Zookeeper集群是一个基于主从复制的高可用集群,每个服务器承担如下三种角色中的一种。

  • Leader

  1. 一个 Zookeeper 集群同一时间只会有一个实际工作的 Leader,它会发起并维护与各 Follwer 及Observer间的心跳。
  2.  所有的写操作必须要通过 Leader 完成再由 Leader 将写操作广播给其它服务器。只要有超过 半数节点(不包括observeer节点)写入成功,该写请求就会被提交
  • Follower

  1.  一个Zookeeper集群可能同时存在多个Follower,它会响应Leader的心跳
  2.  Follower可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理
  3. 并且负责在Leader处理写请求时对请求进行投票。
  • Observer

  1. 角色与Follower类似,但是无投票权。
  2. Observer可直接处理并返回客户端的读请求,同时会将写请求转发给Leader处理; 加入更多Observer节点,提高伸缩性,同时不影响吞吐率。

五、CAP理论

CAP 理论指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  • 一致性:在分布式系统中的所有数据备份,对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
  • 可用性:分布式系统在面对各种异常时可以提供正常服务的能力,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
  • 分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。

一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

在这三个基本需求中,最多只能同时满足其中的两项,P 是必须的,因此只能在 CP 和 AP 中选择。zookeeper 保证的是 CP,eruka 实现的是 AP,nacos既可以实现AP也可以实现CP。

如果系统没有发生“分区”的话,节点间的网络连接通信正常的话,也就不存在 P 了。这个时候,我们就可以同时保证 C 和 A 了。因此,如果系统发生“分区”,我们要考虑选择 CP 还是 AP。如果系统没有发生“分区”的话,我们要思考如何保证 CA 。

因此,AP 方案只是在系统发生分区的时候放弃一致性,而不是永远放弃一致性。在分区故障恢复后,系统应该达到最终一致性。这一点其实就是 BASE 理论延伸的地方。

六、BASE理论

BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。

核心思想:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充。

  • 基本可用

基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。

什么叫允许损失部分可用性呢?

  • 响应时间上的损失: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理用户请求的时间变为 3 s。
  • 系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
  • 软状态

软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

  • 最终一致性

最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

分布式一致性的 3 种级别:

  1. 强一致性 :系统写入了什么,读出来的就是什么。

  2. 弱一致性 :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。

  3. 最终一致性 :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值