Redis分段锁,如何设计?

问题场景:热点库存扣减问题

秒杀场景,有一个难度的问题:热点库存扣减问题。

  • 既要保证不发生超卖

  • 又要保证高并发

如果解决这个高难度的问题呢? 答案就是使用redis 分段锁。

什么是分布式锁?

一个分布式系统中,存在客户端多个用户,同时通过多个业务微服务,发起一个数据修改。

如果没有分布式锁机制保证,在那多台机器上的多个服务可能进行并发修改操作,导致数据修改的不一致,出现脏读脏写,这就会造成问题。

而分布式锁机制就是为了解决类似这类问题,保证多个服务之间互斥的访问共享资源,如果一个服务抢占了分布式锁,其他服务没获取到锁,就不进行后续操作。

上图中,哪个客户端的服务抢占了分布式锁,就可以去扣减库存。

其他服务没获取到分布式锁,就不进行后续操作。

  • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。

  • 用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

分布式锁的条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。

  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

  • 具有容错性。只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。

  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

普通的分布式锁如何实现?

分布式锁的实现由很多种,文件锁、数据库、redis等等,比较多;分布式锁常见的多种实现方式:

  1. 数据库悲观锁;

  2. 数据库乐观锁;

  3. 基于Redis的分布式锁;

  4. 基于ZooKeeper的分布式锁。

在实践中,还是redis做分布式锁性能会高一些

普通分布式锁的性能问题

分布式锁一旦加了之后,对同一个商品的下单请求,会导致所有下单操作,都必须对同一个商品key加分布式锁。

假设某个场景,一个商品1分钟6000订单,每秒的 600个下单操作,

假设加锁之后,释放锁之前,查库存 -> 创建订单 -> 扣减库存,每个IO操作100ms,大概300毫秒。

具体如下图:

可以再进行一下优化,将 创建订单 + 扣减库存 并发执行,将两个100ms 减少为一个100ms,这既是空间换时间的思想,大概200毫秒。

将 创建订单 + 扣减库存 批量执行,减少一次IO,也是大概200毫秒。也就是单个商品而言,只有 5 QPS.

假设一个商品sku的数量是10000,10秒内秒杀完,也就是单个商品而言,需要 单商品 100 QPS,如何应对一个商品的 100qps秒杀。

甚至,如果单商品需要 1000qps秒杀呢?

答案是,使用 分段加锁

第一次优化之后:使用Redis分段锁提升秒杀的并发性能

如何提高性能呢?空间换时间

为了达到每秒600个订单,可以将锁分成 600 /5 =120 个段,反过来, 每个段1秒可以操作5次, 120个段,合起来,及时每秒操作600次。

进行抢夺锁的,如果申请到一个具体的段呢?

  • 随机路由法

  • hash取模法

如果是用随机路由算法,可以将请求随机到一个分段, 如果不行,就轮询下一个分段,具体的流程,大致如下:

这个是一个理论的时间预估,没有扣除 尝试下一个分段的 时间, 另外,实际上的性能, 会比理论上差,从咱们实操案例的测试结果,也可以证明这点。

随机路由法的问题:

不同分端之间,可能库存消耗不均,导致部分用户无法扣减库存,反复进行重试,拖慢系统性能。

如何进一步优化:hash取模法。

第二次优化之后:使用hash取模法,减少库存消耗不均和无效重试

由于秒杀场景的分布式锁,实际上是为了防止超卖, 和库存是强相关的。

所以,可以结合库存,把秒杀的分布式锁进行改进。

第一步:把redis 的分段方式进行演进,额外增加一个总库存分段锁,用于分配存储剩余的总库存。采用多批次少量分配的思路,通过定时任务,从总库存向分段库存中迁移库存。

第二步:使用hash取模法,把用户路由到某一个分段,如果分段里边的库存耗光了,就去访问剩余的总库存。

库存动态迁移

为了防止分段多库存耗光,大家都去抢占总库存锁。

采用多批次少量分配的思路,通过定时任务,从总库存向分段库存中迁移库存。

至此, hash取模法的分段锁设计方案,已经完美实现。

相关推荐
IT项目管理27 分钟前
达梦数据库DMHS介绍及安装部署
linux·数据库
你都会上树?34 分钟前
MySQL MVCC 详解
数据库·mysql
大春儿的试验田40 分钟前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存
likeGhee1 小时前
python缓存装饰器实现方案
开发语言·python·缓存
hqxstudying1 小时前
Redis为什么是单线程
java·redis
C182981825751 小时前
OOM电商系统订单缓存泄漏,这是泄漏还是溢出
java·spring·缓存
Ein hübscher Kerl.1 小时前
虚拟机上安装 MariaDB 及依赖包
数据库·mariadb
醇醛酸醚酮酯2 小时前
Qt项目锻炼——TODO清单(二)
开发语言·数据库·qt
GreatSQL社区2 小时前
用systemd管理GreatSQL服务详解
数据库·mysql·greatsql
掘根2 小时前
【MySQL进阶】错误日志,二进制日志,mysql系统库
数据库·mysql