引言
在分布式系统中,保证数据的一致性和避免竞争条件是至关重要的。分布式锁是一种常用的机制,而Redis作为一款高性能的内存数据库,提供了简单而强大的分布式锁方案。本文将深入探讨如何利用Redis高并发分布式锁来解决分布式系统中的并发控制问题,并提供实战案例。
正常库存扣减代码
public void deductStock(){
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock>0){
stock = stock -1 ;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("扣减成功,剩余库存:"+stock);
}else {
System.out.println("扣减失败,库存不足");
}
}
//弊端:两个线程同时执行读取stock为50,然后各自-1 修改为49,实际应该50-2=48
代码调整后
public void deductStock(){
synchronized (this){
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock>0){
stock = stock -1 ;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("扣减成功,剩余库存:"+stock);
}else {
System.out.println("扣减失败,库存不足");
}
}
}
//弊端:适用于单体项目,如果该项目被部署两台服务器,两台服务器同时访问获取stock为50,然后各自-1 修改为49,实际应该50-2=48 也会存在上述问题,因为synchronized只能在当前项目下生效
redis的一个简单的分布式锁
public void deductStock(){
String lockKey = "lockKey";
try {
Boolean result = redisTemplate.opsForValue().setIfAbsent("lockKey", "nuoyi",10, TimeUnit.SECONDS);
if(!result){
System.out.println("....");
return ;
}
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock>0){
stock = stock -1 ;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("扣减成功,剩余库存:"+stock);
}else {
System.out.println("扣减失败,库存不足");
}
}catch (Exception e){
e.printStackTrace();
}finally {
redisTemplate.delete("lockKey");
}
}
//弊端:适用于访问量不高的系统 如果访问量非常的大,第一个a请求获取到锁 ,设置过期10s,执行业务需要15s,a业务执行10s后锁自动过期被第二个请求b拿到并执行业务,当b业务执行到第5s时,b的锁被a的请求给释放了,如此高并发循环,导致锁失效
优化上述redis的分布式锁解决不是自己的锁不释放
public void deductStock(){
String lockKey = "lockKey";
String clientId = UUID.randomUUID().toString();
try {
Boolean result = redisTemplate.opsForValue().setIfAbsent("lockKey", clientId,10, TimeUnit.SECONDS);
if(!result){
System.out.println("....");
return ;
}
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock>0){
stock = stock -1 ;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("扣减成功,剩余库存:"+stock);
}else {
System.out.println("扣减失败,库存不足");
}
}catch (Exception e){
e.printStackTrace();
}finally {
//不是自己的锁不删除
if(clientId.equals(redisTemplate.opsForValue().get(lockKey))){
redisTemplate.delete("lockKey");
}
}
}
//不是自己的锁不删除,但是这个只是解决了a请求删除b请求的锁,如果a请求15秒锁第十秒过期了,b请求就进来了还是会有问题,解决方案:给锁续命
Redisson代码
private final Redisson redisson;
public void deductStock(){
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);//获取锁
try {
redissonLock.lock();//加锁及锁续命 默认锁失效30s 守护线程每10s续命一次
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if(stock>0){
stock = stock -1 ;
redisTemplate.opsForValue().set("stock",stock+"");
System.out.println("扣减成功,剩余库存:"+stock);
}else {
System.out.println("扣减失败,库存不足");
}
}catch (Exception e){
e.printStackTrace();
}finally {
redissonLock.unlock();//释放锁
}
}
//三行代码即可满足获取锁、锁续命、释放锁,完美解决上述redis的释放锁及锁续命问题 redisson的底层还是redis,使用了大量的lua脚本,lua脚本支持原子性
redisson配置
@Bean
public Redisson redisson(){
Config config = new Config();
//useSingleServer 单机版
config.useSingleServer().setAddress("redis://"+instance.getRedisHost()+":"+instance.getRedisPort()).setDatabase(instance.getRedisDataBase());
return (Redisson)Redisson.create(config);
}
lua脚本语言:
-
减少网络开销(批量操作)
-
原子性
-
替代redis的事务
为什么redis不常使用lua?
因为Redis是个单线程,如果lua有耗时运算或循环,Redis则阻塞,不会管其他的操作
通过学习本文,读者将深入了解Redis分布式锁的原理和实践应用。分布式锁在构建高并发、分布式系统中发挥着关键作用,正确使用和理解分布式锁是确保系统稳定性和可靠性的重要一环。希望本文能为读者提供有益的指导和实战经验。