redis分布式锁踩坑记录

编程入门 行业动态 更新时间:2024-10-11 09:25:54

redis<a href=https://www.elefans.com/category/jswz/34/1770120.html style=分布式锁踩坑记录"/>

redis分布式锁踩坑记录

前言

下面说的问题, 很多都可以使用redisson解决, 所以我就不说redisson

我就说说不用redisson如何解决吧

本章只提供思路

redis主从复制的坑

问题

redis 主从复制存在时间间隔, 主节点写入数据之后, 从节点还没来得及同步主节点数据.

主节点挂了, 从节点没有该数据, 此时从节点变身为主节点, 完了, 数据没有, 原本主节点的分布式锁丢失

其他节点可以去加锁了

解决

使用redissonRedLock, 或者使用redissonClient.getMultiLock

RedLock的思路非常简单, 给3个节点添加分布式锁, 如果两个节点已经有了锁数据, 就表示上锁成功(大于节点一般的数)

后面思路就简单了, 如果某个主节点挂了, 剩下的两个主节点都存在分布式锁, 即便从节点变成主节点, 还是可以保证分布式锁还锁着

但是这种方式已经被redisson标记最好不要再使用了

还可以使用多个锁, 这种方式更加简单, 需要全部节点都写入才算写入分布式锁, 如果有一个节点错误那么分布式锁失败, 就这么简单

存在问题

效率极慢

redis分布式锁不可重入

问题: 不可重入问题

默认情况redis使用setnx实现分布式锁, 但是这种锁不可重入, 无法重试

解决方案

锁不可重入的原因在于, 重入锁的方案是上一次锁, 计数一次, 解锁一次, 计数减少一次, 直到计数为0, 那么锁删除

所以我们不能使用string类型的锁, 我们可以使用 hash

key的话类似于对象锁, value的话使用的是 uuid + threadId形式

uuid区分是否是同一个项目, 一般是静态类型

这个uuid通常是java的UUID类型是静态的类型生成的一个微服务就只有一个唯一的

问题: 锁超时问题

问题发现

如果锁超时请求还未执行完毕, 那么下一次又有新的请求在上锁, 此时正在执行的线程至少两个以上

解决方法

锁超时问题其实也很好解决, 续约

锁在上锁的时候, 添加定时器, 在锁即将过期前, 如果还没有解锁的话, 我们可以续约时间

传入参数: 锁超时时间, 默认锁续约总时间

创建定时器, 在锁过期的时候续约

事务和锁作用域不同

问题

事务和锁的作用域不同导致锁解锁了, 但是事务还未提交, 此时又来了个线程2上了锁, 而线程1的线程还未交事务提交掉

解决方案

让锁的作用域大于事务的

实现方法两个 TransactionTemplate 或者使用 AOPContext 获取代替对象 调用添加了事务注解的函数

一人参与一次秒杀

问题发现: 一人多次秒杀

正常一个人只能参与一次秒杀

但是很多人使用外挂, 实现一人在短时间内发生多次秒杀的情况, 这种是不行的

解决方案

非常简单, 分布式Set集合就行了

订单id是key, set集合是userId, 在插入集合时判断下set集合有多少数据了, 达到商品数量就可以返回秒杀结束

这里面还有多个步骤导致线程不安全的问题

秒杀的其他解决方案:

要对10000包奶粉进行秒杀 那么你就可以给多个集群分发数量进行秒杀 比如我们的集群有3台, 那么我可以使用限流组件将1w包奶粉分发给 3 台电脑, 两台 3000 包, 其中一台 4000 包, 在这三台服务器之前放置一个类似负载均衡的机器, 如果有一台服务器中的奶粉被秒杀结束, 那么告知负载均衡组件, 负载均衡组件对其进行降级

redis连接池用尽

存在redis连接池连接被用尽, 导致无法获取新的redis连接, 特别是你开启了redis事务的情况下
解决方案: 使用RedisConnectionUtils释放redis连接

stringRedisTemplate.connectionFactory?.let { RedisConnectionUtils.unbindConnection(it) }
Thread.sleep(20)

删除了别人的锁

分布式锁存在最初始版本有点像

我上了A锁, 你发现A锁在, 那我就等着

现在我上了A锁, 然后我被阻塞住了, A锁超时, 你发现没锁了, 直接上A锁

此时我开始运行, 完成任务, 执行 unlock, 此时把A锁解锁掉了

解决方案

这不是很简单, 给锁写上我的名字, 但是我的名字很多, 也许存在同名的我, 所以再加上我家的地址

我的名字: threadId, 我家的地址: static final String uuid = UUID.toString()

静态保证了整个微服务的标记, 也就是我家的住址, 而我家有多个不重名的成员

unlock涉及多个步骤线程不安全

解决方案

使用lua脚本

秒杀业务性能提升

秒杀业务由于存在无限个人秒杀商品, 所以外界的压力是无限大的

那么我们只能使用同样看起来无限大的redis去承载外部的无限大的秒杀任务

将秒杀和数据库隔开, 这样保证数据库不会收到过量的IO操作

所以秒杀操作全部放在redis中

秒杀任务触发时, 将会包装一个event事件丢给mq消息队列

消息队列将会以mysql能够受得了的频率慢慢写入数据

为什么这里要使用mq, 而不是线程池呢? 因为线程池太容易丢失event了, 特别是断电了怎么办? 它的阻塞队列存在于内存中的

更多推荐

redis分布式锁踩坑记录

本文发布于:2024-02-14 08:32:24,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1762798.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:分布式   redis   锁踩坑

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!