java使用zookeeper实现分布式锁

     在博文中已知晓3中实现分布式锁方案的优缺点。现在写个测试类试试。

基于ZooKeeper分布式锁的流程

  • 在zookeeper指定节点(locks)下创建临时顺序节点node_n
  • 获取locks下所有子节点children
  • 对子节点按节点自增序号从小到大排序
  • 判断本节点是不是第一个子节点,若是,则获取锁;若不是,则监听比该节点小的那个节点的删除事件
  • 若监听事件生效,则回到第二步重新进行判断,直到获取到锁

举个例子:假如要抢购iphone,那在zookeeper中先创建指定节点/iphone,是永久性的。比如A,B,C3人同时抢购,都同时在/iphone节点下创建临时顺序节点B为/iphone/uuid_lock_0000000001,A为/iphone/uuid_lock_0000000002,C为/iphone/uuid_lock_0000000003。谁先创建节点,序号越小。B获取所有子节点后,发现最小排在最前面,那它那锁;A获取所有子节点后,发现它不是排在最前面,它不拿锁,而是监听B节点的删除事件;B抢完后,释放锁(删除节点),触发通知A节点,A节点再次获取所有子节点,检查自己是不是排最前面了。依次类推。

按照这思路,有人使用zookeeper client的api,自己实现获取锁和释放锁的过程。

参考别人的博文。

但是有外国大神基于zookeeper 的client api进行封装,提供了Curator工具,其中的InterProcessMutex封装了获取锁和释放锁,可以拿来使用。本博文就记录下使用InterProcessMutex来做分布式锁的测试。

pom.xml

<dependency>
		    <groupId>org.apache.zookeeper</groupId>
		    <artifactId>zookeeper</artifactId>
		    <version>3.4.13</version>
		    <type>pom</type>
	   </dependency>
  
  	<dependency>
	    <groupId>org.apache.curator</groupId>
	    <artifactId>curator-recipes</artifactId>
	    <version>2.13.0</version>
	</dependency>
  
  <dependency>
	    <groupId>org.apache.curator</groupId>
	    <artifactId>curator-client</artifactId>
	    <version>2.13.0</version>
	</dependency>

注意,curator的版本所使用的不要使用太高。比如我本地安装的zookeper版本是3.4.13,如果curator使用3.x或者4.x,然后看mavean的依赖包,发现zookeeper的jar是5.x版本的,高于本地安装的zookeeper版本,运行时报错;调为2.x就OK了。

package com.fei;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class Test01 {

	
	private static String produceName = "iphone";//商品名称
	private static int produceNum = 20;//商品数量
	
	
	public static CuratorFramework getClient(){
		//操作失败重试机制 1000毫秒间隔 重试3次
		RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
		//创建Curator客户端
		CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", retryPolicy);
		return client;
	}
	
	
	public static void main(String[] args) throws Exception {
		
		List<Thread> threads = new ArrayList<Thread>();
		for(int i=0 ;i<100;i++){
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					
					//商品没了
					if(produceNum <=0){
				    	return;
				    }
					
					CuratorFramework client = Test01.getClient();
					client.start();
					/** InterProcessMutex,是线程安全的,一个JVM创建一个即可
					 *   这里每个线程里都独自创建,是为了模拟多个JVM
					 */
					InterProcessMutex lock = new InterProcessMutex(client,"/"+produceName);
					try {
						lock.acquire();
					} catch (Exception e1) {
						e1.printStackTrace();
						System.out.println(Thread.currentThread().getName() + "抢锁异常");
					}
				    //抢到锁了,但是商品没了
					 if(produceNum <=0){
					    	client.close();
					    	return;
					    }
					
				    int sleepMills = new Random().nextInt(1000);
				    int tmp = produceNum;
				    try {
				    	//模拟生成订单耗时,库存减少耗时
						Thread.sleep(sleepMills);//休眠
						 produceNum = produceNum -1;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				    
				   
					System.out.println(Thread.currentThread().getName() +" 休眠" + sleepMills + " mills " + " 抢到"+produceName + "  " + tmp);
					
					try {
						lock.release();
					} catch (Exception e) {
						e.printStackTrace();
						System.out.println(Thread.currentThread().getName() + "释放锁失败");
					}
					client.close();
				}
					
		},"线程"+i);
			
		threads.add(t);
	}
		
		for(Thread t : threads){
			t.start();
		}
		
		for(Thread t : threads){
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
	
}

