本案例主要讲解
Redis
实现分布式锁的两种实现方式:Jedis
实现、Redisson
实现。网上关于这方面讲解太多了,小编自认为文笔没他们好,还是用示例代码说明。
一、jedis
实现
该方案只考虑
Redis
单机部署的场景
1.1 加锁
1.1.1 原理
jedis.set(String key, String value, String nxxx, String expx, int time)
key
: 使用key
来当锁,因为key
是唯一的;value
: 我传的是唯一值(UUID
),很多童鞋可能不明白,有key
作为锁不就够了吗,为什么还要用到value
?原因是分布式锁要满足解铃还须系铃人:通过给value
赋值为requestId
,我们就知道这把锁是哪个请求加的了,在解锁的时候要验证value
值,不能误解锁;nxxx
: 这个参数我填的是NX
,意思是SET IF NOT EXIST
,即当key
不存在时,我们进行set
操作;若key
已经存在,则不做任何操作;expx
: 这个参数我传的是PX
,意思是我们要给这个key
加一个过期的设置,具体时间由第五个参数决定;time
: 与第四个参数相呼应,代表key
的过期时间。
1.1.2 小结
set()
加入了NX
参数,可以保证如果已有key
存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性;- 其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即
key
被删除),不会发生死锁; - 最后,因为我们将
value
赋值为requestId
,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。
1.2 释放锁
释放锁时需要验证
value
值,也就是说我们在获取锁的时候需要设置一个value
,不能直接用del key
这种粗暴的方式,因为直接del key
任何客户端都可以进行解锁了,所以解锁时,我们需要判断锁是否是自己的(基于value
值来判断)
- 首先,写了一个简单
Lua
脚本代码,作用是:获取锁对应的value
值,检查是否与requestId
相等,如果相等则删除锁(解锁); - 然后,将
Lua
代码传到jedis.eval()
方法里,并使参数KEYS[1]
赋值为lockKey
,ARGV[1]
赋值为requestId
。eval()
方法是将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.