zookeeper实现分布式锁

zookeeper系列之【IdGenerator,简单原理】

zookeeper系列之【IdGenerator,多线程测试】

zookeeper系列之【分布式锁】

zookeeper系列之【master选举】


加锁,在单进程,多线程下,就是对某个线程,在某个时间段内,独占某个对象资源。这个对象资源就是锁。

多进程,或者集群环境下,加锁,同样是在某个时间段内,独占某个对象资源(或者某个数据资源)。不同的是,信息交换,需要走网络。

这边介绍下Zookeeper实现分布式锁的方式。分公平锁和非公平锁。(锁的竞争实际上跟master的竞争是一个道理)

公平锁原理

这个过程就跟排队买火车票是一样的。窗口(锁)只有一个,如果有10个人排队,队伍中的任何一个人都不用关心队伍有多长。他只需要知道2个信息,① 我前面的人完事没有。② 我愿意等多久。

① 前面的人完事了,那我自然就可以买了,要不然我就继续等着。

② 我只愿意等1分钟,那过了1分钟,还没等到,我就溜呗。

第①点说明,队伍中任何一个人只需要watch前面的人,前面的人完事通知后面的人。

第②点说明,队伍中有人是可能中途放弃的,当这个人放弃之后,他后面的人watch的对象就会变成放弃的人的前面的人。

1.首先提供这个锁的base目录

2.我们把队伍中的没个人看做是准备获取锁的每个应用,并且给来排队的每个应用都来一个顺序唯一的编号。每个应用来排队的时候,在锁的base目录下创建EphemeralSequential节点。然后把列表下排队的节点都get回去。看看自己排在哪边,如果是第一个,则直接算是获取锁了。要不然就watch他的前一个。然后等着。

目录结构
/fairlock/lock1
├── /0000000000
└── /0000000002
└── /0000000003
└── /0000000004

非公平锁原理

公平锁,分先来后到,要排队。

非公平锁,看运气,谁抢的快就是谁的,部分先后。(可能出现锁饥饿问题,某个节点动作总是比人家慢一点,怎么都抢不到锁)

做法:指定Zookeeper上的锁节点(必须是还没有创建的),谁能第一个创建此节点,就算是获取了锁。

每个应用进来先尝试创建节点,如果成功,则算是获取锁。否则,watch这个节点(看它啥时候被删除),当此节点被删除的时候,就算是锁被释放了。然后被通知到的所有其他应用就继续尝试创建此节点。谁先创建成功,锁就是谁的。否则继续等待(如果设置了超时时长,那到时间就放弃等待,取消watch)。

目录结构
/unfairlock/lock1

代码实现

public interface ILock {

	/**
	 * 试图获取锁,不做等待
	 */
	boolean tryLock();

	/**
	 * 无限制等待
	 */
	void lock();

	/**
	 * 限制时长等待锁
	 * @param time
	 * @param timeUnit
	 * @return
	 */
	boolean lock(long time, TimeUnit timeUnit);

	/**
	 * 释放锁
	 */
	void releaseLock();
}
public class FairLock implements ILock {

	// 客户端持有
	private ZkClient client;

	// base目录+"/lockName"
	private String lockPath;

	// lockPath+"/"
	private String lockPathSon;

	// 当前持有的锁的全路径
	private String currentLockPath;

	public FairLock(ZkClient client, String basePath, String lockName) {
		this.client = client;

		if (!client.exists(basePath)) {
			client.createPersistent(basePath, true);
		}
		lockPath = basePath.concat("/").concat(lockName);

		if (!client.exists(lockPath)) {
			client.createPersistent(lockPath);
		}
		lockPathSon = lockPath.concat("/");
	}

	@Override
	public boolean tryLock() {
		return this.getLock(0, TimeUnit.MILLISECONDS);
	}

	@Override
	public void lock() {
		this.getLock(0, null);
	}

	@Override
	public boolean lock(long time, TimeUnit timeUnit) {
		return this.getLock(time, timeUnit);
	}

	private boolean getLock(long time, TimeUnit timeUnit) {
		// 进来就先创建临时顺序节点
		currentLockPath = client.createEphemeralSequential(lockPathSon, null);
		return this.waitLock(time, timeUnit);
	}

	private boolean waitLock(long time, TimeUnit timeUnit) {
		long start = System.currentTimeMillis();
		long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);// 后续要等待的时间

		List<String> children = client.getChildren(lockPath);
		Collections.sort(children);

		String nodeName = currentLockPath.substring(currentLockPath.lastIndexOf("/") + 1);
		boolean hasLock = nodeName.equals(children.get(0));// 判断自己是不是第一个
		if (hasLock) {
			return true;
		}
		if (timeUnit != null && waitMillos <= 0) {
			client.delete(currentLockPath);	// 超时放弃,则删除自己的节点
			return false;
		}

