目录
3.4 set nx ex/px+Lua(解决锁重入和锁续期)
一、基于 Redis 演示超卖现象
1.1 Redis 超卖现象
1. 编写业务逻辑实现
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void deduct(){
//1.查询库存信息
String stock = stringRedisTemplate.opsForValue().get("stock");
//2.判断库存是否充足
if(stock != null && stock.length() > 0){
Integer st = Integer.valueOf(stock);
if(st>0){
//3.扣减库存
stringRedisTemplate.opsForValue().set("stock",String.valueOf(st-1));
}
}
}
2. 通过 JMeter 进行并发压力测试
3. 查看 Redis 请求后实际的商品库存数量:发生超卖现象
1.2 超卖现象解决方案
JVM 本地锁机制:本地锁可以解决一个资源内部的并发资源的问题
Redis 提供的乐观锁机制(性能较低):watch multi exec
watch:可以监控一个或多个 key 的值,如果在事务执行之前,key 的值发生变化,则取消事务的执行
multi:开启事务
exec:执行事务
分布式锁:分布式锁可以实现跨进程跨服务器的使用
基于 Redis 实现:setnx
基于 Zookeeper/etcd 实现
基于 MySQL 实现
二、Redis 的乐观锁机制
2.1 原生客户端演示
Redis中的WATCH
命令用于实现乐观锁机制。它可以监视一个或多个键,在事务执行前,如果这些被监视的键没有被其他客户端修改,事务可以正常执行;但只要有一个被监视的键在事务执行前被修改了,事务在执行EXEC
时就会失败,从而让应用程序可以重新尝试事务或者采取其他处理逻辑,以保证在高并发场景下数据的一致性。
在执行WATCH stock
之后,stock
键的值被客户端2修改了。WATCH
命令会监视指定的键,当在事务执行前(从WATCH
命令执行到EXEC
命令执行之间),被监视的键发生了变化,那么事务在执行EXEC
时就会失败,以保证数据的一致性和避免并发问题。
2.2 业务代码实现
public void deduct(){
stringRedisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//1.查询库存信息
operations.watch("stock");
String stock = operations.opsForValue().get("stock").toString();
//2.判断库存是否充足
if(stock != null && stock.length() != 0){
Integer st = Integer.valueOf(stock);
if(st>0){
//3.扣减库存
operations.multi();
operations.opsForValue().set("stock",String.valueOf(--st));
List exec = operations.exec();
if(exec == null || exec.size()==0){
//重试操作
deduct();
}
return exec;
}
}
return null;
};
});
}
运行 JMeter 进行并发测试:吞吐量只有104请求/秒:(性能太差)
查看 Redis 商品库存数量:
三、单机部署 Redis 实现分布式锁
3.1 分布式锁的演变和升级
Redis 实现分布式锁的演变和升级:
setnx:set if Not eXists -> setnx key value -> 问题:因为没有设置过期时间,所以可能存在死锁的问题(执行一半程序崩溃,后面的释放锁流程没有执行)。
set nx ex/px:Redis 2.6+ set key value