基于Zookeeper实现高可用高性能分布式锁

分布式锁实现简单介绍

分布式锁的实现方式有很多中常用到的可能是:
1、具于数据库

1、利用select … where … for update 排他锁
这里需要注意的是:
where name = "lock" 这个name一定要设置索引,不然数据库会锁整张表。但是特殊情况下,由于数据量不大,mysql优化器可能走不到这个索引,仍然出现锁表问题。
2、利用数据库乐观锁 
基于CAS思想,是不具有互斥性,不会产生锁等待而消耗资源。
操作过程中认为不存在并发冲突,只有update version失败后才能觉察到。不过轮询检查消耗资源。

2、具于Redis

利用Redis的高性能,具于CAS的思想。锁的可控性不太好如:锁删除失败,过期时间不好控制。还有就是轮询获取锁消耗性能。

3、具于Zookeeper

利用zookeeper的临时有序节点去实现,判断当前节点是否是当前节点中最小的一个。
如果是那么获取锁成功。
如果不是去监听当前节点的挨着的上一个节点。
当收到上一个节点删除的消息。判断当前节点是否最小的,如果是获取成功。
性能没有Redis高,但是不用担心死锁,或者去轮询检查消耗资源。可靠性还高。
需要注意的是:master节点挂了以后,在选举出master过程中,zookeeper是不可的。

zookeeper分布式锁实现代码(具于zookeeper 原生API实现)

package top.shop520.zookeeper.lock;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/**
 * zk分布式锁思路
 * 1、创建一个  【临时有序节点】
 * 2、判断当前节点是不是创建临时节点里边最小的一个
 * 3、如果不是监听当前节点距离最近的 比当前节点小的那个
 * 4、当监听到的节点删除了,再去判断当前节点是不是最小的一个
 * 5、如果是 表示获取锁成功
 * @author zhang
 *
 */
public class ZkLock {
     

	private final String ZK_HOST = "192.168.199.242:2181";
	private final int SESSION_TIMEOUT = 5000;// 毫秒
	private ZooKeeper zk = null;
	private CountDownLatch countDownLatch = new CountDownLatch(1);
	
	private final String LOCK = "/lock";
	private final String LOCK_NAME = "lock_name";
	//保存当前锁节点名称
	private String lockpath;
	
