Bootstrap

Redis 基础 - 优惠券秒杀《分布式锁(使用Redisson)》

Redis基础 - 基本类型及常用命令
Redis基础 - Java客户端
Redis 基础 - 短信验证码登录
Redis 基础 - 用Redis查询商户信息
Redis 基础 - 优惠券秒杀《非集群》
Redis 基础 - 优惠券秒杀《分布式锁(初级)》

基于setnx实现的分布式锁存在的问题

1)不可重入
同一个线程无法多次获取同一把锁

2)不可重试
获取锁只尝试一次就返回false,没有重试机制

3)超时释放
锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患

4)主从一致性
暂时可以理解成读写分离模式,即有一个主节点和多个从节点,当执行写操作时,就去访问主节点,当执行读操作时,去访问从节点。当然,主节点需要把自己的数据同步给所有的从节点,保证主和从的数据一致,这样的话,可以在多个节点上完成读的操作,提高整个服务的并发能力和高可用性。
而且如果主节点宕机了,可以在从节点中选出一个当做新的主节点,这样整个集群的可用性就更强了。但是主从之间的数据同步是有延迟的,所以在极端情况下,可能会发生这样的情况,比如有个线程在主节点获取了锁,因为获取锁是set操作,是一个写操作,在主节点完成写操作之后,假如尚未同步给从节点的时候,因为存在延迟,突然间主节点宕机,此时我们就会选新的从作为主,而这个从节点上因为没有完成同步,所以她是没有这个锁的标示,即这时候其他线程可以趁虚而入,去拿到锁(新的set),这时候就等于是多个线程拿到了锁,所以可能在极端情况下出现安全问题。当然,她出现的概率较低,因为主从同步的延时极低,往往可以在毫秒级别(甚至更低)完成。
所以以上说的这四个问题,要么发生概率极低,要么就是不一定有这样的需求,在有些业务上需要,而在有些业务上不需要,所以这些个点都是功能上的拓展点,不是说必须得实现。那么不实现我们也能够用,其实大多数场景下,之前实现的锁已经够用了。比如主从一致性问题若用单节点,就不存在这个问题了。
但如果你对锁的要求很高,或你有这样的需求,必须想办法去解决这四个问题。而解决这些问题可就麻烦了。不可重入问题好说,但不可重试、超时释放、主从一致性等问题解决起来相对麻烦,整个实现起来也很繁琐。所以不推荐亲自去实现,而是去找找成熟的框架帮我们实现,那就是Redisson。

Redisson 分布式锁

Redisson 是一个在Redis基础上实现的一个分布式工具的集合。即分布式系统下用的各种各样的东西她都有,包括分布式锁,分布式锁只是她其中的一块儿功能。
官网地址:https://redisson.org
github地址:https://github.com/redisson/redisson

在企业环境下其实没有必要自己去实现锁,之前的内容只是有助于明白分布式锁的原理。所以推荐大家以后使用分布式锁时,直接使用这种开源框架就可以。(网友1:经典白学)

简单使用例子

1)引入依赖
pom.xml

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.13.6</version>
</dependency>

2)配置Redisson
src/main/java/com.asd/config/RedissonConfig.java

@Configuration
public class RedissonConfig {
   
	@Bean
	public RedissonClient redissonClient() {
   
		// 配置,用org.redisson.config的Config
		Config config = new Config();

		// 配置地址,这里是单节点模式
		config.useSingleServer().setAddress("redis://redis所在服务器IP:6379").setPassword("123123");

		// 创建RedissonClient 对象
		return Redisson.create(config);
	}
}

3)修改优惠券秒杀代码中获取锁部分
VoucherOrderServiceImpl.java

// 注入RedissonClient
@Resource
private RedissonClient redissonClient;
...
@Override
public Result seckillVoucher(Long voucherId) {
   
	...
	// 创建锁对象(旧的,改为如下)
	// SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
	// 锁对象(新的)
	RLock lock = redissonClient.getLock("lock:order:" + userId);

	// 获取锁(旧的,改为如下)
	boolean isLock =
;