zookeeper使用curator作为客户端配合springboot实现分布式锁案例

一、说在之前

zookeeper集群使用zap协议来保证集群之间数据的一致性,频繁的进行写、监听操作将对zk集群产生较大压力,所以不推荐大家使用。
推荐使用redis作为分布式锁:
redisson是redis的一个组件,是redis提供的一种分布锁的实现,它实现的jdk lock锁的接口,还有就是其底层是lua脚本,将锁的获取与锁的设置超时时间实现原子性操作,避免获取到锁而服务器宕机超时时间设置失败的情况。
参考我之前的文章 redis使用redisson作为客户端配合springboot作为分布式锁的案例

但是作为一个热爱学习的人,依然选择把我总结的zookeeper实现分布式锁的流程分享给大家

二、curator实现分布式锁的原理

2.1 zookeeper有关的概念

  • 有序节点:顾名思义就是有顺序的节点。zk会在生成节点时根据现有的节点数量添加整数序号。比如已经存在节点/lock/node-0000000000,下一个节点就是/lock/node-0000000001。

  • 临时节点:临时节点只在zk会话期间存在,会话结束或超时时会被zk自动删除。

  • 事件监听:通过zk的事件监听机制可以让客户端收到节点状态变化。主要的事件类型有节点数据变化、节点的删除和创建。

  • PERSISTENT:持久化的节点。一旦创建后,即使客户端与zk断开了连接,该节点依然存在。

  • PERSISTENT_SEQUENTIAL:持久化顺序编号节点。比PERSISTENT节点多了节点自动按照顺序编号。

  • EPHEMERAL:临时节点。当客户端与zk断开连接之后,该节点就被删除。

  • EPHEMERAL_SEQUENTIAL:临时顺序编号节点。比EPHEMERAL节点多了节点自动按照顺序编号。(分布式锁实现使用该节点类型)

2.2 实现步骤

算法流程如下:

1、每个客户端创建临时有序节点
2、客户端获取节点列表,判断自己是否列表中的第一个节点,如果是就获得锁,如果不是就监听自己前面的节点,等待前面节点被删除。
3、如果获取锁就进行正常的业务流程,执行完释放锁。

上述步骤2中,有人可能担心如果节点发现自己不是序列最小的节点,准备添加监听器,但是这个时候前面节点正好被删除,这时候添加监听器是永远不起作用的,其实zk的API可以保证读取和添加监听器是一个原子操作。

在这里插入图片描述

三、以上的原理,百度都是,下面我来说代码操作

3.1 架构

  • springboot2.x项目
  • zookeeper 客户端 curator 4.0
  • zookeeper 服务端 3.6.0

3.2 先放出依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>
    </dependencies>

3.3 代码

  • Controller
@RestController
public class NoDistributeController {

    /**
     * 无分布式锁
     */
    @Autowired
    NoDistributeService redisDistributeService;

    /**
     * 查询剩余订单结果接口
     * @return
     */
    @GetMapping("/query")
    public String query() {
        return redisDistributeService.queryMap();
    }

    /**
     * 下单接口
     * @return
     */
    @GetMapping("/order")
    public String order() {
        redisDistributeService.order();
        return redisDistributeService.queryMap();
    }
}
  • Service
@Service
public class NoDistributeService {

    /**
     * 模拟商品信息表
     */
    private static Map<String,Integer> products;

    /**
     * 模拟库存
     */
    private static Map<String,Integer> stock;

    /**
     * 订单
     */
    private static Map<String,String> orders;

    static {
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        //模拟订单表数据 订单编号 苹果 库存 100000
        products.put("苹果",100000);
        //模拟库存表数据 订单编号 苹果 库存100000
        stock.put("苹果",100000);
    }

    /**
     * 模拟查询秒杀成功返回的信息
     * @return  返回拼接的秒杀商品结果字符串
     */
    public String queryMap() {
        String pid = "苹果";
        return "秒杀商品限量:" +  products.get(pid) + "份,还剩:"+stock.get(pid) +"份,成功下单:"+orders.size() + "人";
    }

    /**
     * 下单
     */
    public void order() {
        String pid = "苹果";
        //从库存表中获取库存余量
        int stockNum = stock.get(pid);
        //如果库存为0 则输出库存不足
        if(stockNum == 0) {
            System.out.println("商品库存不足");
        }else{ //如果有库存
            //往订单表中插入数据 生成UUID作为用户ID pid
            orders.put(UUID.randomUUID().toString(),pid);
            //线程休眠 模拟其他操作
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //减库存操作
            stock.put(pid,stockNum-1);
        }
    }
}

3.3 Jmeter测试结果

在这里插入图片描述在这里插入图片描述
2秒,下单1000次
查看结果:http://localhost:8080/query

在这里插入图片描述
可以看出, 下单了994个人,而订单只下了29份,严重出现问题。

四、使用zookeeper解锁

4.1 伪代码

//需要使用分布式锁的地方,代码如下:
String lockOn= "锁的名称";
//curatorFramework cutator客户端
InterProcessMutex mutex = new InterProcessMutex(curatorFramework,lockOn);
//入参传入超时时间、单位,抢夺时,如果出现堵塞,会在超过该时间后,返回false
boolean locked =mutex.acquire(2,TimeUnit.SECONDS);

//finally部分  mutex.release();

实际代码

修改service

@Service
public class NoDistributeService {

    /**
     * 模拟商品信息表
     */
    private static Map<String,Integer> products;

    /**
     * 模拟库存
     */
    private static Map<String,Integer> stock;

    /**
     * 订单
     */
    private static Map<String,String> orders;

    static {
        products = new HashMap<>();
        stock = new HashMap<>();
        orders = new HashMap<>();
        //模拟订单表数据 订单编号 苹果 库存 100000
        products.put("苹果",100000);
        //模拟库存表数据 订单编号 苹果 库存100000
        stock.put("苹果",100000);
    }

    /**
     * 模拟查询秒杀成功返回的信息
     * @return  返回拼接的秒杀商品结果字符串
     */
    public String queryMap() {
        String pid = "苹果";
        return "秒杀商品限量:" +  products.get(pid) + "份,还剩:"+stock.get(pid) +"份,成功下单:"+orders.size() + "人";
    }

    /**
     * 下单
     */
    public void order() {
        //创建分布式锁, 锁空间的根节点路径为/curator/lock
        InterProcessMutex mutex  = new InterProcessMutex(client,"/curator/lock");


        try {
            boolean acquire = mutex.acquire(0, TimeUnit.SECONDS);

            if(!acquire){
                logger.info("锁被占用");
                return;
            }else {
                        String pid = "苹果";
                        //从库存表中获取库存余量
                        int stockNum = stock.get(pid);
                        //如果库存为0 则输出库存不足
                        if(stockNum == 0) {
                            System.out.println("商品库存不足");
                        }else{ //如果有库存
                            //往订单表中插入数据 生成UUID作为用户ID pid
                            orders.put(UUID.randomUUID().toString(),pid);
                            //线程休眠 模拟其他操作
                            try {
                                TimeUnit.MILLISECONDS.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //减库存操作
                            stock.put(pid,stockNum-1);
                        }
            }

        } catch (Exception e) {
            logger.info("锁出现异常");
        } finally {
            try {
                mutex.release();
            } catch (Exception e) {
                logger.info("释放锁出现异常");
            }
        }
    }
}

以上完成了zookeeper分布式锁的所有步骤

结尾

如果有疑问,可评论留言,及时解答,谢谢

猜你喜欢

转载自blog.csdn.net/qq_34168515/article/details/105669774