	public ZkLock() {
     
		init();
	}
	public void init() {
     
		try {
     
			zk = new ZooKeeper(ZK_HOST,SESSION_TIMEOUT,new Watcher() {
     

				@Override
				public void process(WatchedEvent event) {
     
					if(event.getType()==Event.EventType.None) {
     
						if(event.getState()==Event.KeeperState.SyncConnected) {
     
							System.out.println("启动成功!");
							countDownLatch.countDown();
						}
					}
					//会话超时可能是因为网络颤动导致,让zk去重新连接
					if(event.getState() == Event.KeeperState.Expired) {
     
						System.out.println("会话超时!");
						init();
					}
				}
				
			});
			countDownLatch.await();
		} catch (Exception e) {
     
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取锁
	 */
	public void acquiredLock() {
     
		try {
     
			//创建锁节点
			createLock();
			//尝试获取锁节点
			attemptLock();
			
		} catch (Exception e) {
     
			System.out.println("获取锁异常:");
			e.printStackTrace();
		}
	}
	
	/**
	 * 尝试获取锁
	 */
	private void attemptLock() {
     
		try {
     
			//获取锁节点下所有子节点
			List<String> list = zk.getChildren(LOCK, false);
			//对子节点数据进行排序
			Collections.sort(list);
			//获取当前节点的位置
			int index = list.indexOf(lockpath.substring(LOCK.length()+1));
			//如果是第一个 那么表示获取锁成功
			if(index == 0) {
     
				System.out.println("获取锁成功");
				return;
			}else {
     
				//获取上一个节点
				String preNode = list.get(index-1);
				//去监听这个节点
				Stat stat = zk.exists(LOCK+"/"+preNode, watcher);
				//这里判断 这个节点是否已经删除了,
				if(null==stat) {
     
					attemptLock();
				}else {
     
					/**
					 * 如果上一个节点还存在,那么这里去同步等待
					 * 让watcher 去等待
					 */
					synchronized (watcher) {
     
						watcher.wait();
					}
					//然后接着去获尝试获取锁
					attemptLock();
				}
			}
			System.out.println(list);
		} catch (Exception e) {
     
			e.printStackTrace();
		}
	}
	/**
	 * 监听锁释放的监听器
	 */
	Watcher watcher = new Watcher() {
     

		@Override
		public void process(WatchedEvent event) {
     
			//当监听器监听到节点被删除了
			//释放上边的那个等待锁
			if(event.getType()==Event.EventType.NodeDeleted) {
     
				synchronized (this) {
     
					//去告诉主线程可以向下执行了
					notifyAll();	//注意不能使用 notify 它只能随机的去唤醒一个线程,而且唤醒那个由操作系统控制。
				}
			}
			
		}
	};
	
	/**
	 * 创建锁节点
	 */
	private void createLock() {
     
		try {
     
			//判断当前节点是否存在
			Stat stat = zk.exists(LOCK, false);
			if(null == stat) {
     
				//这个节点创建一个持久话节点,不用每次去创建
				String node = zk.create(LOCK, new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);
				System.out.println("保存锁的临时节点路径是:"+node);
			}
			//创建临时有序节点
			lockpath = zk.create(LOCK+"/"+LOCK_NAME, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
			System.out.println("临时有序节点是:"+lockpath);
		} catch (Exception e) {
     
			e.printStackTrace();
		}
		
	}
	/**
	 * 释放锁
	 */
	public void releaseLock() {
     
		//删除临时节点
		try {
     
			zk.delete(lockpath, -1);
			zk.close();
			System.out.println("锁已经释放:"+lockpath);
		} catch (Exception e) {
     
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
     
		//创建锁
		ZkLock zkLock = new ZkLock();
		//获取锁
		zkLock.acquiredLock();
		
		//这里去锁做业务
		
		
		//释放锁
		zkLock.releaseLock();

	}
	
}

使用demo及测试

我这里用多线程去模拟了分布式下,多客户端获取锁的情况
package top.shop520.zookeeper.lock;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;

public class OnSale {
     
	private String name;

	/**
	 * @return the name
	 */
	public String getName() {
     
		return name;
	}

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
     
		this.name = name;
	}

	ThreadLocalRandom random = ThreadLocalRandom.current();

	public void sale(int i) {
     
		try {
     
			Thread.sleep(random.nextInt(5000));
		} catch (InterruptedException e) {
     
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(name + " 卖出去了第:" + i + "件商品!");
	}

	
	/**
	 * 模拟多客户端并发
	 * @param args
	 */
	private static CountDownLatch countDownLatch = new CountDownLatch(10);
	private static ExecutorService threadPool = Executors.newCachedThreadPool();
	public static void main(String args[]) {
     
		for(int i=0;i<10;i++) {
     
			threadPool.execute(new Runnable() {
     
				@Override
				public void run() {
     
					long treadId = Thread.currentThread().getId();
					System.out.println("客户端:"+treadId+"开始");
					try {
     
						countDownLatch.await();
					} catch (InterruptedException e) {
     
						e.printStackTrace();
					}
					for(int i=0;i<10;i++) {
     
						OnSale onSale = new OnSale();
						onSale.setName("卖货啦:"+treadId);
						ZkLock zkLock = new ZkLock();
						zkLock.acquiredLock();
						onSale.sale(i);
						zkLock.releaseLock();
					}
					System.out.println("卖货啦:"+treadId+"卖完了");
				}
			});
			countDownLatch.countDown();
		}
	}
}

测试结果查看

启动程序,发现创建了10个客户端
基于Zookeeper实现高可用高性能分布式锁_第1张图片
执行过程中

发现我们的客户端严格按照所得顺序执行

基于Zookeeper实现高可用高性能分布式锁_第2张图片
执行结束
基于Zookeeper实现高可用高性能分布式锁_第3张图片
如果有发现问题,欢迎评论区指出。
如果有同学用不熟悉Zookeeper原生的api,可以查看这个博客了解,地址:
https://blog.csdn.net/RenJianLeLe/article/details/108034937

这里我发现了一个篇关于Redis去实现分布式锁的博客感觉写的还不错。
https://www.cnblogs.com/moxiaotao/p/10829799.html

你可能感兴趣的:(zookeeper,zookeeper,分布式,多线程,java)