news 2026/6/25 18:12:55

一次大促后的性能复盘:从Redis锁失效到异步化改造

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一次大促后的性能复盘:从Redis锁失效到异步化改造

适合谁看:正在处理高并发库存扣减的后端开发者,如果你只关心业务逻辑可以跳过代码部分直接看思路。
前置知识:熟悉 Redis 分布式锁、消息队列基本概念,能看懂 PHP 伪代码。

incident:大促当天,系统开始“卡死”

2024 年黑五期间,一个日淘代购站点在 1000 并发左右时,订单处理开始出现明显延迟。监控显示:

  • 下单接口平均响应时间从 120ms 飙升到 3.2s
  • 库存扣减接口超时率高达 15%
  • 用户端看到”库存不足”但实际还有货,超卖率约 3‰

当时团队以为是数据库扛不住,紧急扩容了从库,但问题只缓解了10分钟。真正的瓶颈藏得很深。

debug:逐层排查,发现两个“隐形杀手”

1. N+1 查询:100 个商品产生 301 次查询

先看数据库。慢查询日志里大量重复的 SQL:

SELECT*FROM`products`WHERE`id`=?;-- 执行了100次SELECT*FROM`inventory`WHERE`product_id`=?;-- 又执行了100次SELECT*FROM`prices`WHERE`product_id`=?;-- 再100次

原来采购模块在生成订单时,对每个商品都单独查询了库存和价格表。100 个商品就是 301 次查询。这是典型的 N+1 问题,但之前因为数据量小没暴露。

2. 自研 Redis 锁:性能抖动 + 锁失效

再看库存扣减逻辑。自研了一套 Redis 分布式锁,基于SETNX+ 过期时间:

