Java教程:如何深入理解Redis分布式锁

本文从超卖问题出发,通过测试演示了在并发情况下如何导致超卖现象。接着分析了synchronized锁的优化,但在多Tomcat实例下仍然存在问题。随后介绍了手动实现Redis分布式锁的步骤,包括使用SETNX命令以及需要注意的锁超时和唯一ID设置。最后,文章推荐了Redisson库,提供了简单易用的分布式锁实现,并探讨了其原理和锁续租机制。
摘要由CSDN通过智能技术生成

相信很多同学都听说过分布式锁,但也仅仅停留在概念的理解上,这篇文章会从分布式锁的应用场景讲起,从实现的角度上深度剖析redis如何实现分布式锁。

一、超卖问题

我们先来看超卖的概念:
当宝贝库存接近0时,如果多个买家同时付款购买此宝贝,或者店铺后台在架数量大于仓库实际数量,将会出现超卖现象。超卖现象本质上就是买到了比仓库中数量更多的宝贝。

本文主要解决超卖问题的第一种,同时多人购买宝贝时,造成超卖。

测试代码

那么超卖问题是如何产生的呢?我们准备一段代码进行测试:

 	@Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 第一种实现,进程内就存在线程安全问题
     * 可以只启动一个进程测试
     */
    @RequestMapping("/deduct_stock1")
    public void deductStock1(){
   

        String stock = stringRedisTemplate.opsForValue().get("stock");
        int stockNum = Integer.parseInt(stock);
        if(stockNum > 0){
   
            //设置库存减1
            int realStock = stockNum - 1;
            stringRedisTemplate.opsForValue().set("stock",realStock + "");
            System.out.println("设置库存" + realStock);
        }else{
   
            System.out.println("库存不足");
        }

    }

这段代码中,使用redis先获取库存数量(当然实际场景中不会只保存一个全局库存数,应该根据每一个商品单元(sku)保存一份库存数)。

 String stock = stringRedisTemplate.opsForValue().get("stock");
 int stockNum = Integer.parseInt(stock);

接下来,判断库存数是否大于0:

  • 如果大于0,将库存数减一,通过set命令,写回redis

    这里没有使用redis的decrement命令,因为此命令在redis单线程模型下是线程安全的,而为了可以模拟线程不安全的情况将其拆成三步操作

  //设置库存减1
  int realStock = stockNum - 1;
  stringRedisTemplate.opsForValue().set("stock",realStock + "");
  System.out.println("设置库存" + realStock);
  • 如果小于等于0,提示库存不足

JMeter测试

通过JMeter进行并发测试,看下会不会出现超卖的问题:

1.启动tomcat

这种情况下,只需要启动一个tomcat就会出现超卖。我们先启动一个tomcat在8080端口上。

在这里插入图片描述

2.下载JMeter

Apache JMeter是Apache组织开发的基于Java的压力测试工具。
从官网上下载即可:
https://jmeter.apache.org/download_jmeter.cgi
下载完之后解压,运行bin目录下的jmeter.bat,显示如下界面:

在这里插入图片描述

如果嫌字体太小,可以选择放大:

在这里插入图片描述

3.配置JMeter

在Test Plan上点击右键,创建线程组(Thread Group)

在这里插入图片描述

配置一下具体参数:

在这里插入图片描述

  • Number of Threads 同时并发线程数
  • Ramp-Up Period(in-seconds) 代表隔多长时间执行,0代表同时并发。假设线程数为100, 估计的点击率为每秒10次, 那么估计的理想ramp-up period 就是 100/10 = 10 秒
  • Loop Count 循环次数

这里给出500是为了直接测试并发500抢,看看能不能正好把500个货物抢完。

添加Http请求:

在这里插入图片描述

添加请求URL:

在这里插入图片描述

添加聚合结果,用来显示整体的运行情况:

在这里插入图片描述

到此为止JMeter的配置结束。

4.设置库存量

启动redis-server,使用redis-client连接:

在这里插入图片描述

把库存数设置为500。

5.开始测试

点击运行按钮,启动测试:

在这里插入图片描述

首先我们看到聚合报告里输出的结果:

在这里插入图片描述

错误率0%,样本数500,证明500个请求都已经执行,但是发现控制台输出如下:

在这里插入图片描述

很显然,一份商品都被卖了多次,这显然是不合理的。

原因分析

现在我们只启动了一个tomcat,在单jvm进程的情况下,tomcat会使用线程池接收请求:

在这里插入图片描述

而由于每个线程可能同时获取到库存量,所以库存量在两个线程中显示的都是500,然后两个线程就继续进行扣减库存操作,得出499写回redis中,在这个过程中,显然存在线程安全的问题。同一个商品被卖出了2份,超卖问题就出现了。

二、加锁优化

synchronized锁

要保证单jvm中线程安全,最简单直接的方式就是添加synchronized关键字,那么这样行不行呢,我们来做一个测试:

  /**
     * 第二种实现,使用synchronized加锁
     * 可以只启动一个进程测试
     */
    @RequestMapping("/deduct_stock2")
    public void deductStock2(){
   

        synchronized (this){
   
            String stock = stringRedisTemplate.opsForValue().get("stock");
            int stockNum = Integer.parseInt(stock);
            if(stockNum > 0){
   
                //设置库存减1
                int realStock = stockNum - 1;
                stringRedisTemplate.opsForValue().set("stock",realStock + "");
                System.out.println("设置库存" + realStock);
            }else{
   
                System.out.println("库存不足");
            }
        }

    }

在进行扣减库存前,先通过synchronized关键字,对资源加锁,这样就只有一个线程能进入到扣减库存的代码块中。来测试一下:

重置库存
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值