库存扣减热key优化

文章讨论了在高并发场景下,如何通过在本地缓存设置库存标识和分片机制,减少对Redis的直接操作,以提高系统健壮性,优化库存扣减流程。流程涉及读取标识、检查分片库存并递归处理直到找到可用库存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:

        某个需求扣减库存时,原有的逻辑是直接使用redis的自增,redis的数据格式为hash,所有奖品的库存,会集中到单个redis的key上面去处理,下单高峰的时候,会有热key的情况出现,为了提高系统的健壮性,有必要优化查询和扣减库存的方式。

基本思路

                                                                思路图

                                                                具体流程图

流程说明:

        本地缓存设置一个标识:结构是Map,key包括:stock(0-总库存耗尽),以及每个分片的库存情况,比如redis1=0(表示分片1无库存),redis2=1(表示分片2有库存) 。。。。依此类推。

上面的分片数10是举例,实际上以redis的分片数代替

        1. 扣库存前,读取库存标识stock,按照以下步骤:

步骤1,如果有标识stock=0,则直接返回无库存,如果无标识stock,先取本地缓存查看redis(n)是否有库存,

        1.1,如果本地缓存redis(n)为0,则直接进入步骤2,

        1.2,如果本地缓存redis(n)为1或者为空,调用redis分片n进行计数+1,判断数量是否大于分片库存:

                1.2.1,如果不大于,直接返回扣库存成功,

                1.2.2,如果大于,在本地缓存标识分片n无库存,同时进入步骤2;

步骤2,按顺序读取分片n+1的库存,先取本地缓存查看redis(n+1)是否有库存,

        2.1, 如果本地缓存redis(n+1)为0,则直接再重新进入步骤2,

        2.2,如果本地缓存redis(n+1)为1或者为空, 则分片n+1计数加1,判断数量是否大于分片n+1库存:

                2.2.1,如果不大于,直接返回扣库存成功,并在本地缓存标识分片n+1有库存,

                2.2.2,如果大于,在本地缓存标识分片n+1无库存,继续循环步骤2,直到最后一个分片;

步骤3,如果读到最后一个分片库存还是0,设置stock=0,返回无库存

### 关于资源管理和库存扣减的实现方法 #### Redis 实现高并发下的库存扣减 在高并发场景下,传统的数据库操作可能无法满足性能需求。因此,可以考虑使用 Redis优化库存扣减的过程。Redis 提供了原子性的命令 `DECRBY` 或者 `INCRBY`,能够安全地执行加减操作而无需担心线程竞争问题。 以下是基于 Redis 的具体实现逻辑: 1. **初始化库存** 将初始库存值写入 Redis 中的一个键值对中,例如设置商品 ID 对应的库存数量。 ```bash SET product_id_001 100 ``` 2. **扣减库存** 当有订单请求时,通过 `DECRBY` 命令减少指定的商品库存。如果返回的结果小于零,则表示库存不足,拒绝该次购买行为。 ```python import redis r = redis.Redis(host='localhost', port=6379, db=0) def deduct_stock(product_id, quantity): stock_key = f"product:{product_id}:stock" current_stock = int(r.get(stock_key)) if r.exists(stock_key) else None if not current_stock or current_stock < quantity: return False # 库存不足 result = r.decrby(stock_key, quantity) return True if result >= 0 else False ``` 3. **处理异常情况** 如果由于网络延迟等原因导致多次重复调用接口尝试扣减同一笔交易的库存,在实际应用中还需要引入分布式锁机制或者幂等性校验来防止超卖现象发生[^4]。 #### 数据库层面的解决方案 除了利用缓存服务外,也可以直接依赖关系型数据库完成这一功能。下面列举了几种常见的做法及其优缺点分析: - **悲观锁** - 在 SQL 查询语句后面加上 `FOR UPDATE` 子句锁定目标行直到事务提交为止,从而阻止其他会话修改这些数据项。 ```sql BEGIN; SELECT stock FROM products WHERE id = ? FOR UPDATE; IF (stock >= required_amount) THEN UPDATE products SET stock = stock - required_amount WHERE id = ?; END IF; COMMIT; ``` - 缺点在于当大量客户端同时访问相同的数据集时会造成阻塞效应降低整体吞吐率[^3]。 - **乐观锁** - 添加版本号列 version 到表结构里,并且每次更新前先比较当前记录的实际版本是否匹配预期值;如果不一致则重新读取最新状态再做判断。 ```java @Version private Integer version; public boolean reduceStock(Long productId,Integer amount){ Product p = entityManager.find(Product.class,productId); if(p.getStock()>=amount && p.getVersion()==expectedVersion){ p.setStock(p.getStock()-amount); p.setVersion(expectedVersion+1); entityManager.merge(p); return true; }else{ return false; // either insufficient stock or stale data detected. } } ``` - 这样即使多个进程几乎同步发起请求也不会互相干扰,不过仍需注意死循环重试可能导致额外负载增加的情况出现。 #### 综合对比与推荐策略 对于简单的应用场景可以选择单机版 MySQL 结合适当索引来应对中小规模的需求即可胜任工作负荷。然而面对极端条件下的海量请求压力测试表明采用内存级 NoSQL 技术如 Redis 显著优于传统的关系型数据库产品[^2]。此外构建专门服务于此类特定用途的服务模块也有助于提升系统的可维护性和扩展能力。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值