Redisson 分布式锁

. 开头

在单体应用中,我们可以用Java的synchronizedlock来使用锁,但在微服务的场景下,一个应用会部署多个实例,就需要保证多个实例的多个线程同时只能有一个线程来操作资源,那就需要分布式锁,

Redisson分布式锁的基本原理是通过Redis的setnx命令实现的。当一个进程需要获取锁时,通过调用Redis的setnx命令,在Redis中创建一个key表示锁的名称,如果成功地创建了这个key,则表示获取锁成功,可以执行相应的业务逻辑。如果创建key失败,则表示锁已经被其他进程获取了,当前进程需要等待直到获取到锁为止。当执行完业务逻辑后,需要释放锁,即通过Redisdel命令删除锁的key,这样其他进程就可以获取到锁了。

举个通俗的栗子:你上厕所的时候推一下门看看里面有没有人(尝试获取锁),里面有人你需要在门口等着(等待锁),当他上完后开门了(释放锁),你进去的时候把门关上了(上锁),你执行完脱裤子、拉屎、提裤子等操作后把门打开了(释放锁)。

本文我们将通过抢购茅台的例子来进行演示。

抢购茅台分为三个步骤:

  1. 判断库存是否充足

  2. 创建新订单

  3. 扣减库存。

2. 安装运行Redis

Redisson是基于Redis的,我们需要先下载运行Redis

Redis是内存数据库,Redisson = Redis + son(儿子)。

2.1 Windows

  • github下载地址:https://github.com/redis-windows/redis-windows/releases

  • github下载很慢,网盘下载(推荐):「Redis-7.0.8-Windows-x64.zip」来自UC网盘分享https://drive.uc.cn/s/4087d341fd084

启动Redis

# 启动服务
redis-server.exe redis.conf

# 老版本的配置文件名称
redis-server.exe redis.windows.conf
  • 需要指定redis.conf配置文件,可以在redis.conf文件中设置端口、密码等。

进入Redis命令行界面

# redis命令行界面
redis-cli.exe

# 设置键值对
set mykey hello
# 获取值
get mykey

2.2 Linux

以CentOS为例,通过yum命令安装。

# 安装
sudo yum install redis

# 启动
sudo systemctl start redis

# 进入redis命令行界面
redis-cli
# 设置键值对
set mykey hello
# 获取值
get mykey

3. 业务SQL脚本

执行如下SQL脚本

  • 生成商品表product

  • 生成商品订单表product_order

  • 商品表插入一条记录,贵州茅台总数量100瓶

CREATE TABLE `product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '商品名称',
  `number` int(11) NOT NULL DEFAULT 0 COMMENT '商品数量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `product_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `product_id` bigint(20) DEFAULT NULL COMMENT '商品id',
  `number` int(11) DEFAULT NULL COMMENT '数量',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `product` VALUES (1, '贵州茅台', 100);

4. SpringBoot 集成 Redisson

4.1 pom.xml

添加redisson-spring-boot-starter包依赖

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.8</version>
</dependency>

4.2 application.properties

redis相关配置

# redis 相关
spring.redis.host=localhost
spring.redis.port=6379

4.3 buy()

购买商品的方法

  • 判断库存数量是否充足

  • 创建订单

  • 扣减库存数量

@Resource
private RedissonClient redissonClient;

@Override
public Boolean buy(Long productId, Integer number) {
    RLock lock = redissonClient.getLock("lock_key_" + productId);

    try {
        boolean lockRes = lock.tryLock(5, TimeUnit.SECONDS);
        if (!lockRes) {
            throw new RuntimeException("获取锁失败~");
        }

        Product product = productService.getById(productId);
        log.info("库存数量:{}", product.getNumber());
        // ①判断库存是否充足
        if (product.getNumber() < number) {
            throw new RuntimeException("库存不足");
        }

        // ②创建订单
        ProductOrder order = new ProductOrder();
        order.setProductId(productId);
        order.setNumber(1);
        order.setCreateTime(LocalDateTime.now());
        save(order);
        log.info("创建订单:{}", order);

        // ③减库存
        product.setNumber(product.getNumber() - 1);
        productService.updateById(product);
        log.info("减库存:{}", product);
    } catch (InterruptedException e) {
        throw new RuntimeException("出现异常啦~");
    } finally {
        // 释放锁
        lock.unlock();
    }

    return true;
}
  • "lock_key_" + productId这里对商品id加锁,同时只能有一个请求线程操作这个商品,其它请求线程必须等待。

  • 对商品id加锁表明每一种商品都是独立加锁的,就相当于你上厕所的时候是把那个坑位的门给关了,而不是把卫生间的门给关了。

  • lock.tryLock(5, TimeUnit.SECONDS),这里设置加锁的时间为5秒,如果当前请求线程5秒内还没有执行完操作就自动释放锁,让下一个线程来进行操作。

4.4 加锁与不加锁分析

不加锁会出现什么问题?

假设库存还剩最后1瓶茅台,用户A和用户B同时发起购买1瓶茅台请求,用户A的请求线程判断库存充足,但还没有执行完创建订单和减库存操作(操作需要访问数据库,比较耗时)。此时用户B的请求线程判断库存数量为1,库存也充足,也进入了创建订单和减库存操作,最后创建了两个订单,库存减了两次。

我们对商品加锁后,当用户A的请求线程执行判断库存、创建订单、减库存的购买操作过程中,用户B的请求线程需要等待用户A把这一系列操作做完释放了锁之后才能去执行。

5. 测试

接口地址:http://localhost:8100/product/buy

用JMeter创建10个线程模拟多用户同时循环请求接口,当不加锁的情况下,如下是真实测试的结果:100瓶茅台产生了204个订单。

图片

当加锁后,100瓶茅台正常产生100个订单。

JMeter的使用可以看前面Sentinel 流量控制和接口防护那一篇文章。

6. 结语

这里通过简单的抢购茅台的例子来演示锁,Redisson分布式锁可以用于单体或者微服务,它是借助redis中间件来加锁,如上内容我们可以创建多个微服务实例,然后调用接口结果也是一样的。

记住:在限时抢购活动中,大量用户通过网络同时请求购买同一个商品,可能导致系统出现并发抢购的情况,从而会导致商品售罄或者超卖等问题。这种情况下一定要用锁,不管是单体应用还是微服务。


图片

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

missterzy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值