当前位置: 首页 > article >正文

【黑马点评日记】Redis+Lua+异步队列:高并发秒杀系统优化方案

个人主页北极的代码欢迎来访作者简介java后端学习者❄️个人专栏苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言我们之前学的Lua脚本把库存扣减和用户去重都放在 Redis 里原子执行。这一步确实挡住了无效请求卖完/重复也解决了大部分的秒杀问题但是还有一些值得优化的地方。摘要本文探讨秒杀系统从同步处理到异步架构的演进过程。传统同步模式Lua脚本即时写库面临三大痛点数据库瞬时压力导致宕机风险、用户响应延迟、系统耦合度高。异步模式通过RedisLua脚本快速完成资格校验后立即将请求转入消息队列实现三大优势数据库写入峰值从5000QPS降至稳定500QPS用户响应时间从秒级降至毫秒级业务逻辑解耦为独立模块。技术实现采用双线程模型Tomcat线程生产者专注快速校验2ms完成消费者线程异步处理耗时操作50-100ms。关键代码包括Redis库存预热、原子性Lua脚本校验、阻塞队列实现任务缓冲。这种架构使系统吞吐量提升20倍200线程支持10万QPS同时通过削峰填谷保护数据库体现了快速响应、异步消化的设计哲学。问题分析只靠 Redis Lua系统还面临什么问题java// 1. 在 Redis 里 Lua 脚本执行成功扣了库存记录了用户 // 2. 返回成功然后你需要执行 // - 创建订单对象 // - 写数据库订单表、库存表、活动记录表... // - 调用积分接口如果有 // - 发送通知 // ...问题来了假设 1 秒内有5000 个请求成功。那服务器就要在1 秒内同步执行 5000 次“创建订单 写库 调用其他服务”。数据库每秒能承受 5000 次写入吗大概率不能数据库会卡死甚至宕机。用户的 HTTP 连接会一直等待你做完这些事情才返回响应时间从几毫秒变成了几秒。这时我们才会发现Lua 脚本只是守住了仓门但仓库里处理单据的柜台数据库被挤爆了。异步秒杀的核心目标异步秒杀就是要解决上面这个“后续处理导致的系统崩溃和响应慢”问题。它的做法是Lua 脚本验证成功扣完 Redis 库存后立即给用户返回一个“排队中请稍候”或“抢购成功处理中”。耗时仅几毫秒HTTP 连接立刻释放用户前端开始转圈等待。系统把“谁买了什么”这条消息丢进一个消息队列。后端一个或几个消费者服务从消息队列里慢慢取消息逐一创建订单、写数据库。等订单创建好了再通过 WebSocket 或轮询通知用户“您抢购成功啦”。异步秒杀额外解决了什么问题问题同步模式Lua脚本 即时写库异步模式Lua脚本 MQ 异步写库数据库压力瞬时并发写入直接把库打崩。削峰填谷变成了稳定的低速写入保护数据库。系统响应用户等 2-5 秒才返回结果体验差。用户几十毫秒就收到“排队中”体验好得多。系统耦合下单逻辑、扣库存、积分等代码耦合在一起。秒杀结束立即返回后续逻辑解耦各自独立处理。容错性如果下单时积分服务挂了秒杀直接失败。积分服务故障只影响后续处理可以重试或补偿不影响秒杀资格判定。Lua 脚本解决这个东西能不能卖和卖给谁的问题。核心是防超卖、防重异步消息队列解决“卖成功了之后怎么慢慢处理后续复杂业务同时又不把系统干崩”的问题。核心是削峰、解耦、自保一句话如果并发量很低Lua 脚本扣完库存直接写库没问题。但到了秒杀这种量级异步处理不是为了让程序能更正确地卖。异步模式正确做法text5 万个请求/秒 │ ▼ ┌─────────────────────────────────────────┐ │ Tomcat 线程池200 个 │ │ ┌─────────────────────────────────────┐ │ │ │ 每个线程只做 │ │ │ │ 1. Redis 操作用 1ms │ │ │ │ 2. 发送 MQ 消息用 1ms │ │ │ │ 3. 返回结果 │ │ │ │ │ │ │ │ 一个线程 1 秒能处理 500 个请求 │ │ │ │ 200 个线程 1 秒能处理 10 万个请求 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘ │ ▼ 所有请求都能处理100ms 内返回排队中 │ ▼ ┌─────────────────────────────────────────┐ │ 消息队列 │ │ ┌─────────────────────────────────────┐ │ │ │ 消息排队慢慢消费 │ │ │ │ 消费者以 500 QPS 的速度写数据库 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────┘第一步Tomcat 线程处理请求只做轻量级工作text用户请求 ↓ Tomcat 线程从线程池中取一个 ↓ 执行 Lua 脚本扣 Redis 库存、校验资格 ← 耗时 1-2 毫秒 ↓ 如果成功发送一条消息到消息队列 ← 耗时 1 毫秒 ↓ 立刻返回给用户“您已排队请稍后查询结果” ↓ 【这个 Tomcat 线程就此结束回到线程池去处理下一个请求】关键点Tomcat 线程全程没碰数据库总耗时只有 3 毫秒左右。它和“后续处理”完全没有关系了。第二步独立线程消费者处理重任务与 Tomcat 线程完全无关text消息队列里的一条消息内容是用户 1001 秒杀了商品 2001 ↓ 消费者线程池独立于 Tomcat取出这条消息 ↓ 执行 - 创建订单写数据库 ← 耗时 30 毫秒 - 扣减数据库库存 ← 耗时 10 毫秒 - 调用积分接口 ← 耗时 20 毫秒 ↓ 处理完成。如果需要通知用户再发一条消息或 WebSocket 推送。关键点这个线程和之前那个 Tomcat 线程不是同一个它们同时运行互不干扰。对比串联和异步两种模式模式流程Tomcat 线程耗时能抗的并发串联同步Redis → 订单 → 库存 → 积分 → 返回100 毫秒很低分开异步Redis → 发 MQ → 返回Tomcat 结束独立线程MQ → 订单 → 库存3 毫秒很高因为这是异步架构的核心概念Tomcat 线程是珍贵的、有限的一般 200 个。不能让它们去等数据库 I/O那样 200 个线程瞬间用完后续请求只能排队或超时。必须把慢操作交给别的线程让 Tomcat 线程只做快速操作Redis、发送消息然后立刻归还线程池。“独立线程”就是指不属于 Tomcat 线程池的那些线程。总之Redis 和 Tomcat 不是“分开执行”而是 Tomcat 线程调用 Redis 快速处理后就把任务交给另一个独立线程如 MQ 消费者去完成自己立刻返回去处理下一个请求。Tomcat 线程不再参与后续的数据库等慢操作。代码实现1.新增优惠卷时把信息保存到 Redis中Override Transactional public void addSeckillVoucher(Voucher voucher) { // 保存优惠券 save(voucher); // 保存秒杀信息 SeckillVoucher seckillVoucher new SeckillVoucher(); seckillVoucher.setVoucherId(voucher.getId()); seckillVoucher.setStock(voucher.getStock()); seckillVoucher.setBeginTime(voucher.getBeginTime()); seckillVoucher.setEndTime(voucher.getEndTime()); seckillVoucherService.save(seckillVoucher); //保存秒杀信息到Redis stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEYvoucher.getId(), voucher.getStock().toString());2.判断秒杀库存一人一单用 Lua 脚本是因为它既能保证检查库存 扣库存 记录用户这三步的原子性又能做条件判断库存不足就返回失败而且性能极高不会让线程阻塞等待。Redis 事务做不了条件判断分布式锁性能差且会阻塞。Lua 脚本是秒杀场景下唯一正确的选择。-- 1. 参数列表 -- 1.1. 优惠券id local voucherId ARGV[1] -- 1.2. 用户id local userId ARGV[2] -- 2. 数据key -- 2.1. 库存key local stockKey seckill:stock: .. voucherId -- 2.2. 订单key修正原代码写成了stockKey local orderKey seckill:order: .. voucherId -- 3. 脚本业务 -- 3.1. 判断库存是否充足 local stock redis.call(get, stockKey) if (tonumber(stock) 0) then -- 库存不足返回1 return 1 end -- 3.2. 判断用户是否已经下单 local isMember redis.call(sismember, orderKey, userId) if (isMember 1) then -- 用户已下单返回2 return 2 end -- 3.3. 扣减库存 redis.call(incrby, stockKey, -1) -- 3.4. 记录用户下单 redis.call(sadd, orderKey, userId) -- 3.5. 秒杀成功返回0 return 03.抢购成功信息加入阻塞队列中抢单流程判断以及执行lua脚本private static final DefaultRedisScriptLong SECKILL_SCRIPT ; //静态代码块进行初始化 static { SECKILL_SCRIPT new DefaultRedisScript(); SECKILL_SCRIPT.setLocation(new ClassPathResource(seckill.lua)); SECKILL_SCRIPT.setResultType(Long.class); } /** * 下单秒杀优惠卷 * param voucherId 优惠券id * return */ public Result seckillVoucher(Long voucherId){ //获取用户id Long userId UserHolder.getUser().getId(); //1.执行lua脚本 Long resultstringRedisTemplate.execute( SECKILL_SCRIPT , Collections.emptyList() , voucherId.toString(),userId.toString() ); //判断结果是否为0 int rresult.intValue(); //不为0则代表没有购买资格 if (r!0){ return Result.fail(r1?库存不足:不能重复下单); }基于阻塞队列实现异步下单阻塞队列是 Java 并发包提供的线程安全的队列可以用来实现简单的异步处理不依赖外部消息队列如 RabbitMQ、RocketMQ。Component Slf4j public class SeckillConsumer { Autowired private OrderService orderService; Autowired private SeckillService seckillService; // 获取阻塞队列需要暴露 // 方式一使用 PostConstruct 启动消费者线程 PostConstruct public void startConsumer() { // 启动一个独立的消费者线程 Thread consumerThread new Thread(() - { log.info(秒杀订单消费者线程启动); while (true) { try { // 从阻塞队列中取订单如果没有订单会阻塞 SeckillService.SeckillOrder order seckillService.getOrderQueue().take(); // 处理订单创建订单、扣数据库库存 processOrder(order); } catch (InterruptedException e) { log.error(消费者线程被中断, e); Thread.currentThread().interrupt(); break; } catch (Exception e) { log.error(处理订单失败, e); // 记录失败订单待人工处理 } } }); consumerThread.setName(seckill-consumer-thread); consumerThread.setDaemon(true); // 设置为守护线程 consumerThread.start(); } /** * 处理订单在独立线程中执行 */ private void processOrder(SeckillService.SeckillOrder order) { log.info(开始处理订单userId{}, voucherId{}, order.getUserId(), order.getVoucherId()); // 1. 创建订单写数据库 orderService.createOrder(order.getUserId(), order.getVoucherId()); // 2. 扣减数据库库存 boolean success orderService.deductStock(order.getVoucherId()); if (!success) { log.error(数据库库存扣减失败voucherId{}, order.getVoucherId()); // 这里可以发送告警进行补偿 } // 3. 可选发送通知WebSocket、短信等 notifyUser(order.getUserId(), 秒杀成功订单已创建); log.info(订单处理完成userId{}, voucherId{}, order.getUserId(), order.getVoucherId()); } /** * 模拟通知用户 */ private void notifyUser(Long userId, String message) { // 实际可以用 WebSocket、MQ、短信等 log.info(通知用户{}{}, userId, message); } }角色线程做什么耗时生产者Tomcat 线程Lua 脚本校验成功 → 把订单信息放入队列 → 立即返回排队中1-2 毫秒消费者独立的后台线程从队列取出订单 → 创建订单写数据库 → 扣库存50-100 毫秒关键生产者和消费者是不同线程互不阻塞。用户请求 → Lua 脚本1ms → 放入阻塞队列1ms ← 用户只等 2ms → 返回排队中 独立线程阻塞队列.take() → 创建订单50ms → 扣库存30ms → 完成后可选通知用户生产者用offer()非阻塞放入消费者用take()阻塞获取队列满时立即回滚 Redis 并降级服务关闭前等待队列清空。时间线图text项目启动 │ ├─ Tomcat 线程池启动等待请求 ├─ 消费者线程启动执行 orderQueue.take()进入阻塞状态 │ │ 此时队列为空消费者线程在睡觉不占用 CPU │ ▼ 第 1 秒用户 A 请求来了 │ │ Tomcat 线程 │ 1. 执行 Lua 脚本成功 │ 2. orderQueue.offer(订单A) ← 放入队列 │ 3. 返回排队中 │ │ 消费者线程被唤醒 │ 1. take() 拿到订单A │ 2. 创建订单写数据库 │ ▼ 第 2 秒用户 B 请求来了 │ │ Tomcat 线程放入订单B │ 消费者线程处理订单B如果订单A已处理完 │ ▼ 一直运行...注意生产者和消费者是同时存在的只是大部分时间消费者在阻塞没有订单时订单来了就被唤醒。形象比喻角色比喻说明Tomcat 线程餐厅前台收银员客人来了收钱、开单、把单子放到待做菜挂钩上然后立刻招呼下一个客人阻塞队列待做菜挂钩挂订单的地方消费者线程后厨厨师厨师一直在后厨看着挂钩有单子就拿去做菜慢做完再拿下一单结语如果对你有帮助请点赞关注收藏你的支持就是我最大的鼓励

