知识卡片
用长度受限的队列同时控制高频库存查询与抢购名额发放
内容
抢购系统里最高频的请求是”查询商品在本次抢购中是否还有库存”,如果每次查询都直接读写抢购库里的库存计数器,高并发下极容易出现竞态问题。解法是为每个商品单独构建一个长度受限的cache结构(可以用Redis的List真正实现一个队列),队列的长度上限就等于这次抢购放出的商品数量(比如商品A配了20件,队列长度上限就是20)——当用户请求到来时,业务代码检查这个队列当前的长度:如果队列长度还没到上限,用户就有资格排进队尾等待,一旦排到队头就获得抢购名额,此时才真正去对抢购库的库存做递减操作;如果队列已经排满(长度达到20),后续所有请求可以直接判定商品已被抢空、快速返回,不需要再去尝试竞争库存。这个设计巧妙地把”库存还有没有”这个查询问题,转化成了”这个队列还有没有空位”这个更容易高并发处理的问题——队列本身的长度检查和入队操作是原子且轻量的,不涉及复杂的库存计算逻辑,真正的库存递减动作被推迟到用户已经确定获得名额之后才执行,从而把高并发场景下最容易出问题的”库存竞争”环节,收窄成了一个更简单、更容易保证正确性的”排队入列”操作。这个思路对任何”数量有限的资源在高并发下被抢购”的场景(不限于电商,比如抢票、抢名额报名)都有参考价值:把资源的可用性检查转化为一个天然支持高并发、语义清晰的队列结构,比直接对着一个共享计数器做高并发的读改写更容易保证正确性。
参考来源
- 位置:《高可用架构(第1卷)》第3章《电商架构热点专题》"3.3 秒杀系统架构解密与防刷设计"节,"3.3.4 如何保证商品库的库存可靠"(源文件:_epub-src/OEBPS/Text/Chapter3_3_5.xhtml)
- 结论依据:原文说明"我们构建商品维度的cache……假设商品A,运营配了20件,此事来了N多用户的请求,业务代码都会来查询cache_prefix_a_id这个队列的长度,若队列长度≤0,则有权去……抢购库的商品库存。若队列长度在20件内,则通过业务代码内的等待来等待队头的位置,然后获得抢购权限。若队列长度太长,则可以直接返回,认为商品已被抢空",直接支撑本卡片结论。
- 原始内容:我们构建商品维度的cache……假设商品A,运营配了20件,此事来了N多用户的请求,业务代码都会来查询cache_prefix_a_id这个队列的长度……若队列长度在20件内,则通过业务代码内的等待来等待队头的位置,然后获得抢购权限。若队列长度太长,则可以直接返回,认为商品已被抢空。