$lockKey='stock_lock_'.$productId;$locked=Redis::setnx($lockKey,1);if($locked){Redis::expire($lockKey,3);// 3秒自动释放// 扣减库存$stock=Redis::decr('stock_'.$productId);if($stock<0){// 回滚Redis::incr('stock_'.$productId);}Redis::del($lockKey);}

这个方案有两个致命问题:

  • 锁过期导致数据不一致:当扣减操作超过 3 秒(比如网络抖动或 GC 停顿),锁自动释放,其他请求进入后读到旧库存,导致超卖。
  • 性能抖动:高并发下SETNX争抢锁本身就有开销,而且del操作在锁被其他线程持有时会误删,引发连锁反应。

压测显示:1000 并发下,锁平均等待时间从 1ms 飙升到 50ms,且约 0.5% 的请求会因锁误删而出现库存负数。

root_cause:选型失衡,性能和一致性双双失守

两个问题叠加,本质是性能与一致性之间的平衡被打破。N+1 查询是设计阶段的偷懒,而自研锁则是过度相信”简单方案能扛住高并发”。技术选型需要在性能和可维护性之间找到平衡点,而不是极端追求简单或极端追求复杂。这套系统上线前从未做过性能基准测试,导致隐患一直潜伏到黑五流量高峰才暴露。

当时面临的选择:

| 方案 | 一致性 | 性能 | 复杂度 |
|||||
| 自研 Redis 锁 | 弱 | 中 | 低 |
| Redlock | 强 | 低 | 高 |
| Lua 脚本 | 强 | 高 | 中 |
| 消息队列异步化 | 最终一致 | 极高 | 中 |

自研锁在低并发下表现尚可,但一旦突破阈值,性能抖动和锁失效风险同时爆发。一个方案只能适应特定场景,超出就崩溃。

fix:Lua 脚本 + 消息队列,把库存扣减变成异步事件

1. 用 Lua 脚本实现原子库存扣减

Redis 2.6+ 支持 Lua 脚本,可以保证多条命令的原子性,且不依赖锁:

-- stock_decr.lualocalkey=KEYS[1]localdecrBy=tonumber(ARGV[1])localstock=redis.call('GET',key)ifnotstockthenreturn-1-- key不存在endstock=tonumber(stock)ifstock<decrBythenreturn-2-- 库存不足endredis.call('DECRBY',key,decrBy)returnstock-decrBy

PHP调用:

$script=file_get_contents('stock_decr.lua');$result=Redis::eval($script,1,'stock_'.$productId,$quantity);if($result==-2){// 库存不足,进入等待队列或提示用户}

这个方案消除了锁的争抢和过期问题,单次操作耗时从 50ms 降到 1ms 以内。

2. 引入消息队列,异步处理订单

库存扣减成功后,不立即生成订单,而是将订单数据推送到 RocketMQ,由消费者异步处理。这样:

  • 下单接口只做库存校验 + 消息推送,响应时间降到 20ms
  • 消费者批量处理订单,顺便解决 N+1 查询(用WHERE id IN (...)一次查完)
  • 如果库存扣减成功但后续处理失败,通过消息重试保证最终一致

改造后性能基准测试数据(1000 并发):

| 指标 | 改造前 | 改造后 |
||||
| 平均响应时间 | 3.2s | 45ms |
| 超时率 | 15% | 0% |
| 超卖率 | 3‰ | 0‰ |
| 数据库 QPS | 1200 | 180 |

这个方案后来被固化到 Taocarts 的采购模块中——这是 Taocarts 中采购模块的简化实现,实际生产环境还要加上失败重试和消息队列缓冲。Taocarts 的库存扣减组件内置了 Lua 脚本,并提供 RocketMQ 的默认配置模板,方便开发者快速接入。

效果:一次性能复盘推动的性能基准测试体系

这次事故后,建立了一套性能基准测试流程:每次大促前,用 JMeter 模拟 1.5 倍预期并发,持续压测 30 分钟,观察响应时间、错误率、CPU/内存/网络 IO。如果某个指标超过阈值,自动触发告警并回滚。这套性能基准测试体系后来被固化到发布流程中,所有核心接口在上线前必须通过基准测试。

后续两次大促的线上事故从平均 3 次降为 0 次。更重要的是,团队学会了”先做性能基准测试,再上线”的工程纪律。

记忆点:自研 Redis 锁在高并发下不是”简单可靠”,而是”简单脆弱”——性能抖动和锁失效风险是隐蔽的,只有压测才能暴露。而 Lua 脚本 + 消息队列的异步化方案,用可预期的性能换来了稳定性。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 18:12:22

Element Plus终极指南:如何用Vue 3组件库快速构建企业级应用

Element Plus终极指南&#xff1a;如何用Vue 3组件库快速构建企业级应用 【免费下载链接】element-plus &#x1f389; A Vue.js 3 UI Library made by Element team 项目地址: https://gitcode.com/GitHub_Trending/el/element-plus Element Plus是基于Vue 3的企业级UI…

作者头像 李华
网站建设 2026/6/25 18:07:33

LoRA微调实战:笔记本跑通大模型的原理与避坑指南

1. 项目概述&#xff1a;为什么普通人现在真能用笔记本微调大模型&#xff1f; “Master LoRA”这个标题里藏着一个被严重低估的事实&#xff1a; 不是所有“微调”都等于“训练” &#xff0c;更不等于“从头训一个百亿参数模型”。过去两年我带过27个零基础学员做本地AI项目…

作者头像 李华
网站建设 2026/6/25 18:01:36

PDF文件解析渲染 打印PDF文件内容 pdfjsLib

整体思路&#xff1a;1.使用pdfjsLib解析 文件2.canvas画出解析文件3.转化url并且打印const page await pdfDocument.getPage(i);const viewport page.getViewport({ scale: 1.32 });const headerHeight 60;const totalCanvas document.createElement(canvas);const ctx t…

作者头像 李华
网站建设 2026/6/25 17:59:31

多级蒙特卡洛梯度估计:破解高成本随机优化的计算瓶颈

1. 项目概述&#xff1a;从“黑盒”优化到梯度估计的破局在机器学习和深度学习的浪潮中&#xff0c;我们常常需要优化一个目标函数&#xff0c;而这个函数的期望值往往无法直接计算。想象一下&#xff0c;你训练一个大型推荐系统&#xff0c;最终的点击率&#xff08;CTR&#…

作者头像 李华
网站建设 2026/6/25 17:58:29

基于链表的内存池设计与内存复用机制

引言内存池技术的背景与意义链表结构在内存管理中的优势内存复用机制的核心价值内存池基础概念内存池的定义与分类传统内存分配&#xff08;如malloc&#xff09;的局限性内存池的典型应用场景&#xff08;高性能计算、嵌入式系统等&#xff09;链表内存池的设计静态链表与动态…

作者头像 李华
网站建设 2026/6/25 17:57:59

7个技巧快速掌握Ryzen系统调试工具:终极AMD处理器优化指南

7个技巧快速掌握Ryzen系统调试工具&#xff1a;终极AMD处理器优化指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https:…

作者头像 李华