zookeeper实现分布式锁

一、问题:

  • 当只有一个jvm时候,使用ReentrantLock,synchronized可以解决多线程并发带来的影响。当项目采用分布式部署时,就会有多套代码,那多套代码之间使用ReentrantLock的lock()、unlock()就无法互通,还是能各进到各的被锁代码块去同时修改数据

二、解决:

1、前提:

  • 安装zookeeper,并成功启动(安装启动具体不细讲,linux环境的话可参考另一篇博客zookeeper,windows安装可自行百度,这里只讲代码如何实现分布式锁)
  • 安装启动后,假设安装主机ip为:192.168.1.16,端口为:2181

2、需求:

  • 订单服务系统中要产生订单号,而订单号必须唯一,不能让多个线程同时去生成,订单号从0开始每创建一个订单就把订单号+1

3、方法:分布式锁

  1. 添加zookeeper的客户端依赖包
		<!-- zookeper的客户端zkclient -->
		<dependency>
		    <groupId>com.101tec</groupId>
		    <artifactId>zkclient</artifactId>
		    <version>0.10</version>
		</dependency>
  1. 封装与具体业务无关的代码。封装分布式锁的工具类,这些工具类不耦合具体业务,可以在任何需要分布式锁的地方通用。
  • (1)创建一个最底层接口ZkLock,只包含最基本的两个方法:lock()和unlock()
package com.aliyun.vuelogin.zklock;

public interface ZkLock {

	public void zkLock();

	public void zkUnLock();
}
  • (2)创建抽象类ZkAbstractTemplateLock并实现ZkLock接口,里面包含了连接zookeeper(zookeeper的ip、连接时长time、zookeeper临时节点名称等)的业务,并且抽象出两个与具体业务相关的两个方法,即根据不同的业务场景确定什么时候获取锁tryZkLock(),什么时候等待获取锁waitZkLock()
package com.aliyun.vuelogin.zklock;

import org.I0Itec.zkclient.ZkClient;

public abstract class ZkAbstractTemplateLock implements ZkLock {

	public static final String ZKSERVER = "192.168.1.16:2181";// 配置自己zookeeper安装的主机及其端口号

	public static final int TIME_OUT = 45 * 1000;

	protected String path = "/zkLock0401";// zookeeper节点名称,自己起,不可重复,实现此类时要用

	ZkClient zkClient = new ZkClient(ZKSERVER, TIME_OUT);

	@Override
	public void zkLock() {
		if (tryZkLock()) {
			System.out.println(Thread.currentThread().getName() + "获取锁成功!");
		} else {
			waitZkLock();
			zkLock();
		}
	}

	public abstract boolean tryZkLock();// 获取锁,获取成功返回true

	public abstract void waitZkLock();// 别人占有锁,需要等待

	@Override
	public void zkUnLock() {
		if (zkClient != null) {
			zkClient.close();
			System.out.println(Thread.currentThread().getName() + "释放锁成功!");
		}
	}
}

package com.aliyun.vuelogin.zklock;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;

public class ZkDistributedLock extends ZkAbstractTemplateLock {

	CountDownLatch countDownLatch = null;

	@Override
	public boolean tryZkLock() {// 获取

		try {
			zkClient.createEphemeral(path);// 创建zookeeper临时节点,占有锁时创建,释放锁时删除
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	@Override
	public void waitZkLock() {// 别人已经占有锁时,等待

		IZkDataListener iZkDataListener = new IZkDataListener() {// 创建监控器

			@Override
			public void handleDataDeleted(String arg0) throws Exception {// 一旦删除zookeeper节点,立马监控到,释放锁
				if (countDownLatch != null) {
					countDownLatch.countDown();
				}
			}

			@Override
			public void handleDataChange(String arg0, Object arg1) throws Exception {
			}
		};
		
		zkClient.subscribeDataChanges(path, iZkDataListener);// 订阅监控数据
		
		if (zkClient.exists(path)) {// 如果此节点被创建,即其他线程占有锁,则需要一直等待
			// 使用倒计时发射锁--闭锁(CountDownLatch:倒计时,为0放行)
			countDownLatch = new CountDownLatch(1);
			try {
				countDownLatch.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
  1. 与具体业务相关的代码
  • (1)模拟一个MVC的mapper:一个生产订单号的类
package com.aliyun.vuelogin.zklock;

public class OrderNumberCreateUtil {

	public static int number = 0;// 注意设置成静态,只加载初始化一次

	public String getOrdNumber() {
		return "生成订单号:" + (number++);
	}
}
  • (2)模拟MVC中的service
package com.aliyun.vuelogin.zklock;

public class OrderService {

	OrderNumberCreateUtil orderNumberCreateUtil = new OrderNumberCreateUtil();

	ZkDistributedLock zkDistributedLock = new ZkDistributedLock();

	public void getOrdNumber() {
		zkDistributedLock.zkLock();
		System.out.println("订单号为:" + orderNumberCreateUtil.getOrdNumber());
		zkDistributedLock.zkUnLock();
	}
}
  • (3)模拟三十个http请求分别到三十套代码的controller
package com.aliyun.vuelogin.zklock;

public class TsetZkDistributeLock {

	public static void main(String[] args) {

		for (int i = 0; i < 30; i++) {
			new Thread(() -> {
				new OrderService().getOrdNumber();
			}, String.valueOf(i)).start();
		}
	}
}

4、测试代码

在这里插入图片描述

5、补充

二-3-2-(2)中使用了模板模式:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值