Redis分布式锁解决并发超卖问题——Jedis和Redisson

项目实战 专栏收录该内容
0 篇文章 0 订阅

参考B站视频资源:BV1Vt4y1D7BH,程序员诸葛

一、基本介绍

1.1 超卖场景

不同用户在读请求的时候,发现商品库存足够,然后同时发起请求,进行秒杀操作,减库存,导致库存减为负数。

——主要解决方案Redis分布式锁、MQ队列

1.2 普通加锁

如果直接对减库存代码加同步锁,由于分布式系统有多个Tomcat,前端请求会被分发到不同的Tomcat上去,Tomcat会各自访问数据库,这样加锁就控制不了。

1.3 解决方案——Redis的分布式锁(Jedis和Redisson)

——用分布式锁,用Redis中的setnx命令:setnx key value可实现分布式锁。

setnx跟set的区别:
① set key v1,set key,v2,v2会覆盖v1;
② 用setnx,将key设置为value时,当且仅当key不存在,若存在,不会做任何动作。

setnx被封装到了Jedis和Redisson中,Jedis和Redisson是Java中对Redis操作的封装框架

  • (1)Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户端,它的方法和Redis 的命令很类似;
  • (2)Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更强大。
    命令分别为:stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout:30, TimeUnit.SECONDS);redisson.lock();

多个Tomcat都去到Redis中排队(因为Redis是单线程的,不管多少分布式的Tomcat请求过来,都会到Redis中去排队)。
在这里插入图片描述

二、高并发分布式锁——Jedis

以下代码相当于:jedis.setnx(key,value)

加锁:设置一个key:lockKey

@AutoWire 
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/stock");//注解SpringMVC
public String deductStock(){
	String lockKey = "lockKey";
	String clienId = UUID.randomUUID().toString();//加一个本线程的客户ID
	try{
		//锁加30s过期时间,这样高并发下锁会提前失效,导致超卖,需加ID,在后面判断线程结束才释放锁
		//既然线程结束就会释放锁,为和还要超时时间?——担心后面程序挂掉,而导致死锁
		Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, timeout:30, TimeUnit.SECONDS); //相当于jedis.setnx(key,value)
		if(!result){
			return “error_code”;//前面线程设置了key,后面就没法设置,失败就返回error_code
		} 
		//减库存操作
		int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
		if(stock > 0){
			int realStock = stock - 1;
			stringRedisTemplate.opsForValue().set("stock",realStock + "");
			System.out.println("扣减成功,剩余库存" + realStock);
		} else {
			System.out.println("扣减失败,库存不足");
		}
	}finally{
		if(clienId.equals(stringRedisTemplate.opsForValue().get(lockKey)));//跟本线程ID对比,确保本线程执行完后才删除相应锁
		stringRedisTemplate.delete(lockKey);//业务结束,释放锁
	}
}

2.1 程序思路

  • 上述设置了:在主线程即减库存线程结束就删除锁,以及设置锁超时时间
  • 既然线程结束就会释放锁,为何还要超时时间?——担心后面程序挂掉,而导致死锁

2.1.1 锁延期

但是也可能会出现线程未结束锁就超时了,锁超时时间又不能设置得太久,怎么办?

——可另外设置一个分线程来开启一个定时任务,每隔一段时间检查一下主线程在锁快超时时有没有执行完,若没有执行完,则把锁过期时间继续设置为初始值30s —— 锁延期

三、高并发分布式锁——Redisson

Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功能,相比于Jedis 更强大,特别是在分布式领域

3.1 具体实施

(1)先引入Redisson依赖包3.6.5:

@SpringBoorApplication
public class Application{
	public static void main(String[] args){
		SpringApplication.run(Application.class,args);
	}
	@Bean
	public Redisson redisson(){//redisson客户端,注入到Bean容器中
		
	}
}

(2)在controller中注入:

@AutoWire 
private Redisson redisson;
@AutoWire 
private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/stock");//注解SpringMVC
public String deductStock(){
	String lockKey = "lockKey";
	RLock redissonLock = redisson.getLock(lockKey);//(1)拿一个Redisson的锁对象
	try{
		redisson.lock();//(2)加锁,底层默认设置超时时间为30s
		//(3)减库存操作
		int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
		if(stock > 0){
			int realStock = stock - 1;
			stringRedisTemplate.opsForValue().set("stock",realStock + "");
			System.out.println("扣减成功,剩余库存" + realStock);
		} else {
			System.out.println("扣减失败,库存不足");
		}
	}finally{
		redissonLock.lock();//(4)释放锁
	}
}

3.2 程序思路

程序到了(2)处,有且只有一个线程能继续往下执行,其它线程都会阻塞在(2)处,直到该线程被释放掉,其它线程才能加锁继续。**加锁的后台会每隔一段时间去检测线程持有的锁是否存在,还在的话,就延迟锁超时时间,即锁延期。

3.3 Redisson分布式锁底层原理

在这里插入图片描述

多个线程去执行lock操作,仅有一个线程能够加锁成功,其它线程循环阻塞。加锁成功,锁超时时间默认30s,并开启后台线程,加锁的后台会每隔10秒去检测线程持有的锁是否存在,还在的话,就延迟锁超时时间,重新设置为30s,即锁延期

对于原子性,Redis分布式锁底层借助Lua脚本实现锁的原子性。
锁延期是通过在底层用Lua进行延时,延时检测时间是对超时时间timeout /3

  • Redis一般可以设置多级缓存,第一台Redis服务器作为主服务器,第二台作为备用的。称作主从或者哨兵
  • 或者搭建一个Redis集群架构

3.4 分布式锁经典面试题

问题:如果Redis是用了集群架构,当主Redis加锁了,开始执行线程,若还没来得及将锁通过异步同步的方式同步到从Redis节点,主节点就挂了,此时会把某一台从节点作为新的主节点,此时别的线程就可以加锁了,这样就出错了,怎么办?

3.4.1 解答一:使用zookeeper代替Redis

分布式锁也可以通过zookeeper来实现,它加锁成功后返回到Redisson线程客户端返回的时间,会有一定的延时(需要超过半数的节点加锁成功才会返回),通过牺牲可用性来保证一致性

3.4.2 解答二:利用RedLock

——超过半数的Redis节点加锁成功才算成功
在这里插入图片描述
这样会造成性能问题,好像将高并发请求,用来串行执行了。

分布式锁的本质:是将并行请求,变成了串行请求
——一致性zookeeper更好,并发性Redis更好,根据业务场景来选择。

3.4.3 如何提高分布式锁并发性呢?

  • 0
    点赞
  • 0
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值