分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式,在分布式系统中,如果不同的应用之间共享一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
Redis 分布式锁实现原理
实现原理可参考 http://www.redis.cn/topics/distlock.html。
Redis 是单线程的,所以 Redis 命令具有原子性,Redis 提供了以下几个命令
因为锁的实现中拥有比较、加锁等一系列操作,为保证原子性,需要引入 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() { @Override public 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 相关资料