		int idx = Collections.binarySearch(children, nodeName);
		String watchPath = lockPathSon.concat(children.get(idx - 1));

		CountDownLatch countDownLatch = new CountDownLatch(1);
		IZkDataListener dataListener = new IZkDataListener() {

			@Override
			public void handleDataChange(String dataPath, Object data) throws Exception {
			}

			@Override
			public void handleDataDeleted(String dataPath) throws Exception {
				countDownLatch.countDown();
			}
		};
		client.subscribeDataChanges(watchPath, dataListener);// 找到自己的前一个节点关注上
		try {
			if (timeUnit == null) {
				countDownLatch.await();
			} else {
				countDownLatch.await(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		client.unsubscribeDataChanges(watchPath, dataListener);// 拿到了锁或者超时,取消关注

		return this.waitLock(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);// 递归等待
	}

	@Override
	public void releaseLock() {
		client.delete(currentLockPath);
	}
public class UnFairLock implements ILock {

	// 客户端持有
	private ZkClient client;

	// base目录
	private String lockPath;

	private CountDownLatch countDownLatch;

	private IZkDataListener dataListener = new IZkDataListener() {
		@Override
		public void handleDataChange(String dataPath, Object data) throws Exception {
		}

		@Override
		public void handleDataDeleted(String dataPath) throws Exception {
			boolean hasLock = takeLock();
			if (hasLock) {// 只有真正拿到了锁才通知
				countDownLatch.countDown();
			}
		}
	};

	public UnFairLock(ZkClient client, String basePath, String lockName) {
		this.client = client;

		if (!client.exists(basePath)) {
			client.createPersistent(basePath, true);
		}
		lockPath = basePath.concat("/").concat(lockName);
	}

	@Override
	public boolean tryLock() {
		return this.getLock(0, TimeUnit.MILLISECONDS);
	}

	@Override
	public void lock() {
		this.getLock(0, null);
	}

	@Override
	public boolean lock(long time, TimeUnit timeUnit) {
		return this.getLock(time, timeUnit);
	}

	private boolean takeLock() {
		try {
			client.createEphemeral(lockPath);
			return true;// 创建目录成功,就算是拿到了锁
		} catch (ZkNodeExistsException ignored) {
			return false;
		}
	}

	private boolean getLock(long time, TimeUnit timeUnit) {
		long start = System.currentTimeMillis();
		long waitMillos = timeUnit == null ? 0 : timeUnit.toMillis(time);

		boolean hasLock = this.takeLock();
		if (hasLock) {
			return true;
		}

		if (timeUnit != null && waitMillos <= 0) {
			return false;
		}

		countDownLatch = new CountDownLatch(1);
		client.subscribeDataChanges(lockPath, dataListener);
		boolean res = true;
		try {
			if (timeUnit == null) {// 永久等待
				countDownLatch.await();
			} else {
				// 超时情况有可能返回false
				res = countDownLatch.await(waitMillos - (System.currentTimeMillis() - start), TimeUnit.MILLISECONDS);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
			return false;
		}
		client.unsubscribeDataChanges(lockPath, dataListener);
		return res;
	}

	@Override
	public void releaseLock() {
		client.delete(lockPath);
	}
}
public class TestClient {

	private static final int THREAD_NUM = 10;

	public static void main(String[] args) throws InterruptedException {
		// 线程池
		ExecutorService executorService = new ThreadPoolExecutor(10, 100, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(200), new ThreadFactory() {

			private final AtomicInteger threadNum = new AtomicInteger(0);

			@Override
			public Thread newThread(Runnable runnable) {
				return new Thread(runnable, "pool-1-thread-" + threadNum.incrementAndGet());
			}
		});

		CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
		for (int i = 0; i < THREAD_NUM; i++) {
			executorService.execute(new Runnable() {
				@Override
				public void run() {
					ZkClient client = new ZkClient("localhost:2181,localhost:2182,localhost:2183", 5000, 5000, new SerializableSerializer());
					ILock fairLock = new FairLock(client, "/fairlock", "lock1");// 公平锁
//					ILock unFairLock = new UnFairLock(client, "/unfairlock", "lock1");// 非公平锁
					try {
						fairLock.lock();// 上锁
						System.out.println(Thread.currentThread().getName() + " get lock");
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
						fairLock.releaseLock();// 释放锁
						System.out.println(Thread.currentThread().getName() + " release lock");
					}
					countDownLatch.countDown();
				}
			});
		}

		countDownLatch.await();
		executorService.shutdown();
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值