相关知识
分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式,在分布式系统中,如果不同的应用之间共享一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
- 互斥性。在分布式环境下,同一时间只有一个客户端能持有锁。
- 具备锁失效机制,防止死锁。例如锁的持有者在持有锁期间崩溃而没有主动解锁,锁需要在规定时间后自动失效,以保证后续可用。
- 具备可重入性,防止死锁。
- 解铃还须系铃人。释放锁与加锁应该为相同客户端,不能把别人加的锁给解了。
Redis 分布式锁实现原理
实现原理可参考 http://www.redis.cn/topics/distlock.html。
Redis 是单线程的,所以 Redis 命令具有原子性,Redis 提供了以下几个命令
- setnx,意思是待创建的键如果已存在,则创建失败,否则创建成功,实现互斥性。
- expire,给键加上过期时间,实现自动失效机制
- set key value [EX|PX] [NX],可以将上面两个命令的动作,在这一个命令实现,其中,EX|PX 是过期时间,EX 使用秒,PX 使用毫秒,NX 表示 setnx 的意思
因为锁的实现中拥有比较、加锁等一系列操作,为保证原子性,需要引入 Lua 脚本
加锁流程:
释放锁流程
目标
整合 Redis 实现分布式锁
操作步骤
添加依赖
依赖以及配置见上章https://blog.csdn.net/qq_34479912/article/details/105719636
编码
锁是针对某个资源的状态,保证其访问的互斥性,在实际使用当中,这个状态一般是一个字符串。使用 Redis 实现锁,主要是将状态放到 Redis 当中,利用其原子性,当其他线程访问时,如果 Redis 中已经存在这个状态,就不允许之后的一些操作。spring boot使用Redis的操作主要是通过RedisTemplate(或StringRedisTemplate )来实现。
1.1 将锁状态放入 Redis:
redisTemplate.opsForValue().setIfAbsent("lockkey", "value"); // setIfAbsent如果键不存在则新增,存在则不改变已经有的值。
1.2 设置锁的过期时间
redisTemplate.expire("lockkey", 30000, TimeUnit.MILLISECONDS);
1.3 删除/解锁
redisTemplate.delete("lockkey");
这么就是简单实现,但是1.1和1.2这么做,这两步违背了原子性,也就是一旦锁被创建,而没有设置过期时间,则锁会一直存在。
1.4 获取锁
redisTemplate.opsForValue().get("lockkey");
1.5 解决方案
spring data的 RedisTemplate 当中并没有这样的方法。但是在jedis当中是有这种原子操作的方法的,需要通过 RedisTemplate 的 execute 方法获取到jedis里操作命令的对象.
String result = template.execute(new RedisCallback<String>() {@Overridepublic String doInRedis(RedisConnection connection) throws DataAccessException {JedisCommands commands = (JedisCommands) connection.getNativeConnection();return commands.set(key, "锁定的资源", "NX", "PX", 3000);}});
注意: Redis 从2.6.12版本开始 set 命令支持 NX 、 PX 这些参数来达到 setnx 、 setex 、 psetex 命令的效果,文档参见: SET — Redis 命令参考
NX: 表示只有当锁定资源不存在的时候才能 SET 成功。利用 Redis 的原子性,保证了只有第一个请求的线程才能获得锁,而之后的所有线程在锁定资源被释放之前都不能获得锁。
锁的进阶
模拟一个比较常见的秒杀场景,这时候就需要用到锁。
创建RedisLockHelper
这个是Redis加锁和解锁的工具类,里面使用的主要是两个命令,SETNX和GETSET。
SETNX命令 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做
GETSET命令 先查询出原来的值,值不存在就返回nil。然后再设置值 对应的Java方法在代码中提示了。 注意一点的是,Redis是单线程的!所以在执行GETSET和SETNX不会存在并发的情况。
创建Controller模拟秒杀场景
源码地址
本章源码 :https://github.com/caiyuanzi-song/boot.git
参考
https://www.cnblogs.com/toutou/archive/2019/02/11/redis_lock.html
扩展
Redis 相关资料
- spring-data-redis文档: https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/#new-in-2.0.0
- Redis 文档: https://redis.io/documentation
- Redis 中文文档: http://www.redis.cn/commands.html