案例–商品减库存
1.商品减库存的逻辑在单体架构下可以通过synchronized关键字来保证并发问题,但是在分布式情况下就不能保证了,以下代码来验证;
环境搭建:redis ,nignx 负载均衡, springboot 项目模拟减库存,jmeter 压测工具模拟高并发;--环境不会搭的,后面留言吧,不放太多代码上来了;
@GetMapping("/noLock")
public synchronized String noLock(){
Integer stock = Integer.valueOf(template.opsForValue().get(stockStr));
int deStock=stock-1;
if(stock>0){
template.opsForValue().set(stockStr,deStock+"");
System.out.println("扣减库存成功"+deStock);
}else {
System.out.println("扣减库存失败");
return "fail";
}
return "ok";
}
可以看到出现了超卖的情况,synchronized无法保证分布式的并发情况;
Redis分布式锁—setNx
setNx 的特性时,当不存在这个key时才能设置成功,否则返回false;
使用时还需要注意几点问题:
1.要在finally块中解锁
2.未执行解锁逻辑前 代码可能抛异常导致无法执行解锁操作,所以加锁要设置超时时间
3.假设设置的超时时间为10S; 线程1 执行业务逻辑需要15S,那么锁会自动释放;同时线程2可以获取锁,执行业务逻辑假设8S,当线程2执行到5S时, 线程1执行了解锁逻辑, 注意!!!此时线程1解锁的是线程2的锁!! ,所以我们还要为当前线程设置唯一的编号,只能自己解自己设置的锁
public synchronized String decrementStock(){
UUID UUID= java.util.UUID.randomUUID();
//为当前线程设置唯一的uuid,防止删除时可能造成的误删除;
String uuid = UUID.toString();
//设置redis锁
Boolean lockFlag = template.opsForValue().setIfAbsent(lockStr, uuid, 10l, TimeUnit.SECONDS);
//如果没有获取到 在这里自旋;
while (!lockFlag){
lockFlag = template.opsForValue().setIfAbsent(lockStr, uuid, 10l, TimeUnit.SECONDS);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果获取到锁,去执行业务逻辑
if(lockFlag){
try {
//拿到商品库存
Integer stock = Integer.valueOf(template.opsForValue().get(stockStr));
if(stock>0){
//减库存
int deStock=stock-1;
template.opsForValue().set(stockStr,deStock+"");
System.out.println("扣减库存成功"+deStock);
}else {
System.out.println("扣减库存失败");
return "fail";
}
}finally {
//最终要删除本线程设置的锁,
if(uuid.equals(template.opsForValue().get(lockStr))){
template.delete(lockStr);
}
}
}
return "ok";
}
还会出现一种问题是,业务逻辑执行的时间都要很长,设置的超时时间不好判断; 这里的思路是采用“续命”的方式,即每隔一段时间由子线程去为锁延时; 这部分代码要保证原子性操作; 可以使用lua脚本执行redis命令; 也可以使用现成的框架Redisson
Redisson
使用redisson实现上述功能,非常简单;
public synchronized String redissonDecrementStock(){
RLock lock = client.getLock(lockStr);
lock.lock();
Integer stock = Integer.valueOf(template.opsForValue().get(stockStr));
try {
if(stock>0){
int deStock=stock-1;
template.opsForValue().set(stockStr,deStock+"");
System.out.println("扣减库存成功"+deStock);
}else {
System.out.println("扣减库存失败");
return "fail";
}
}finally {
lock.unlock();
}
return "ok";
}
整理一下Redis分布式锁的具体实现思路,需要代码的留言吧;