高并发单体项目 库存问题
1. 简单的业务逻辑需求
@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {
@Autowired
private RedisCache redisCache;
@PostMapping("/del_stock")
public AjaxResult delductStock() {
int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
if(stock > 0) {
int realStock = stock - 1;
redisCache.setCacheObject("stock", realStock + "" );
System.out.println("扣减成功,剩余库存:" + realStock);
return AjaxResult.success(realStock);
}else {
System.out.println("扣减失败,库存不足,");
return AjaxResult.error("扣减失败,库存不足");
}
}
}
bug:在高并发中多个用户会超卖
2. 使用java 内置锁
@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {
@Autowired
private RedisCache redisCache;
@PostMapping("/del_stock")
public AjaxResult delductStock() {
synchronized (this) {//代码块
int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
if(stock > 0) {
int realStock = stock - 1;
redisCache.setCacheObject("stock", realStock + "" );
System.out.println("扣减成功,剩余库存:" + realStock);
return AjaxResult.success(realStock);
}else {
System.out.println("扣减失败,库存不足,");
return AjaxResult.error("扣减失败,库存不足");
}
}
}
}
在集群中,jdk,synchronized,只能控制单机下的并发,但是大部分项目都是多台服务器部署项目,就控制不了并发问题,出现超卖
redis实现分布式锁
@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {
@Autowired
public RedisTemplate redisTemplate;
@Autowired
private RedisCache redisCache;
@GetMapping("/del_stock")
public AjaxResult delductStock() {
String lockKey = "prodcut_001";
//第一个请求加锁
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");
if(!flag) {
return AjaxResult.error("当前系统繁忙,请稍后再试");
}
try {
int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
if(stock > 0) {
int realStock = stock - 1;
redisCache.setCacheObject("stock", realStock + "" );
System.out.println("扣减成功,剩余库存:" + realStock);
return AjaxResult.success(realStock);
}else {
System.out.println("扣减失败,库存不足,");
return AjaxResult.error("扣减失败,库存不足");
}
} finally {
//释放当前业务完成后释放锁
redisTemplate.delete(lockKey);
}
}
}
当系统宕机,出现死锁显现
非高并发
@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {
@Autowired
public RedisTemplate redisTemplate;
@Autowired
private RedisCache redisCache;
@GetMapping("/del_stock")
public AjaxResult delductStock() {
String lockKey = "prodcut_001";
// //第一个请求加锁
// Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//redis.setnx
// //添加延时时间
// redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
//原子命令
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge", 10, TimeUnit.SECONDS);
if(!flag) {
return AjaxResult.error("当前系统繁忙,请稍后再试");
}
try {
int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
if(stock > 0) {
int realStock = stock - 1;
redisCache.setCacheObject("stock", realStock + "" );
System.out.println("扣减成功,剩余库存:" + realStock);
return AjaxResult.success(realStock);
}else {
System.out.println("扣减失败,库存不足,");
return AjaxResult.error("扣减失败,库存不足");
}
} finally {
//释放当前业务完成后释放锁
redisTemplate.delete(lockKey);
}
}
}
只能针对非高并发业务场景,可以不会超卖;如果在超高并发时候,可能会出现线程1释放锁,是线程2加的锁
超高并发
@RestController
@RequestMapping("/api/product")
public class ProdcutController extends BaseController {
@Autowired
public RedisTemplate redisTemplate;
@Autowired
private RedisCache redisCache;
@GetMapping("/del_stock")
public AjaxResult delductStock() {
String lockKey = "prodcut_001";
// //第一个请求加锁
// Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, "zhuge");//redis.setnx
// //添加延时时间
// redisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
//给每一个线程标识一个唯一id
String clientId = IDGeneratorUtils.getUUID().toString();
//原子命令
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey,clientId, 10, TimeUnit.SECONDS);
if(!flag) {
return AjaxResult.error("当前系统繁忙,请稍后再试");
}
try {
int stock =Integer.parseInt(redisCache.getCacheObject("stock"));
if(stock > 0) {
int realStock = stock - 1;
redisCache.setCacheObject("stock", realStock + "" );
System.out.println("扣减成功,剩余库存:" + realStock);
return AjaxResult.success(realStock);
}else {
System.out.println("扣减失败,库存不足,");
return AjaxResult.error("扣减失败,库存不足");
}
} finally {
//在释放锁时候,保证是当前线程id加的锁
if(clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
//假如这个卡顿一下,可能会删除 下一个线程2加的锁
//释放当前业务完成后释放锁
redisTemplate.delete(lockKey);
}
}
}
}
假如在删除时候,这个卡顿一下,可能会删除 下一个线程2加的锁
- 解决方案:
- 锁续命:假如在10s定时任务,检测当前线程id锁是否存在,如果redis存在,就再延时 超时时间。通过redission框架自动实现 锁续命
<dependency>
<groupId>org.redission</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
- redis框架模式
单机模式,集群模式,主从模式
网友评论