以商品超卖为例讲解Redis分布式锁

本文通过商品超卖场景,详细讲解了使用Redis实现分布式锁的两种方法,包括加锁、释放锁的原理及操作,以及通过实际案例展示了分布式锁在并发控制中的效果,强调了加锁的必要性和防止死锁的策略。
摘要由CSDN通过智能技术生成

本案例主要讲解Redis实现分布式锁的两种实现方式:Jedis实现、Redisson实现。网上关于这方面讲解太多了,小编自认为文笔没他们好,还是用示例代码说明。

一、jedis 实现

该方案只考虑Redis单机部署的场景

1.1 加锁

1.1.1 原理

jedis.set(String key, String value, String nxxx, String expx, int time)
  1. key: 使用key来当锁,因为key是唯一的;
  2. value: 我传的是唯一值(UUID),很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因是分布式锁要满足解铃还须系铃人:通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候要验证value值,不能误解锁;
  3. nxxx: 这个参数我填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
  4. expx: 这个参数我传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定;
  5. time: 与第四个参数相呼应,代表key的过期时间。

1.1.2 小结

  • set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性;
  • 其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁;
  • 最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。

1.2 释放锁

释放锁时需要验证value值,也就是说我们在获取锁的时候需要设置一个value,不能直接用del key这种粗暴的方式,因为直接del key任何客户端都可以进行解锁了,所以解锁时,我们需要判断锁是否是自己的(基于value值来判断)

  1. 首先,写了一个简单Lua脚本代码,作用是:获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁);
  2. 然后,将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKeyARGV[1]赋值为requestIdeval()方法是将Lua代码交给Redis服务端执行。

1.3 案例(家庭多人领取奖励的场景)

这里放出的是关键代码,详细可运行的代码可至文末地址下载示例代码。

1.3.1 准备

该案例模拟家庭内多人通过领取一个奖励,但是只能有一个人能领取成功,不能重复领取(之前做过奖励模块的需求)

  • family_reward_record
CREATE TABLE `family_reward_record` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `family_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '商品名称',
  `reward_type` int(10) NOT NULL DEFAULT '1' COMMENT '商品库存数量',
  `state` int(1) NOT NULL DEFAULT '0' COMMENT '商品状态',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入库时间',
  `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=270 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='家庭领取奖励表(家庭内多人只能有一个人能领取成功,不能重复领取)';
  • application.yml
spring:
  datasource:
    url: jdbc:mysql://47.98.178.84:3306/dev
    username: dev
    password: password
    driver-class-name: com.mysql.jdbc.Driver
  redis:
    host: 47.98.178.84
    port: 6379
    password: password
    timeout: 2000
# mybatis
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: cn.van.mybatis.demo.entity

1.3.2 核心实现

  • Jedis 单机配置类 - RedisConfig.java
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值