当前位置: 代码迷 >> 综合 >> SpringBoot2.0实战(14)整合Redis之实现分布式锁
  详细解决方案

SpringBoot2.0实战(14)整合Redis之实现分布式锁

热度:65   发布时间:2023-09-29 02:57:26.0

相关知识

分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式,在分布式系统中,如果不同的应用之间共享一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

  • 互斥性。在分布式环境下,同一时间只有一个客户端能持有锁。
  • 具备锁失效机制,防止死锁。例如锁的持有者在持有锁期间崩溃而没有主动解锁,锁需要在规定时间后自动失效,以保证后续可用。
  • 具备可重入性,防止死锁。
  • 解铃还须系铃人。释放锁与加锁应该为相同客户端,不能把别人加的锁给解了。

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 脚本

加锁流程:

SpringBoot2.0实战(14)整合Redis之实现分布式锁

 

释放锁流程

SpringBoot2.0实战(14)整合Redis之实现分布式锁

 

目标

整合 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里操作命令的对象.

SpringBoot2.0实战(14)整合Redis之实现分布式锁

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);}});

SpringBoot2.0实战(14)整合Redis之实现分布式锁

注意: Redis 从2.6.12版本开始 set 命令支持 NX 、 PX 这些参数来达到 setnx 、 setex 、 psetex 命令的效果,文档参见: SET — Redis 命令参考

NX: 表示只有当锁定资源不存在的时候才能 SET 成功。利用 Redis 的原子性,保证了只有第一个请求的线程才能获得锁,而之后的所有线程在锁定资源被释放之前都不能获得锁。

锁的进阶

模拟一个比较常见的秒杀场景,这时候就需要用到锁。

创建RedisLockHelper

SpringBoot2.0实战(14)整合Redis之实现分布式锁

这个是Redis加锁和解锁的工具类,里面使用的主要是两个命令,SETNX和GETSET。

SETNX命令 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做

GETSET命令 先查询出原来的值,值不存在就返回nil。然后再设置值 对应的Java方法在代码中提示了。 注意一点的是,Redis是单线程的!所以在执行GETSET和SETNX不会存在并发的情况。

创建Controller模拟秒杀场景

SpringBoot2.0实战(14)整合Redis之实现分布式锁

 

源码地址

本章源码 :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