[分布式锁] Springboot整合Redisson实现超卖问题还原和分析

超卖简单代码

写一段简单正常的超卖逻辑代码,多个用户同时操作同一段数据,探究出现的问题。

Redis有库存数量为100;如果大于0,则扣减1,重新存储Redis中;
运行代码测试
/**
 * Redis数据库操作,超卖问题模拟
 * @author 
 *
 */
@RestController
public class RedisController {
	
	// 引入String类型redis操作模板
	@Autowired
	private StringRedisTemplate stringRedisTemplate;


	//设置库存
	@RequestMapping("/setStock")
	public String setStock() {
		stringRedisTemplate.opsForValue().set("stock", "100");
		return "ok";
	}

	//模拟商品超卖代码
	@RequestMapping("/deductStock")
	public Map deductStock() {
		Map map = new HashMap<>();
		map.put("code",200);
		// 获取Redis数据库中的商品数量
		Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
		// 减库存
		if(stock > 0) {
			int realStock = stock -1;
			stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
			System.out.println("商品扣减成功,剩余商品:"+realStock);
		}else {
			System.out.println("库存不足.....");
		}
		return map;
	}
}

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第1张图片


单体应用的并发

在单应用模式下,使用Apifox压测,并发线程数20个

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第2张图片

 测试结果:

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第3张图片

每个请求相当于一个线程,当几个线程同时拿到数据时,线程A拿到库存为84,这个时候线程B也进入程序,并且抢占了CPU,访问库存为84,最后两个线程都对库存减一,导致最后修改为83,实际上多卖出去了一件

这里多个线程处理一个变量导致了并发问题,我们使用synchronized进行处理

使用synchronized同步锁

依旧还是先测试单服务器

// 使用 synchronized同步锁,处理单体应用的并发
	@RequestMapping("/deductStock2")
	public Map deductStock2() {
		synchronized (this) {
			Map map = new HashMap<>();
			// 获取Redis数据库中的商品数量
			Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
			// 减库存
			if(stock > 0) {
				int realStock = stock -1;
				stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
				System.out.println("商品扣减成功,剩余商品:"+realStock);
			}else {
				System.out.println("库存不足.....");
			}
			map.put("code",200);
			return map;
		}
	}

库存100

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第4张图片

重新压测,得到的日志信息如下所示: 

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第5张图片

 可以看到,按顺序扣减成功


分布式应用的并发

 在单机模式下,添加synchronized关键字,的确能够避免商品的超卖现象!

但是在分布式微服务中,针对该服务设置了集群,synchronized依旧还能保证数据的正确性吗?

假设多个请求,被注册中心负载均衡,每个微服务中的该处理接口,都添加有synchronized,

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第6张图片

 依然会出现类似的超卖问题:

synchronized只是针对单一服务器JVM进行加锁,但是分布式是很多个不同的服务器,导致两个线程或多个在不同服务器上共同对商品数量信息做了操作!

我们复制刚才的应用并设置两个端口8000、8001。然后开两个Apifox并设置并发数20,分别点击进行模拟分布式并发

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第7张图片

测试结果:[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第8张图片

可以看到40次请求,正确扣减后的库存应为60,但最后库存为77,少扣了17个

使用Redission分布式锁

我们引入Redission分布式锁,并改造一下代码

// 使用 Redisson分布式锁,处理分布式多应用的并发
	@RequestMapping("/deductStock3")
	public Map deductStock3() throws InterruptedException {
		Map map = new HashMap<>();
		RLock keyDeductStock = redissonClient.getLock("key:deductStock");

		//尝试获取分布式锁20秒,获取失败则返回
		boolean b = keyDeductStock.tryLock(20, TimeUnit.SECONDS);
		map.put("code",300);
		if (!b){
			return map;
		}
		try{
			// 获取Redis数据库中的商品数量
			Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
			// 减库存
			if(stock > 0) {
				int realStock = stock -1;
				stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
				System.out.println("商品扣减成功,剩余商品:"+realStock);
			}else {
				System.out.println("库存不足.....");
			}
			map.put("code",200);
			return map;
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			keyDeductStock.unlock();
		}
		return map;
	}

所有应用在访问扣减库存接口的时候,必须先获取Redission分布式锁, 尝试获取时间设置为20秒,如果20秒之内没获取,则直接返回获取失败,如果线程成功获取锁,则执行业务逻辑,锁的超时时间默认30s,看门狗默认10s会进行续期,将超时时间重置为30s,当业务执行完,则释放锁

开始分布式压力测试:

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第9张图片

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第10张图片

 可以看到40个请求都成功。我们看看数据是否正常

[分布式锁] Springboot整合Redisson实现超卖问题还原和分析_第11张图片

 可以看到,库存成功扣减。

你可能感兴趣的:(#,Redis,redis,数据库)