相关文章:

【黑马点评日记】Redis+Lua+异步队列:高并发秒杀系统优化方案

🔥个人主页:北极的代码(欢迎来访) 🎬作者简介:java后端学习者 ❄️个人专栏:苍穹外卖日记,SSM框架深入,JavaWeb ✨命运的结局尽可永在,不屈的挑战却不可须臾或…...

Steam成就管理神器:3步掌握开源成就解锁工具终极指南

Steam成就管理神器:3步掌握开源成就解锁工具终极指南 【免费下载链接】SteamAchievementManager A manager for game achievements in Steam. 项目地址: https://gitcode.com/gh_mirrors/st/SteamAchievementManager Steam Achievement Manager(简…...

linux 音频

Linux 音频核心是ALSA(内核硬件层) PulseAudio/PipeWire(用户空间混音 / 路由) JACK(专业低延迟),当前主流发行版已全面转向 PipeWire。Linux 音频架构 1. 内核层:ALSA(A…...

强化学习工具规划与GRPO算法实践指南

1. 强化学习中的工具规划概述在强化学习领域,工具规划(Tool Planning)正逐渐成为解决复杂决策问题的新范式。这个概念源于对人类使用工具完成复杂任务行为的模拟——就像木匠会根据不同工序选择锤子、锯子或刨子一样,智能体也需要…...

M5Stack热成像模块开发与应用指南

1. M5Stack Thermal Camera 2 Unit 热成像模块深度解析作为一名长期从事嵌入式开发的工程师,我最近测试了M5Stack推出的Thermal Camera 2 Unit热成像模块。这款产品将ESP32芯片与MLX90640红外传感器相结合,为开发者提供了一个高性价比的热成像解决方案。…...

ISO-27145实战避坑指南:搞懂OBD诊断中的单帧、首帧与流控帧(ISO15765-2解析)

ISO-27145实战避坑指南:搞懂OBD诊断中的单帧、首帧与流控帧(ISO15765-2解析) 在汽车电子诊断领域,ISO-27145标准已经成为排放相关诊断的黄金准则。然而,许多开发者在实际应用中,尤其是处理多包数据传输时&a…...

别再搞错了!Xilinx 7系列FPGA的LVDS bank电压,HR用2.5V还是HP用1.8V?一次讲清

Xilinx 7系列FPGA的LVDS接口设计:HR与HP Bank电压配置全解析 在Xilinx 7系列FPGA(包括Kintex-7和Virtex-7)的设计中,LVDS接口的电压配置一直是硬件工程师容易混淆的关键点。我曾亲眼见过一个团队因为bank电压配置错误,…...

告别网络调试助手:用ESP-01S和51单片机实现一个简易的远程LED控制器

从零打造智能灯控:ESP-01S与51单片机的无线魔法 1. 项目构思与核心价值 想象一下,躺在沙发上用手机控制书桌上的台灯,或者在公司远程查看家中植物补光灯的状态——这种智能家居的便捷体验,其实用不到昂贵的商业解决方案。借助ESP-…...

Qt布局中的‘弹簧’与‘边距’:用QSpacer和Margin/Padding解决控件对齐与间距的5个典型问题

Qt布局中的‘弹簧’与‘边距’:用QSpacer和Margin/Padding解决控件对齐与间距的5个典型问题 在Qt界面开发中,布局管理是构建优雅用户界面的核心技能。当开发者掌握了基础的QHBoxLayout、QVBoxLayout等布局容器后,往往会遇到更精细的控件排列需…...

脑机接口爆发:测试工程师的黄金机遇

当前,脑机接口(BCI)技术正从实验室走向产业化,市场规模呈指数级增长。据权威预测,中国脑机接口市场规模将从2024年的32亿元跃升至2030年的120亿元(资料5/6)。这一爆发式增长背后,是国…...

揭秘Ryujinx:用C构建的高性能Nintendo Switch模拟器架构深度解析

揭秘Ryujinx:用C#构建的高性能Nintendo Switch模拟器架构深度解析 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx 您是否曾好奇,如何在PC上精确模拟一台游戏主机…...

IBR215 Pico-ITX SBC工业边缘计算解析

1. IBR215 Pico-ITX SBC核心架构解析这款来自IBASE的IBR215单板计算机采用了NXP i.MX 8M Plus这一专为边缘计算设计的SoC方案。这颗芯片的独特之处在于其异构计算架构:四核Cortex-A53处理常规运算任务,800MHz Cortex-M7实时核处理低延迟响应,…...

10分钟完成OpenCore EFI配置:OpCore Simplify图形化工具完整指南

10分钟完成OpenCore EFI配置:OpCore Simplify图形化工具完整指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为繁琐的黑苹果OpenC…...

蓝牙基础(十一):蓝牙耳机音频编码、传输流程、声音延迟与失真

MySQL 中的 count 三兄弟:效率大比拼! 一、快速结论(先看结论再看分析) 方式 作用 效率 一句话总结 count(*) 统计所有行数 最高 我是专业的!我为统计而生 count(1) 统计所有行数 同样高效 我是 count(*) 的马甲兄弟…...

别只盯着AC仿真了!用Cadence STB和Noise分析,搞定ClassAB输出运放的稳定性与噪声难题

别只盯着AC仿真了!用Cadence STB和Noise分析,搞定ClassAB输出运放的稳定性与噪声难题 ClassAB输出级运放因其高驱动能力和低静态功耗的优势,在模拟IC设计中占据重要地位。然而,许多工程师在设计这类运放时,往往陷入AC仿…...

C# WinForm上位机数据采集看板系统源码|IDC智能平台|支持模拟通信测试(含Modbus/115协议)

温馨提示:文末有联系方式项目概述 本平台是一款基于C# WinForm开发的工业级IDC数据采集与智能可视化看板系统,专为设备、产线数据汇聚及实时状态展示场景设计,具备高稳定性与强扩展性。核心特性 • 完整VS2019工程源码,结构清晰、…...

给SoC新手的保姆级指南:手把手教你理解APB3总线那些信号(附时序图详解)

给SoC新手的保姆级指南:手把手教你理解APB3总线那些信号(附时序图详解) 第一次接触APB3总线时,看着文档里密密麻麻的信号列表和复杂的时序图,是不是感觉头都大了?别担心,今天我们就用最接地气的…...

LaTeX进阶玩法:给你的自定义文档类(如myclass.cls)添加开关选项

LaTeX进阶玩法:给你的自定义文档类添加开关选项 当你已经掌握了LaTeX基础文档类的创建,下一步就是让它像标准类一样灵活可配置。想象一下,你的用户可以通过简单的\documentclass[nodate,twocolumn]{myclass}调用,就能激活各种排版…...

别再折腾虚拟机了!用Docker Desktop 5分钟在Windows上跑起Hadoop伪分布式环境

5分钟在Windows上搭建Hadoop伪分布式环境:Docker Desktop极简方案 还在为虚拟机安装CentOS、配置Hadoop而头疼?传统方式至少需要半天时间,从下载ISO镜像、创建虚拟机、安装操作系统,到配置Java环境、SSH免密登录,最后…...

别再为Electron webview通信发愁了!手把手教你用postMessage搞定双向传值(附React/Vue示例)

Electron webview通信实战:用postMessage构建高效双向通道 如果你正在Electron项目中集成第三方网页或独立模块,webview的通信问题一定让你头疼过。那种"看得见却摸不着"的隔离感,就像隔着玻璃对话——明明两个页面近在咫尺&#x…...

ArduPilot无人船调试实战:从PID到‘停止转向’,手把手教你调出丝滑航线

ArduPilot无人船调试实战:从PID到‘停止转向’,手把手教你调出丝滑航线 水面上的无人船突然急转,航点切换时像醉汉一样踉跄,转向响应慢半拍——这些"手感不佳"的问题,往往让刚入门的开发者陷入参数迷宫。本文…...

从Monster.com到LinkedIn:聊聊在线招聘20年变迁,给新人的求职效率提升指南

从Monster.com到LinkedIn:在线招聘20年进化史与求职效率革命 2003年,一位斯坦福毕业生在宿舍里创建了名为"领英"的社交平台,当时没人能预料到这个简单的校友网络工具会彻底改写全球职场规则。与此同时,曾经占据北美在线…...

暖心指南|3个真实案例复盘

行业痛点分析当前重庆心理卫生领域面临多重结构性挑战。据《2023年重庆市居民心理健康状况调查报告》显示,全市精神心理疾病患病率已达17.3%,但就诊率不足30%,存在显著的“诊断缺口”。其中,病耻感与认知偏差是主要屏障&#xff0…...

Python爬虫实战:手把手教你从东方财富网抓取期权期货数据(附完整代码)

Python金融数据抓取实战:从东方财富网获取期权期货数据的完整指南 在量化交易和金融数据分析领域,获取实时、准确的期权期货数据是构建交易策略的基础。本文将带你深入探索如何直接从东方财富网获取这些关键数据,不仅提供可立即运行的代码&am…...

除了PE覆盖文件,冰点还原Deep Freeze密码重置还有哪些‘野路子’?聊聊官方与民间的几种方法

冰点还原密码重置全攻略:从官方途径到高阶技巧解析 冰点还原精灵(Deep Freeze)作为企业级系统保护方案的标杆产品,其密码保护机制一直是IT管理员又爱又恨的存在。当那个熟悉的北极熊图标锁住了系统却遗失了管理密码时,…...

第47篇:使用Speech-to-Text API快速构建语音应用(操作教程)

文章目录前言环境准备分步操作步骤一:基础语音识别(同步)步骤二:处理长音频(异步)步骤三:实时语音识别(流式)完整代码与项目结构踩坑提示总结前言 在做一个智能客服项目…...

第46篇:语音识别入门——让AI“听懂”人类语言(概念入门)

文章目录背景引入:从“鸡同鸭讲”到“人机对话”核心概念:什么是语音识别?类比解释:像教一个外星人学中文简单示例:用Python和现成库快速体验小结背景引入:从“鸡同鸭讲”到“人机对话” 在我刚开始接触AI…...

SpringBoot项目里用Camunda 7.18搞流程审批?这份避坑指南和实战代码请收好

SpringBoot整合Camunda 7.18实战:企业级审批流开发避坑指南 当OA系统中的请假申请卡在部门经理环节三天无人处理,当报销流程因为角色权限混乱导致财务无法核销——这些场景暴露出传统硬编码审批逻辑的致命缺陷。本文将带你用Camunda工作流引擎重构审批系…...

Gemma 4工具调用:Python实现大语言模型自动化任务处理

1. 项目概述:Gemma 4工具调用的核心价值Gemma 4作为当前最先进的轻量级开源大语言模型,其工具调用能力正在改变开发者与AI系统的交互方式。不同于传统API调用,工具调用(Tool Calling)允许模型主动识别用户意图&#xf…...

数据中心RDMA网络实战:手把手教你配置PFC和ECN,搞定RoCEv2零丢包

数据中心RDMA网络实战:PFC与ECN配置全指南 当你在深夜盯着监控屏幕上突然跳红的RDMA性能指标时,那种焦虑感我深有体会。去年某金融客户的核心交易系统就曾因RoCEv2网络突发拥塞,导致NVMe over Fabrics存储延迟从200μs飙升到15ms——这足以让…...