ZooKeeper:分布式环境下的一些问题及一致性协议

2020-02-19  本文已影响0人  我也有键盘

弱鸡的学习总结,zk篇第一回。 本篇文章总结了分布式环境下产生的一些问题,及分布式一致性协议。顺便学习了ZooKeeper的ZAB协议,学完对事务的提交、Leader的选举有了更深刻的认识。

什么是ZooKeeper

zk是一个分布式协调组件,为了解决分布式一致性问题,实现分布式锁。

分布式环境中存在哪些问题

什么是分布式一致性问题?

一致性是指,数据在多个副本之间要保持一致的特性。 在一个分布式系统中,存在多个节点,每个节点都会提出一个请求,这些请求必须要被所有节点确认,达成一致,确定最后只有一个请求被通过。

分布式事务

狭义上的事务是指数据库事务,广义上它可以看作程序对系统中的数据进行的一系列访问与更新操作组成的一个程序执行逻辑单元。

事务具有四个特性,即 ACID特性,分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

一个分布式事务可以看作是多个分布式的操作序列组成(子事务),因此分布式事务可以堪称一种嵌套型的事务。

CAP理论和BASE理论

CAP理论 :一个分布式系统不可能同时满足一致性(C: Consistency)、可用性(A: Availability)和分区容错性(P: Partition tolerance)这三个基本需求,最多只能同时满足其中两项。

但是,对于一个分布式系统而言,分区容错性(P)是一个最基本的要求,不能放弃。因此,必须根据业务特点在一致性(C)和可用性(A)之间寻求平衡。

前辈们在权衡 C\A 的实践过程中总结出了BASE理论:

BASE理论: Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)。

一致性协议

当一个事务跨越多个节点时,为了保持事务的ACID特性,就需要引入一个“协调者 Coordinator”来统一调度所有节点的执行逻辑,被调度的节点称为“参与者 Participant”。 协调者负责调度参与者的行为,并最终决定是否把事务真正进行提交。

ZAB协议包括两种基本模式:崩溃恢复消息广播

崩溃恢复:当集群中的Leader服务器出现崩溃、掉线或重启等异常情况时,ZAB协议就进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader,同时集群中已经有过半的服务器与新的Leader完成状态同步后,ZAB就退出恢复模式。


消息广播: 类似于一个二阶段提交过程。对于客户端的事务请求,Leader服务器会为其生成对应的事务Proposal,并发给集群中所有Follower服务器,然后收集选票进行事务提交。

在广播之前,Leader首先为Proposal分配一个全局递增且唯一的事务ID,即ZXID(64位)。每一个事务Proposal按照ZXID的先后顺序进行排序处理,具体的,Leader为每一个Follower都分配一个队列,并将Proposal按ZXID依次放入队列中,根据FIFO的策略发送给Follower。

Follower接收到Proposal后,先将其以事务日志的形式写入到本地磁盘中,写入成功后返回给Leader一个Ack响应。当Leader收到超过半数Follower的Ack后,就广播一个Commit消息给所有Follower服务器通知其进行事务提交,同时Leader本身也执行这次事务提交。


当Leader失去连接时,需要保证:

​ a. 已经被Leader提交的事务不丢失

​ b. 没有被Leader提交的事务要跳过

leader掉线后.png

如图,新的集群保留P1 P2后,开始了新Leader统治的新篇章。即:

新选出来的Leader服务器要拥有急群众所有机器最大的ZXID, 这样就能保证新Leader一定具备所有已经提交的Proposal,而且可以省去新的Leader服务器检验Proposal的提交和丢弃这一操作。
ZXID代表Proposal的事务ID,它的前32位用来标识Leader的朝代 epoch,后32位作为计数器记录事务ID.当新的Leader产生后,ZXID的前32位加1,后32位清0。
所以Leader选举时,先比较epoch,epoch大的当选;epoch相同,再比较事务id zxid;都相同比较myid,myid大的优先。

zk的分布式锁

借用curator帮忙实现。 首先需要依赖:

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

然后是小demo:

public static void main(String[] args) {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().
                connectString("127.0.0.1:2181").sessionTimeoutMs(5000).
                retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
        curatorFramework.start();
        //分布式可重入排它锁
        InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/lock-demo");

        for (int i = 0; i < 10; i++) {

            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 尝试竞争锁");
                    lock.acquire();
                    System.out.println(Thread.currentThread().getName() + " 成功获得了锁");
                } catch (Exception e) {

                }
                try {
                    //睡3秒,可以登陆zk-cli去服务器上看看生成的节点是什么样的
                    Thread.sleep(3000);
                } catch (Exception e) {

                } finally {
                    try{
                        lock.release();
                        System.out.println(Thread.currentThread().getName() + " 释放了锁");

                    } catch (Exception e) {

                    }
                }
            }).start();
        }

    }

最后总结下以前被面试过的: zk 分布式锁为什么比 redis分布式锁更好?

如果非要说zk分布式锁比redis好,大概主要是利用了zk的两个特性:

  1. zk支持有序节点,假设最小的节点为获得锁,那么只要判断当前节点是否为所有子节点中最小的即可。如果当前节点不是所有子节点最小的,那么就意味着没有过的锁。当比自己小的节点删除以后,客户端收到wathcer事件,此时再判断自己的节点是不是最小的,重复这个过程直到获得锁。可以避免所有客户端同时竞争锁(惊群现象)。

  2. zk临时节点的特性:当客户端session结束后,临时节点自动删除,即释放锁。不像redis需要手动删除,或者设置超时时间。

参考书籍

《从Paxos到ZooKeeper分布式一致性原理与实践》,倪超

上一篇下一篇

猜你喜欢

热点阅读