分布式锁功效初探——以电商问题为例

发布时间:2023年12月24日

电商库存问题

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开发的压测软件,模拟多并发

文章来源:https://blog.csdn.net/weixin_40970987/article/details/135168466
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。