ZK实践篇 - 基于zookeeper实现分布式锁

什么是分布式锁?

为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。

为什么需要锁

  1. 多任务环境中才需要;

  2. 任务都需要对同一共享资源进行写操作;

  3. 对资源的访问是互斥的;

Tips:任务通过竞争获取锁才能对该资源进行操作(①竞争锁);当有一个任务在对资源进行更新时(②占有锁),其他任务都不可以对这个资源进行操作(③任务阻塞),直到该任务完成更新(④释放锁);

JVM锁解决不了分布式环境多任务对共享资源竞争的协同操作问题,因此就需要分布式锁。

分布式锁方案比较

分布式锁比较

设计实现锁

锁E-R图

1、定义锁的接口Lock

2、在AbstractLock模板锁里面实现getLock方法,实现通用的逻辑。

3、不能确实的步骤,作为虚拟方法,甩锅给子类实现。

4、子类只需要聚焦自己的小步骤逻辑,实现tryLock,waitLock,unLock方法。

ZK基于同名节点的分布式锁

此种实现方式会出现羊群效应。

ZK基于临时序列节点实现分布式锁

这种实现方式能防止羊群效应

代码

代码地址:https://github.com/SunSmileAZY/zk-lock.git

1、创建lock接口类

/**
 *
 * @author pine
 * @date 20200221
 */
public interface Lock {
    /**
     * 获取到锁的资源
     */
    void getLock();

    /**
     * 释放锁
     */
    void unLock();
}

2、创建抽象类

package cn.pine.zk;

/**
 *
 * @author pine
 * @date 20200221
 */
public abstract class AbstractLock  implements  Lock{

    public void getLock() {

        //任务通过竞争获取锁才能对该资源进行操作(①竞争锁);
        // 当有一个任务在对资源进行更新时(②占有锁),
        // 其他任务都不可以对这个资源进行操作(③任务阻塞),
        // 直到该任务完成更新(④释放锁)
        //尝试获得锁资源

        //①竞争锁
        if (tryLock()) {
            System.out.println("##获取lock锁的资源####");
        } else {

            //③任务阻塞
            waitLock();

            // 重新获取锁资源
            getLock();
        }
    }

    // ②占有锁
    public abstract boolean tryLock();

    // 等待
    public abstract void waitLock();
}

3、继承ZookeeperAbstractLock 实现锁

package cn.pine.zk;

import org.I0Itec.zkclient.ZkClient;

/**
 * 提取公共代码
 * @author pine
 * @date 20200221
 */
public abstract class ZookeeperAbstractLock extends AbstractLock {
    // zk连接地址
    private static final String CONNECTSTRING = "127.0.0.1:2181";
    // 创建zk连接
    protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
    protected static final String PATH = "/lock";


    protected static final String PATH2 = "/lock2";



}
package cn.pine.zk;

import org.I0Itec.zkclient.IZkDataListener;
import org.springframework.stereotype.Service;

import java.util.concurrent.CountDownLatch;
/**
 *
 * @author pine
 * @date 20200221
 */
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
    private CountDownLatch countDownLatch = null;

    @Override
    //尝试获得锁
    public  boolean tryLock() {
        try {
            zkClient.createEphemeral(PATH);
            return true;
        } catch (Exception e) {
            //如果创建失败报出异常
//			e.printStackTrace();
            return false;
        }

    }

    @Override
    public 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 void unLock() {
        //释放锁
        if (zkClient != null) {
            zkClient.delete(PATH);
            zkClient.close();
            System.out.println("释放锁资源...");
        }
    }
}
package cn.pine.zk;

import cn.pine.simple.OrderNumGenerator;

import java.util.concurrent.CountDownLatch;

/**
 *
 * @author pine
 * @date 20200221
 */
public class OrderService implements Runnable {
    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

    //发令枪,模拟50个并发
    private static CountDownLatch countDownLatch = new CountDownLatch(50);

//    private Lock lock = new ZookeeperDistrbuteLock();

    private Lock lock = new ZookeeperDistrbuteLock2();

    public void run() {
        getNumber();
    }
    public void getNumber() {
        try {
            countDownLatch.await();
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unLock();
        }
    }
    public static void main(String[] args){

        System.out.println("####生成唯一订单号###");

        for (int i = 0; i < 50; i++) {
            new Thread(new OrderService()).start();
            countDownLatch.countDown();
        }

    }
}
package cn.pine.zk;

import cn.pine.simple.OrderNumGenerator;

import java.util.concurrent.CountDownLatch;

/**
 * 测试类
 * @author pine
 * @date 20200221
 */
public class OrderService implements Runnable {
    private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();

    //发令枪,模拟50个并发
    private static CountDownLatch countDownLatch = new CountDownLatch(50);

//    private Lock lock = new ZookeeperkLock();

    private Lock lock = new ZookeeperDistrbuteLock2();

    public void run() {
        getNumber();
    }
    public void getNumber() {
        try {
            countDownLatch.await();
            lock.getLock();
            String number = orderNumGenerator.getNumber();
            System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unLock();
        }
    }
    public static void main(String[] args){

        System.out.println("####生成唯一订单号###");

        for (int i = 0; i < 50; i++) {
            new Thread(new OrderService()).start();
            countDownLatch.countDown();
        }

    }
}

总结

分布式锁建议用zk的方式做,注意羊群效应。要用第二种方式做。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值