文章目录
- 电商库存问题
- 单机处理-Sychronized
- 多机器处理-分布式锁
- 入门级别,用redis实现,setnx
- 问题1:逻辑可能异常,造成死锁
- 问题2:机器宕机
- 问题3:锁一直失效,乱套
- 锁续命
- redisson
- 分布式丢锁问题
- 主从、分布式的情况下锁丢失
- 红锁问题
- 分布式锁性能优化
- 锁的粒度越小越好
- 分段锁
- 电商可能存在问题(下单链路)
- 订单重复生成
- 付钱了,后台状态取消了
- Future(成功监听处理)
- Lua脚本(redisson内部加锁实现)
- Apache JMeter工具(压测)
电商库存问题
redis里面存储库存
程序里面减
多并发可能会有问题(超卖)
单机处理-Sychronized
可以加锁解决(Sychronized),但是只能解决单机问题,在分布式多机器上用谨慎,很可能出BUG
多机器处理-分布式锁
前台Nginx,后台代码部署于多个tomcat,共用redis
多台机器,并发越多,超卖的情况就越明显
入门级别,用redis实现,setnx
setnx key value,当且仅当key不存在,否则会使用第一次的值,后续不会覆盖
利用这个特性去实现锁
因为redis核心命令执行是单线程,库存计算需要存redis,多次进来需要排队,根据是否存在指定key去判断
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "aaa");
//用返回结果去判断锁
if(!result){return "系统正在执行"
}
// 执行业务逻辑
// 注意删除锁
stringRedisTemplate.delete("lockKey")
问题1:逻辑可能异常,造成死锁
加try catch,删除放到finaly
try{// 执行业务逻辑
}finally{stringRedisTemplate.delete("lockKey")
}
问题2:机器宕机
锁,加过期时间
// 不要这样分开执行,因为宕机无时不在
stringRedisTemplate.expire("lockKey", 10, TimeUnit.SECONDS);// 使用元执行stringRedisTemplate.opsForValue().setIfAbsent("lockKey", "aaa", 10, TimeUnit.SECONDS);
问题3:锁一直失效,乱套
线程运行时间无法预料,会出现下面的问题
逻辑执行一半,过期时间到时,第二次请求又会进去,但是第一次把第二次的锁删了,第三次请求又会进去,第二次又将锁删除
问题根本点:自己的锁,被别的请求删除
解决:每一个锁的值用唯一值及进行标识,删除时做判断
// 设置锁,值为唯一值
String clientId = UUID.randomUUID().toString();
stringRedisTemplate.opsForValue().setIfAbsent("lockKey", clientId, 10, TimeUnit.SECONDS);// 删除锁,进行值判断
if(clientId.equals(stringRedisTemplate.opsForValue().get("lockKey"))){// 这儿又可能出现锁到时问题,也会有并发问题stringRedisTemplate.delete("lockKey")
}
锁续命
当一个请求过来,加锁成功,开始执行代码逻辑;
这时,在后台开启一个分线程,进行一个定时任务,定时给锁续命;
检查主线程锁是否被删掉,如果删掉了说明任务执行完成了,否则锁快到过期时间时,去延长锁的超时时间
redisson
jedis儿子
redisson github地址
引入依赖
<dependency><groupId>org.redisson</groupId><artifacId>redisson</artifacId><version>3.6.5</version>
</dependency>
配置
@Bean
public Redisson redisson(){// 此为单机模式Config config = new Config();config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(8);return (Redisson) Redisson.create(config);
}
使用
@Autoried
private Redisson redisson;// 获取一把锁对象
RLock redissonLock = redisson.getLock("lockKey");
// 加锁
redissonLock.lock();try{// 执行业务逻辑
}finally{// 删锁redissonLock.unlock();
}
分布式丢锁问题
主从、分布式的情况下锁丢失
上面的实现,单机没问题。但是多机,在主从、分布式的情况下,会有问题
方案一:
zookeeper,主从节点,主leader,从flowwer,遵循CAP,满足一致性
存到主节点,还要同步到子节点,才能生效
redis遵循AP,满足可用性
存则立马生效
redis性能要比zookeeper强很多,但是zookeeper基本不会丢锁
方案二:
Redlock(红锁)
同zookeeper差不多,也需要将加锁key存到多个节点,只有超过半数节点返回添加成功,加锁才成功
这样,新的加锁请求如果存在问题,则不会超过半数,则不会加锁成功
尽量自己用一套分布式锁服务
红锁问题
问题1:高可用问题
不要通过主从节点去处理,同步的时候就可能有问题。新加锁,因为有子节点,所以半数的计算还是会有多并发问题
高可用,可以多加几个节点,防止节点被挂问题,一般用3-5个节点,注意用奇数节点,别用偶数
问题2:持久化问题
aof持久化方案,1s中一次,依然会丢锁
如果第二个节点不够1s,但是节点挂了,但是重启数据就会掉了,后续的就可以再在这个节点加锁
aof,百分百不丢锁不可能
如果用aways,每一条命令持久化一次,但是性能又太差了
分布式锁性能优化
在redis中是将并行转串行实现,但是特别高的并发,性能较低
锁的粒度越小越好
让串行执行的数量越小越好
分段锁
ConcurrentHashMap底层实现
在redis中分段存储
P_101 = 200
转化为
P_101_1 = 20
…
P_101_10 = 20
每一个段位都加一个锁
通过分发算法,去请求不同的redis,可以去进行多次并发打到不同请求
这样有多少个段位,就相当于提升了多少倍
电商可能存在问题(下单链路)
订单重复生成
快速多次点击提交订单
多个tab(浏览器)点击提交订单
通过分布式锁处理,key设为(userId + 购物车商品id排序(id1+id2+id2+…))
付钱了,后台状态取消了
马上过期的时候支付,支付成功了,但是后台取消了
通过分布式锁处理,支付的时候加一把锁(针对订单id),取消的时候加一把锁(针对订单id),key一样,则不会执行业务。
Future(成功监听处理)
Lua脚本(redisson内部加锁实现)
在Redis中执行,具备原子性
Apache JMeter工具(压测)
JAVA开发的压测软件,模拟多并发