Apache ZooKeeper是一个高可用的分布式协调中间件。Goole Chubby的一个开源实现,主要解决分布式一致性问题,提供分布式锁服务。
典型的拜占庭将军问题
Chubby提供了一种粗粒度的分布式锁服务,通过创建文件的形式实现,server向Chubby中创建文件表示加锁,创建成功则表示抢到锁。
由于Chubby没有开源,因此雅虎基于Chubby思想开发了一个类似的分布式协调组件Zookeeper,后捐献给了Apache。
zab协议 过半提交
epoch myid,zxid
当一个事务操作需要跨院多个分布式节点的时候,为了保持事务处理ACID特性,就需要引入一个“协调者”(TM)来同意调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为AP。TM负责调度AP的行为,并最终决定这些AP是否提交事务,整个过程分为两个阶段提交,因此叫2PC。
领导角色,负责发起投票,处理事务请求
跟随者,用于接受客户端请求并返回结果,投票过程中参与投票,处理非事务请求
zookeeper3.3引入一个全新的服务器角色,用于观察zookeeper集群中的最新状态变化并将这些变化同步到observer服务器上。不参与任何形式的投票,且observer服务器只提供feign事务请求服务。
将conf目录下的zoo_smaple.cfg文件复制一份重命名为zoo.cfg,修改文件中dataDir
server.1=IP1:2888:3888
server.2=IP2:2888:3888
server.3=IP3:2888:3888
2888表示zookeeper的端口,3888表示选举leader的端口
在每台zookeeper机器上,dataDir目录下创建myid文件,文件内容为服务器serverID,且需要确保每台都不重复
zookeeper节点称为ZNode是zookeeper的最小单元。每个ZNode上都可以保存数据和挂载子节点。构成一个层次性的树形结构。
ZNode包含stat-状态信息包含数据变化的时间和版本,value-值。
创建后会一直存在zookeeper服务器上,直到主动删除
每个节点都会为它的一级子几点维护一个顺序
生命周期和客户端会话绑定,当客户端失效则自动清理
在临时节点基础上增加排序
容器节点下最后一个节点被删除,容器节点自动删除
设置一个存活时间,存活时间内如果没有任何修改并且没有任何子节点自动删除
zookeeper允许客户端向服务端注册一个watcher监听,当服务端的一些指定事件触发了watcher,向客户端发送一个通知。watcher是一次性的,即一旦触发后,watcher就失效。
ZooKeeper提供三种机制针对Znode进行注册监听:
利用Zookeeper节点的特性实现分布式锁,即同级节点的唯一性,多个进程往Zookeeper的指定节点下相同名称的节点只能有一个成功,失败的节点通过Watcher机制监听,一旦监听到节点的删除事件则再次触发所有进场去写锁。
该方式容易产生惊群效应,简单来说就是如果存在很多客户端等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待的客户端都会被唤醒,此时大量子节点会被唤醒,会对Zookeeper服务器性能产生影响。
每个客户端都在指定的节点下注册一个临时的有序节点,越早创建的节点编号越小,依据最小的节点获取锁。该方式只需要每个节点监听编号比自己小的节点,当比自己小的节点删除,客户端收到watcher事件,此时再判断自己的节点是否是最小的,如果是则获得锁,否则不断重复该过程。
curator对锁进行了封装,提供了InterProcessMutex api。除此以外还提供了leader选举,分布式队列等功能。
public static void main(String[] args) throws Exception {
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().
connectString("127.0.0.1:2181").sessionTimeoutMs(50000000).
retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();
curatorFramework.start();
final InterProcessMutex lock=new InterProcessMutex(curatorFramework,"/locks");
for(int i=0;i<10;i++){
new Thread(()->{
try {
lock.acquire(); //阻塞竞争锁
System.out.println(Thread.currentThread().getName()+"->成功获得了锁");
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(400000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
try {
lock.release(); //释放锁
} catch (Exception e) {
e.printStackTrace();
}
}
},"Thread-"+i).start();
}
}
服务器编号,编号越大在选举算法中的权重越大
zxid即事务id,高32位epoch表示Leader周期变化,每一个新的Leader选举后epoch+1,低32位表示消息计数,值越大说明数据越新,在选举算法中的权重也越大
LOOKING-竞选状态
FOLLOWING-随从状态,同步Leader状态,参与投票
OBSERVING-观察状态,同步Leader状态,不参与投票
LEADING-领导状态
Curator支持两种选举方式:Leader Latch和LeaderSelector Election
参与选举的节点会创建一个顺序节点,其中最小的一个做为master节点,没抢到的节点都监听前一个节点的删除事件,当master节点手动删除,或者挂了,后续节点抢占master。spark就是使用这种方式。
与Leader Latch的区别在于,LeaderSelector释放领导权后,还可以继续竞争
LeaderSelectorClient leaderSelectorClient=new LeaderSelectorClient("ClientA");
LeaderSelector leaderSelector=new LeaderSelector(curatorFramework,"/leader",leaderSelectorClient);
leaderSelector.autoRequeue();//放弃leader后还要重新参与选举
leaderSelectorClient.setLeaderSelector(leaderSelector);
leaderSelectorClient.start(); //开始选举
zookeeper通过三个角色组成了高性能集群,在zookeeper中客户端会随机连接到任意一个节点,如果是读请求则读取数据,如果是写请求则转交给leader提交事务,然后leader会广播事务,只要有超过半数节点写入成功,则写请求提交。
Zookeeper Atomic Broadcast协议是为分布式协调服务专门设计的一种支持崩溃回复的原子广播协议。包括两种基本模式:消息广播和崩溃恢复。
实现原理:
与2PC事务不一样的地方在于,ZAB协议不会终止事务,follower要么回复ACK要么抛弃命令,Leader只要保证消息过半数就会提交,虽然在某一个时刻follower和leader状态不一致,但提升了整体性能。这种数据不一致,由ZAB协议的恢复模式同步数据。
实现原理:
ZAB协议确保已经被Leader提交的事务Proposal能够提交、同时丢弃已经被跳过的事务Proposal。
根据zab协议的同步流程,zookeeper集群内部数据副本是基于过半提交策略,意味着它是最终一致性。加上zookeeper提供的分布式锁服务,因此它是顺序一致性。
顺序一致性是在分布式环境中实现分布式锁的基本要求,即当多个程序抢锁,未抢到锁的程序看到锁的状态是抢到锁的程序状态。