zookeeper实现分布式锁记录

关于分布式锁的背景,和实现方法,我就不做过多赘述了,这里大概总结一下分布式锁使用场景:

1.比如说在分布式环境下, 生成订单号,不过现在生成订单号已经可以用雪花算法来完成了。

2.秒杀的时候,一个商品只能让一个用户抢到。这个其实也可以用redis去解决

其实大概就是要保持原子性的逻辑,但是分布式的环境下无法单一加锁去解决了,这个时候就要用到分布式锁。

 

为什么要选择zookeeper?

单纯的因为实现起来比redis更简单,理解起来更方便,而且听别人说是更可靠,这个我太清楚了。性能虽然不及redis,但是也比数据库要高效了。

大致是思路是利用zookeeper的节点是唯一的这一特性,加上方便的事件通知这些已经写好的方法去实现分布式锁。

最开始说一下我引入的maven依赖吧。就一个很简单的zkclient即可。

	<dependencies>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
    </dependencies>

这里是采用的模板方法去完成代码逻辑的,因为不论是用redis还是什么方法去解决分布式锁,都是存在共同行为的,即加锁,等待,解锁。

关于模板方法设计模式,我觉得也不用去了解太多吧,等你觉得代码能优化的时候再去慢慢看也不急就是。

这里我们先定义一个接口,让模板抽象方法去实现它,我上面说到了的吧,共同行为,即加锁,等待,解锁,所以这个接口里就定义这些方法。

package com.zyx.zookeeper.interfaces;
/**
* @author 作者:zyx
* @version 创建时间:2019年10月23日 下午2:59:58
* 基于zookeeper实现分布式锁,模板方法接口类
*/
public interface MyLock {

	//获取锁
    public void  getLock();
    
    //等待
    public void waitLock();
    
    //释放锁
    public void unLock();
}

然后我们再来写实现这个接口的抽象类

package com.zyx.zookeeper.abstracts;

import com.zyx.zookeeper.interfaces.MyLock;

/**
* @author 作者:zyx
* @version 创建时间:2019年10月23日 下午3:01:41
*/
public abstract class ZookeeperAbstractLockTemplate implements MyLock{
    
     //获取锁
      public void getLock() { 
            //加锁
          if (tryLock()) {
            System.out.println("#####成功获取锁######");
        }else {
            //进行等待
            waitLock();
            
            //重新获取
            getLock();
        }
        
    }
      
      //创建失败 进行等待
      public void waitLock() {
    	  waitImplLock();
      }


      protected abstract boolean tryLock();
      
      protected abstract void waitImplLock();
      
      protected abstract void unImplock();
     
     
      //释放锁
      public void unLock() {
    	  unImplock();
          System.out.println("######释放锁完毕######");
        
    }
}

最后写一个真正的关于zookeeper的实现类去实现这个抽象方法

package com.zyx.zookeeper.template;

import java.util.concurrent.CountDownLatch;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import com.zyx.zookeeper.abstracts.ZookeeperAbstractLockTemplate;

public class ZookeeperLockTemplate extends ZookeeperAbstractLockTemplate {

	private static final String CONNECTION = "127.0.0.1:2181";
	private static final int TIMEOUT = 5000;
	private ZkClient zkClient = new ZkClient(CONNECTION, TIMEOUT);
	/**
	 * 共同创建的临时节点
	 */
	private String path = "/lockPath";

	private CountDownLatch countDownLatch = null;

	@Override
	protected void waitImplLock() {

		IZkDataListener iZkDataListener = new IZkDataListener() {

			public void handleDataDeleted(String dataPath) throws Exception {
				// TODO Auto-generated method stub

			}

			public void handleDataChange(String dataPath, Object data) throws Exception {
				// 3.如果当前节点被删除,那么重新获取锁,在父类里面已经实现了
				// 我们这里把计数器减1,那么程序阻塞就会终止,进入父类的获取锁方法
				if (countDownLatch != null) {
					countDownLatch.countDown();
				}

			}
		};

		// 1.使用事件监听,如果该节点被删除,说明又有了权限去获取该锁
		zkClient.subscribeDataChanges(path, iZkDataListener);

		// 2.使用countDownLatch等待
		if (countDownLatch == null) {
			countDownLatch = new CountDownLatch(1);
		}

		try {
			// 如果当前计数器不为0,那么就一直等待阻塞
			countDownLatch.await();
		} catch (InterruptedException e) {

		}
		// 4.删除时间监听,不然可能会重复注册事件报错
		zkClient.unsubscribeDataChanges(path, iZkDataListener);

	}

	@Override
	protected boolean tryLock() {
		try {
            //这里即在zookeeper中写入一个临时节点进行加锁操作
			zkClient.createEphemeral(path);
			return true;
		} catch (Exception e) {
			// 已经存在该节点了
			return false;
		}

	}

	@Override
	protected void unImplock() {
		if (zkClient != null) {
			zkClient.close();
		}
	}

}

zookeeper实现分布式锁就写完了。是不是很简单和很好理解。。。。加锁即在zookeeper中写入一个临时节点,因为临时节点名字不允许重复,所以再一次写入的时候会报错,实现加锁过程。然后解锁也特别简单吧,即这个线程所完成的逻辑完成之后,会自动删除该临时节点,然后监听该临时节点,如果被删除了,则说明解锁了,我们也不用再等待下去,再去获取锁。等待的过程是用CountDownLatch这个类去实现的,也很好理解。

 

最后的最后,我们写一个简单的测试类,来完成最终的效果展示

package com.zyx.zookeeper;
import java.text.SimpleDateFormat;
import java.util.Date;

//生成订单号 时间戳
public class OrderNumGenerator {
  //区分不同的订单号
    private static int count = 0;
    public String getNumber(){
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            
        }
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");  
        return simpt.format(new Date()) + "-" + ++count;  //时间戳后面加了 count

    }
    
}
package com.zyx.zookeeper;

import com.zyx.zookeeper.interfaces.MyLock;
import com.zyx.zookeeper.template.ZookeeperLockTemplate;

/**
* @author 作者:zyx
* @version 创建时间:2019年10月23日 下午3:35:18
*/
public class App implements Runnable {

    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
    private MyLock lock = new ZookeeperLockTemplate();

    public void run() {
        getNumber();
    }

    public void getNumber() {
        try {
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",number" + number);

        } catch (Exception e) {

        } finally {
            lock.unLock();
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) { // 开启100个线程
            //模拟分布式锁的场景
            new Thread(new App()).start();
        }
    }

}

最开始运行的时候稍微等一下,我这边写了个sleep的。

 

效果图如上,成功的实现了分布式锁,生成的订单号都不重复。 然后使用起来也很简单,即try/catch/finally,加锁解锁即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值