zookeeper(二)使用zookeeper实现分布式锁

一、什么是分布式锁,它为什么会产生

现在我们的项目大多都是分布式部署的,分布式部署一个显而易见的好处就是可以减轻服务器的压力,但也带来一系列的问题,比如:分布式session一致性问题、分布式配置中心、分布式任务调度平台、分布式日志平台、分布式全局ID的生成、分布式事务、分布式限流、分布式锁。

由于项目是分布式部署的,这样部署的每一份都会有自己的jvm运行项目,但是它们需要共享一些实例变量,但是在高并发的情况下,可能有多个jvm同时操作实例变量,这样就会造成数据不一致、值不同步的问题,这个时候我们需要保证只有一个jvm在一个时间操作实例变量,这样我们就引用了分布式锁来限制只有一个jvm能执行任务,其他的先排队。

synchronized和lock只能解决单个jvm中多个线程的线程安全问题

二、分布式锁的常见解决方案,它们的优缺点是

(1) 使用数据库,我们知道数据库本身有表锁、行锁,但是使用数据库作分布式锁的效率非常低下

(2)使用redis,使用redis本身自带的setNx命令,这是一个互斥的命令,如果setNx需要创建的key在redis中已经有了,那么其他请求不能成功返回异常。  好处:我们知道redis的写的效率是81000,读的效率是110000,因此它的效率还是可以的。 缺点:创建分布式锁容易造成死锁,不好控制过期时间,而且有一个bug就是执行任务删除锁,需要先判断一下这个锁是否存在,返回正确的情况下,这个时候恰好过期了,而新锁又由其他线程创建,这个时候去删除就会删除掉别的线程的锁,需要使用redis+lua脚本解决这个问题,一旦发现自己的锁,立即删除。

(3)RedisSon 是专门用来做分布式锁的

(4)zookeeper做分布式锁,比较简单,不同意造成死锁

三、zookeeper实现分布式锁的原理

使用zookeeper的创建的是临时节点的原理,为什么要创建临时节点是因为zookeeper的连接session close以后。临时节点自动消失。

(1)多个jvm中的项目中的线程去zookeeper中创建同一个节点,这个节点是临时节点

(2)创建临时节点的结果可能是一个成功,其他失败,这样成功创建节点的相当于拿到了锁,可以执行任务,没有创建成功相当于没有拿到锁,那么进行等待

(3)拿到锁的执行完任务之后,那么就可以释放锁(close掉zookeeper的session,强制关闭会造成短暂死锁),通过watcher通知给其他的jvm,重新去创建节点,竞争锁。

四、代码演示

zk实现分布式锁

/**
 * Created by 辉 on 2020/6/30.
 */
public class ZookeeperLock  {
    // zk连接地址
    private static final String CONNECTSTRING = "192.168.196.175:2181";
    // 创建zk连接
    protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
    //防止死锁的发生,在创建zk连接的时候可以传入session的失效
    //时间,这样就能在zk断开的时候自动去除这个临时节点
    protected static final String PATH = "/lock";

    public void getLock() {
        if (tryLock()) {
            System.out.println("##获取lock锁的资源####");
        } else {
            // 等待
            waitLock();
            // 重新获取锁资源
            getLock();
        }
    }

    public void unLock() {
        if (zkClient != null) {
            zkClient.close();
            System.out.println("释放锁资源...");
        }
    }

    private CountDownLatch countDownLatch = null;
    boolean tryLock() {
        try {
            zkClient.createEphemeral(PATH);
            return true;  //创建临时节点成功,返回true
        } catch (Exception e) {
//       e.printStackTrace();
            return false; //创建节点失败,那么就会出异常这里就会捕获,这样就会到这里,返回false
        }
    }

     void waitLock() {
        IZkDataListener izkDataListener = new IZkDataListener() {
            public void handleDataDeleted(String path) throws Exception {
                // 唤醒被等待的线程
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
            public void handleDataChange(String path, Object data) throws Exception {

            }
        };
        // 注册事件
        zkClient.subscribeDataChanges(PATH, izkDataListener);
        if (zkClient.exists(PATH)) {
            countDownLatch = new CountDownLatch(1);
            try {
                countDownLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 删除监听
        zkClient.unsubscribeDataChanges(PATH, izkDataListener);
    }

}

订单类

//生成订单类
public class OrderNumGenerator {
    //全局订单id
    public static int count = 0;

    public   String getNumber() {
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
        SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        return simpt.format(new Date()) + "-" + ++count;
    }
}

生成全局订单ID

//使用多线程模拟生成订单号
public class OrderService implements Runnable {
    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
     ZookeeperLock zkLock=new ZookeeperLock();

   public void run() {
      getNumber();
   }

   public   void   getNumber() {
      try {
         zkLock.getLock();
         String number = orderNumGenerator.getNumber();
         System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
      } catch (Exception e) {
         e.printStackTrace();
      } finally {
         zkLock.unLock();
      }
   }

   public static void main(String[] args) {
      System.out.println("####生成唯一订单号###");
      for (int i = 0; i < 100; i++) {
         new Thread(new OrderService()).start();
      }

   }

效果:

zookeeper(二)使用zookeeper实现分布式锁