绝命一问:秒杀Redis分段锁,如何设计?

本文详细介绍了在高并发秒杀场景中使用分布式锁(如Redis分段锁和Hash取模法)来解决热点库存扣减问题,包括普通分布式锁的不足、实现方式和性能优化,旨在帮助面试者理解和掌握这一技术,提升面试表现和架构能力。

尼恩说在前面

秒杀,是一个非常常见的高并发面试题,很多面试官也非常熟悉,上来就让面试者设计一个秒杀系统。

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格,遇到很多很重要的面试题:

分布式锁,如何实现高并发?

小伙伴 没有回答好,导致面试挂了。

小伙伴面试完了之后,来求助尼恩:如何才能回答得很漂亮,才能 让面试官刮目相看、口水直流。所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现 ”offer自由”

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典》V173版本PDF集群,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请到文末公号【技术自由圈】取。

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

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

  • 既要保证不发生超卖

  • 又要保证高并发

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

首先说说什么是分布式锁,普通分布式锁的不足

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

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

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

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

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

什么是分布式锁?

  • 当在分布式模型下,数据只有一份(或有限制),此时需要利用锁的技术控制某一时刻修改数据的进程数。
  • 用一个状态值表示锁,对锁的占用和释放通过状态值来标识。

分布式锁的条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 具有容错性。只要大部分的 Redis 节点正常运行,客户端就可以加锁和解锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

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

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

  1. 数据库悲观锁、
  2. 数据库乐观锁;
  3. 基于Redis的分布式锁;
  4. 基于ZooKeeper的分布式锁。

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

尼恩提示:基于ZooKeeper的分布式锁,请参见尼恩面试宝典 专题15.

尼恩提示:基于redis 的分布式锁,请参见尼恩面试宝典 专题15.

在高并发场景下,如秒杀系统中,防止库存超卖和超买是关键题之。使用 Redis 实现分段锁种有效的解决方案。该机制可以将商品库存划分为多个“段”,每个段独立加锁,从而减少锁竞争、提升并发性能。 ### 分段锁的实现原理 Redis 分段锁的核心思想是将个大的资源(如商品库存)拆分成多个小的子资源(段),每个子资源拥有自己的锁。这样,在并发访时,不同线程可以操作不同的段而互不干扰,从而提高系统的吞吐量。 例如,如果某商品总库存为 1000,可以将其划分为 10 段,每段 100 个库存。每次请求根据某种规则(如用户 ID 或订单 ID 的哈希值取模)决定操作哪段,然后对该段加锁进行库存扣减操作。 ### 数据结构设计 Redis 中可以通过 Hash 或 String 类型来存储库存信息,同时使用 SETNX 命令或 Redlock 算法来实现分布式锁。以下是个简单的数据结构示例: ```text SECKILL:SKU_1001:SEGMENTS = { "segment_0": 100, "segment_1": 100, ... "segment_9": 100 } ``` ### 锁机制与流程 1. **计算段索引**:通过用户 ID 或订单 ID 的哈希值对段数取模,确定要操作的库存段。 2. **获取段锁**:使用 `SET key value NX PX timeout` 命令尝试获取该段的锁。若成功,则继续;若失败,则等待或重试。 3. **检查并更新库存**:在持有锁期间,读取该段库存,判断是否足够。若足够,则执行扣减操作,并更新库存;否则,返回库存不足提示。 4. **释放段锁**:操作完成后,删除对应的锁 key,释放锁资源。 ### 示例代码 以下是个伪代码示例,展示如何使用 Redis 实现分段锁: ```python def deduct_stock(user_id, sku_id, quantity): segments_count = 10 segment_index = hash(user_id) % segments_count lock_key = f"LOCK:{sku_id}:SEGMENT_{segment_index}" stock_key = f"STOCK:{sku_id}:SEGMENTS" # 尝试获取锁 acquired = redis.set(lock_key, "locked", nx=True, ex=10) if not acquired: return "获取锁失败,请重试" try: # 获取当前段库存 current_stock = int(redis.hget(stock_key, f"segment_{segment_index}")) if current_stock < quantity: return "库存不足" # 扣减库存 redis.hincrby(stock_key, f"segment_{segment_index}", -quantity) return "库存扣减成功" finally: # 释放锁 redis.delete(lock_key) ``` ### 优势与注意事项 - **优势**: - 提高并发性能:由于每个段独立加锁,减少了线程之间的锁竞争。 - 防止超卖和超买:通过精确控制每个段的库存变更,确保最终库存不会出现负值。 - **注意事项**: - **段划分策略**:需要合理选择段的数量,避免段过多导致管理复杂度上升,或段过少无法有效缓解锁竞争。 - **锁续期机制**:在业务逻辑执行时间较长的情况下,建议引入类似 Redisson 的 Watch Dog 机制,自动延长锁的有效期[^2]。 - **容错处理**:在网络不稳定或 Redis 异常的情况下,需有重试机制和降级策略,确保系统可用性。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值