执行结果

线程77 休眠835 mills  抢到iphone  20
线程56 休眠837 mills  抢到iphone  19
线程61 休眠714 mills  抢到iphone  18
线程65 休眠532 mills  抢到iphone  17
线程52 休眠485 mills  抢到iphone  16
线程32 休眠396 mills  抢到iphone  15
线程40 休眠798 mills  抢到iphone  14
线程43 休眠440 mills  抢到iphone  13
线程68 休眠308 mills  抢到iphone  12
线程9 休眠326 mills  抢到iphone  11
线程35 休眠137 mills  抢到iphone  10
线程41 休眠579 mills  抢到iphone  9
线程0 休眠797 mills  抢到iphone  8
线程8 休眠505 mills  抢到iphone  7
线程16 休眠922 mills  抢到iphone  6
线程50 休眠83 mills  抢到iphone  5
线程15 休眠78 mills  抢到iphone  4
线程26 休眠787 mills  抢到iphone  3
线程66 休眠123 mills  抢到iphone  2
线程6 休眠977 mills  抢到iphone  1

把休眠时间设置大点,比如2000,然后使用zookepper提供的zkCli.cmd,ls /iphone,会看到有非常多的临时顺序节点

[zk: localhost:2181(CONNECTED) 2] ls /iphone
[_c_ecdd4a5e-54fc-40e0-abd2-889e854374be-lock-0000000231, _c_c7c79044-e4f3-45fd-b9fb-79ed7be4d640-lock-0000000216, _c_729bb483-2f38-42f6-a038-931354d0f19f-lock-0000000251, _c_eec63d70-1b0d-41ce-b288-b59d7fd8ed16-lock-0000000257, _c_76e5885c-77a0-4501-9bbb-28699d743b9b-lock-0000000211, _c_2c79f594-8824-476d-b514-1ec272ac662b-lock-0000000203, _c_fce5ef15-cdc5-47be-bffa-95ebea03010f-lock-0000000226, _c_f34e61a4-10c3-43f7-ac6f-ed957623b173-lock-0000000244, _c_794b0115-ab93-4221-90ad-052758431b60-lock-0000000210, _c_8179eaa5-d204-47de-a390-c55ac397ec88-lock-0000000220, _c_932982a5-3804-4d44-9fd7-e65389e84f79-lock-0000000242, _c_86ddffbe-72f3-493a-b1f2-de4a38b4e0e8-lock-0000000224, _c_184038f3-cbdf-4ca8-af79-f76fbaccad22-lock-0000000222, _c_841c1fbe-ce3f-4b57-8757-5a10388d4d56-lock-0000000250, _c_5c186d97-c0b6-4015-a8a3-fe6f035201cb-lock-0000000232, _c_60a785db-4d14-4ed1-a848-1b614d4bd6d6-lock-0000000254, _c_75817830-4bad-4616-bd2c-91f9ffe15495-lock-0000000259, _c_77b9f782-7b8a-4c2d-86aa-00582cfcbec7-lock-0000000247,

加入商品数只有20个,如果同一时刻有1000人抢,那/iphone下会有1000个临时顺序节点,排在20后面的肯定是无效的。那代码可以优化一下,生成节点前,先获取/iphone下所有的子节点,如果数量小于20,那生成临时顺序节点去抢锁,如果大于20了,那就当做失败了。这样一来,如果集群部署3台服务器,那极端情况也就是生成60个临时顺序节点而已。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值