zookeeper分布锁
zookeeper监控集群
CAP理论:高可用、容错性、一致性最多取其二
BASE理论:B基本可用 、弱状态、最终一致性
发布订阅:redis mq
负载均衡:nginx apche httpd
命名服务:
分布式协调 通知
集群管理 :
Master选举:主从结构
分布式锁:redis
分布式队列
高可用和一致性取舍问题
分布式事务 强一致性:2PC(二阶段提交) 3PC(3阶段提交)
不论是二阶段(强一致性)提交还是三阶段提交 本质是尽可能晚提交(在事务提交前完成其他所有工作)。提交阶段耗时较短出现错误概率低。
JTA(java里面的分布式事务编程接口规范-JTA) JBOSS(天生支持分布式)
解决了单点故障问题引发问题,减少阻塞发生。根据定时器,没有接收到协调者的信息默认执行commit,可能造成数据不一致问题。(其他参与者可能执行了abort)
高可用性 集群 服务拆分:主从结构->MASTER选举 节点数据不一致
PAXOS算法:zookeeper核心原理 对多个分布式线程数据达成一致的算法
NWR策略 N:数据分片 W:Write 至少由W份数据写入成功 R:Read 至少有R份数据读取成功。 不要求数据强一致性 解决读写均衡的一种方式。Read Only Write All 本质上释放了写模式的压力转给读模式。一般情况要求W+R>N。保证不存在一个数据同时存在读写,一般读写超过N/2就返回结果成功,后续数据同步更新操作由系统后台运行。
zookeeper本身就是个分布式系统 单点故障?
一般部署奇数个zk服务器 服务器宕机数量节点总数不变若部署5个3个宕机失败 若6个3个宕机失败 so//
zk只有一个master 多个slaver
Zab(Zookeeper Atomic Broadcast):解决事务一致性问题zk使用ZAB协议。专门用来设计支持崩溃回复和原子广播的协议。
Zookeeper使用主备模式保证数据一致性:
客户端将信息写入主进程(leader)中然后由leader将数据备份到follower中
复制过程同2PC类似,只要由一般备份返回ACK即可执行提交。
ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。
当leader失去与过半的follower联系,进入崩溃回复模式。ZAB协议定义了两个原则:
解决思路:如果有这样的ZAB选举算法:根据zxid,保证选出来的服务器上有当前zxid最大的事务,那么就能保证当前服务器一定有已经成功提交的事务。
崩溃恢复之后,在接受客户端新的请求之气那,leader服务器需要确认事务是否都被过半的follwer提交,即是否完成数据同步。当确认所有的有效follower提交数据后将这些服务器添加到可用服务器列表中。如何做:
在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。
高 32 位代表了每代 Leader 的唯一性,低 32 代表了每代 Leader 中事务的唯一性。同时,也能让 Follwer 通过高 32 位识别不同的 Leader。简化了数据恢复流程。
基于这样的策略:当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。
实现方式:
节点种类:
加锁方式:创建节点,解锁方式:删除节点。
如果使用永久节点,当获取节点的客户端宕机,其他客户端不能获取锁,产生死锁。采用临时节点相当于增加了一个失效时间。
(1)、当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)
(2)、我们注册该节点的listener时,如果节点删除(数据修改),会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为时拿到锁的客户端释放锁,其他的客户端可以抢锁)
(3)、创建的节点应该时临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁
伪代码:
if(!have childnode){
create node(Ephemeral(临时));
}else {
zkClient.subscribeChildChanges("pth",new zkListener)
}
抽象类:
import org.I0Itec.zkclient.ZkClient;
/**
* @ClassName AbstractLock
* @Description TODO
* @Author YGuang
* @Date 2019/7/29 17:10
* @Version 1.0
**/
public abstract class AbstractLock {
public static final String ZK_ADDR = "localhost:2181,localhost:2182,localhost:2183";
public static final int SESSION_TIMEOUT = 10000;
//创建ZK
protected ZkClient zkClient=new ZkClient(ZK_ADDR,SESSION_TIMEOUT);
public void getlock(){
String threadName=Thread.currentThread().getName();
if(tryLock()){
System.out.println("获取当前" + threadName+ "锁成功");
}
else {
System.out.println("获取锁失败" +
threadName+"---");
waitLock();
//递归重新获取锁
getlock();
}
}
public abstract boolean tryLock();
public abstract void releaseLock();
public abstract void waitLock();
}
抽象类实现:
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName SimpleZklock
* @Description TODO
* @Author YGuang
* @Date 2019/7/29 17:09
* @Version 1.0
**/
public class SimpleZklock extends AbstractLock {
private static final String NODE_NAME = "/yg";
//计数器 初始化数是线程个数
private CountDownLatch countDownLatch;
@Override
//创建临时节点如果创建成功,获取锁,创建不成功处理异常
public boolean tryLock() {
if (zkClient == null) {
return false;
}
try {
zkClient.createEphemeral(NODE_NAME);
return true;
} catch (Exception e) {
System.out.println(e + "cant get Lock" + "wait---------------");
return false;
}
}
@Override
//释放锁
public void releaseLock() {
if (zkClient != null) {
zkClient.delete(NODE_NAME);
zkClient.close();
System.out.println("releaseLock success----------");
}
}
@Override
//等待锁的过程
public void waitLock() {
//创建监听器
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String datapath, Object data) throws Exception {
}
@Override
//节点删除被回调
public void handleDataDeleted(String datapath) throws Exception {
if (countDownLatch != null) {
//给count数减1(默认一个线程已访问?)
countDownLatch.countDown();
}
}
};
zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
if (zkClient.exists(NODE_NAME)) {
//如果存在阻塞 设置计时器为1
countDownLatch = new CountDownLatch(1);
try {
//阻塞在当前线程直到所被释放 可能会出现死锁?
countDownLatch.await();
System.out.println("wait lock"+"-------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(NODE_NAME,iZkDataListener);
}
}
测试:
/**
* @ClassName LockTest
* @Description TODO
* @Author YGuang
* @Date 2019/7/30 10:08
* @Version 1.0
**/
public class LockTest {
public static void main(String[] args) {
for (int i=1;i<10;i++){
new Thread(()->{
AbstractLock zkLock=new SimpleZklock();
zkLock.getlock();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
zkLock.releaseLock();
}).start();
}
}
}
多节点代码:
import org.I0Itec.zkclient.IZkDataListener;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName HighPerformanceLock
* @Description TODO
* @Author YGuang
* @Date 2019/7/30 11:17
* @Version 1.0
**/
public class HighPerformanceLock extends AbstractLock {
public static final String PATH="/highyg";
//当前节点路径
private String currentPath;
//前一个节点路径
private String beforePath;
private CountDownLatch countDownLatch;
public HighPerformanceLock(){
//如果不存在父节点,创建持久节点
if(!zkClient.exists(PATH)){
zkClient.createPersistent(PATH);
}
}
@Override
public boolean tryLock() {
if (null == currentPath || "".equals(currentPath)) {
//在path下创建临时顺序节点将他赋予curretpath 临时节点没必要使用名字 因为临时节点会在一个线程结束后被释放 多个线程访问时会生成多个对应临时节点。
currentPath = zkClient.createEphemeralSequential(PATH + "/", "is a lock");
}
List <String> children = zkClient.getChildren(PATH);
Collections.sort(children);
if (currentPath.equals(PATH + "/" + children.get(0))) {
//如果是第一个即可
return true;
} else {//不是第一个值则获取他前面的节点名称,赋值给beforepath
int pathlength = PATH.length();
String midd=currentPath.substring(pathlength+1);
int index = Collections.binarySearch(children, currentPath.substring(pathlength + 1));
beforePath = PATH + "/" + children.get(index - 1);
}
return false;
}
@Override
public void releaseLock() {
if (null!=zkClient){
//释放节点 删除现在持有节点
zkClient.delete(currentPath);
System.out.println("release current Lock------------");
zkClient.close();
}
}
@Override
public void waitLock() {
IZkDataListener iZkDataListener=new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String datapath) throws Exception {
if(null!=countDownLatch){
countDownLatch.countDown();
}
}
};
zkClient.subscribeDataChanges(beforePath,iZkDataListener);
if (zkClient.exists(beforePath)){
countDownLatch=new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wai lock ---------");
zkClient.unsubscribeDataChanges(beforePath,iZkDataListener);
}
}
}