Amdahl 定律——从哪来、为什么、有什么用
Amdahl 定律——从哪来、为什么、有什么用 钩子:你花大价钱把训练从 1 张卡扩到 8 张,满心期待快 8 倍,结果只快了 3 倍;给服务加了 16 个线程,吞吐量却卡在 4 倍上不去。问题往往不在你并行得不好,而在那段"怎么也并行不掉"的串行代码,给整体加速焊死了一个天花板。 这条天花板,就是 Amdahl 定律。 0. 一个反直觉的事实先抛个问题:一段程序,90% 能并行、10% 必须串行。给你无限多的核,它最快能快多少倍? 直觉可能是"无限倍"——毕竟 90% 都能并行嘛。但答案冷冰冰:最多 10 倍。因为那 10% 的串行部分,无论你堆多少核,它都得一个人慢慢走,而总耗时里永远含着这一段。串行占比,就是加速比的天花板。 1. 它从哪来:1967 年,一场对"并行狂热"的泼冷水1967 年,IBM 的计算机架构师 Gene Amdahl 在 AFIPS 会议上提出这条定律(原称 Amdahl's Argument),本意是给当时方兴未艾的并行机热潮"降降温"。那时候业界流行一种乐观...
叠浪镇的掌闸人
一、九道闸,把上游的"讯号"一级级送到磨坊叠浪镇傍着一道常年丰沛的瀑溪而建,镇上最了不起的营生,是用溪水带动下游九座磨坊。可溪水不能直接灌进磨坊——中间隔着一道叫「九折渠」的水利工程:溪水先涌入最高处的闸口,再经九道串联的闸室,一层层往下递,最后那股水的"强弱",正好对应下游某座磨坊该转多快。 管这九道闸的,是姓岑的老闸师,人称岑掌闸。岑掌闸手下九名闸夫,每人守一道闸室。平日里,上游来一股水,闸夫们照老法子微调一下闸板,让水流"不增不减"地交给下一级——九道闸递下来,磨坊转得四平八稳。 可有两年,九折渠接连出了两桩怪事,一桩是涝,一桩是旱,病的却都不是同一处。 二、涝年:水越递越狂,磨坊被冲垮头一桩怪事出在大水年。那年上游来势汹汹,岑掌闸怕水不够使,叮嘱各闸" upstream 水大,你们也稍稍助它一臂之力"。九名闸夫心领神会,每人守闸时都习惯"顺手把水流再催旺一分"——这一道放它一点二成,下一道也放一点二成。 头两道闸还没什么,到了第五道,水流已经汹汹;等九道闸全递完,磨坊前那股...
AI 新闻速递(2026-09-03)
今日摘要:OpenAI 下一代旗舰 Astra 被曝采用"循环深度推理"架构,并成为首个触及公司"关键"网络安全红线(Critical)的模型;谷歌发布 Gemini 3.8 与漏洞挖掘专用版 Flash Cyber,同时宣布 TPU 迭代提速到一年两款;李飞飞 World Labs 发布多模态世界模型 Atlas 1,单图可生成 1 分钟 1440p 视频;纽约最大公立学区出台政策,K-8 全面禁用面向学生的生成式 AI;英伟达被曝接近以约 140 亿美元收购 Hugging Face。以下是详细内容。 一、OpenAI 新旗舰 Astra:以"算"换"参",并首触网络安全红线时间:9 月 2 日(The Information / TechCrunch 报道) 主体:OpenAI OpenAI 即将推出的旗舰模型 Astra 采用循环深度(recurrent-depth)推理架构:输出下一个词之前,文本在同一网络层内反复处理多轮,用更多计算换取更少参数。公开论文显示,这一路线下约 ...
秒杀系统高并发优化实战(C++ / Drogon):5.1 缓存 Key 设计规范与接口基线压测
阶段一我们打出基线 QPS≈371 / 0 超卖,根因钉死在 seckill_sku 单行锁串行化——那是写接口(下单)的问题。但同一时刻还有两个读接口在被反复调用:一万个用户同时刷新列表,MySQL 就把同一条 SELECT 执行一万遍,返回一模一样的结果。读请求和写请求是两种完全不同的动物,这一章我们专门收拾读接口。 本文是「秒杀系统(C++ / Drogon)」系列的第五章第一篇。配套资产:docs/CACHE-DESIGN.md(缓存规范全文)、src/service/CacheKeys.h(Key 唯一构造入口)、src/service/SkuCache.*(Redis 异步封装)、scripts/read-bench.sh + jmeter/read-baseline.jmx(读接口基线压测)。代码对应阶段二 v0.2.x。真实编译与压测在 WSL(i7-14650HX + 本地 MySQL + 本地 Redis)完成。 一、为什么现在才加缓存,加在哪 是什么:读接口(GET /api/seckill/list、GET /api/seckill...
秒杀系统高并发优化实战(C++ / Drogon):5.3 加 Redis 缓存:商品详情接口(含空值哨兵防穿透)
4.2 把商品详情接口(GET /api/seckill/{skuId})跑通了,主键点查、直连 MySQL。这一篇给它套缓存,并顺手落地一个常被放到 5.6 才讲的能力——空值哨兵防缓存穿透。原因很实际:详情接口的 {skuId} 是用户可控的,攻击者拿随机 id 狂打,缓存层若不做处理,会退化成「每次都穿透到 DB」,等于没缓存。所以防穿透和详情缓存是同一道题的正反两面,一起讲。 本文是「秒杀系统(C++ / Drogon)」系列的第五章第三篇,也是 5.1/5.2/5.3 读缓存三篇的收口。配套代码:src/service/SeckillService.cc::detailSku/queryDetailFromDb、src/service/SkuCache.*(setNull/kNullValue)。代码对应阶段二 v0.2.x。Key 规范、TTL 抖动、列表缓存见 5.1/5.2。 一、详情缓存 + 空值哨兵:是什么、坑在哪、本质一句话 是什么:详情接口按 skuId 主键点查...
秒杀系统高并发优化实战(C++ / Drogon):5.2 加 Redis 缓存:商品列表接口
4.1 我们把商品列表接口(GET /api/seckill/list)跑通了,但那时它每次都直连 MySQL。到了第五章,第一件事就是给这个被刷新最频繁的接口套一层 Redis 缓存。它的 key seckill:sku:v1:list 是「全量商品」的聚合值,是热点中的热点——值得专门写一篇讲清楚它和详情缓存在取舍上的不同。 本文是「秒杀系统(C++ / Drogon)」系列的第五章第二篇。配套代码:src/service/SeckillService.cc::listSkus/queryListFromDb、src/service/SkuCache.*、src/main.cc 缓存配置解析。代码对应阶段二 v0.2.x。Key 规范与 TTL 抖动见上一篇 5.1。 一、列表缓存:是什么、坑在哪、本质一句话 是什么:列表接口返回全部商品(LIMIT 100),结果对所有用户一致、允许短暂陈旧——教科书级的缓存对象。 坑:列表是一个全量聚合 key,任意一件 sku 成交都改变了它的内容。如果「写后失效」策略选错(把列表也一起删),在写 QPS 高时这个 key...
局部性原理——从哪来、为什么、有什么用
局部性原理——从哪来、为什么、有什么用 面试官问:为什么 Redis 查一次很快,而同样的数据放远程 MySQL 上要慢几十倍?为什么数组遍历比链表快,明明两者都是"一个接一个读"?为什么 CPU 造了三级缓存,却无论如何不肯做大一点?—— 答案都指向同一条规律:局部性原理。 0. 钩子:一个几乎不可能的缓存假设没有局部性原理,会发生一件很滑稽的事:CPU 花了几百亿造的 L1 Cache 几乎没有命中,命中率趋近于 0,缓存白装。为什么?因为缓存能起作用,靠的是"你读写过的数据,大概率很快还会再读写"——如果每次访问都落在完完全全随机的新位置,那缓存里存的旧东西永远派不上用场,装缓存等于没装。可现实是,几乎所有真实程序都有强局部性,所以缓存这条产业链才成立。局部性不是假设出来的,它是真实工作负载的结构性特征,缓存只是顺着这个特征搭的脚手架。 1. 它从哪来:Peter Denning 与工作集模型1965 到 1968 年,虚拟内存(分页)刚铺开不久,系统遇到一个棘手问题:程序一多、内存一紧张,页面就在内存和磁盘之间疯狂换入换出,系统陷...
临渊镇的合议官
一、临渊镇的刀,总在最后一关栽跟头临渊镇傍着一道深涧而建,以打宝刀出名。镇上刀铺接了单子,从选铁、折叠、淬火到开刃,前几道都由师傅亲手把关。可刀成不成器,最后一道"评刀"却不由师傅定——镇上有个老规矩:每把新刀要过"判官"这一关,判官说合格,方能入鞘出售;判官说不合格,整把回炉。 长久以来,这差事落在一个姓陶的老判官身上。陶公看刀看了三十年,眼力原本极好,可临渊镇的人渐渐发现一件怪事:同一批料、同一位师傅打的刀,陶公今日判合格的,隔日另一把竟在客人手里生生折断;而他昨日毙掉的那把"次刀",后来被人偷偷留下,竟用了十年未卷刃。 "陶公也有走眼的时候?"镇民嘀咕。陶公自己也想不通——他看刀的法子从没变过,怎么时灵时不灵。 二、一个爱抬杠的客人,点破了症结这年秋天,城里来了个收刀的客商,姓裴。裴商人在各家刀铺挑了三十把刀,一一请陶公评过,自己却偷偷另请三位老匠暗中复验。半月后他找上门,把一本账册拍在陶公案上。 "陶兄,你这三十把里,判错了九把。"裴商人笑道,"不是你眼力差,是每...
AI 新闻速递(2026-09-02)
今日摘要:"上下文工程"取代 Prompt 工程成为编程智能体新显学,同一模型仅因上下文编排差异成功率可差 20~40 个百分点;代码智能体进入"平行宇宙"测试,多沙箱并行验证修复方案;机器人基础模型走向视觉-语言-动作一体预训练的统一架构;人形机器人"租赁即服务"(RaaS)商业模式开始跑通;开源代码模型在 SWE-bench 类基准上集体追平闭源。以下是详细内容。 一、主线1. 「上下文工程」取代 Prompt 工程,成为编程智能体的新显学随着 100 万1000 万 token 的上下文窗口成为主流,社区共识正在从"怎么写一句好 prompt"转向"怎么把检索、记忆、工具调用结果组织成一段好的上下文"。新一批评测显示:同一模型、同一任务,仅因上下文里"先放需求还是先放报错""是否带历史成功补丁""工具输出是否裁剪",成功率能差出 2040 个百分点。 值得关注:这标志着 AI 编程的竞争焦点从"模型能...
秒杀系统高并发优化实战(C++ / Drogon):3.1 用户注册接口:PBKDF2 加盐哈希 + 自实现 JWT + 可吊销会话
10000 个人抢 100 件商品,最后卖出去 137 件——这句"段子"我们在表设计那篇就破了。但秒杀真正的第一道门,不是库存,是身份:你是谁?你有没有资格抢?如果你连"注册"这一步都把明文密码写进库,那后面无论 Redis 预扣、Lua 原子、MQ 削峰做得再漂亮,用户表一旦泄露就是全裸——所有"高并发安全"都建立在沙滩上。 本文是「秒杀系统(C++ / Drogon)」系列第三章(登录模块)的开篇。配套仓库 seckill-cpp。当前阶段一 v0.1.x 已落地登录 / 短信模块,目录为 sql/user_schema.sql、src/service/UserService.{h,cc}、src/controllers/UserController.{h,cc}、src/service/password.{h,cc}、src/service/Jwt.{h,cc}、src/service/SessionStore....
秒杀系统高并发优化实战(C++ / Drogon):3.2 用户登录接口:密码比对 + 自实现 HS256 签发 + Redis 可吊销会话
注册接口建好了"身份",这一篇让身份"进门"——登录接口。它要做三件事:比对密码、签发 JWT、把会话写进 Redis。前两件是安全,第三件是"可吊销"——没有它,JWT 一旦签发就只能在 exp 到点后自己失效,改密码/封号都拦不住旧 Token,等于门没锁。 配套代码:src/service/UserService.cc(login)、src/service/Jwt.{h,cc}、src/service/SessionStore.{h,cc}、src/controllers/UserController.cc(login)。延续 3.1 的"服务层只做业务、控制器只做协议转换"分层。 一、登录接口:是什么、坑在哪、本质一句话是什么:登录 = 用"手机号 + 密码"证明你是你,成功后发一个不可伪造的凭证(JWT),并在 Redis 留下一条可吊销的会话记录,让服务端随时能把这把钥匙作废。 坑在哪: 密码明文比对的时...
秒杀系统高并发优化实战(C++ / Drogon):3.10 注册安全加固:同 IP 注册频控 + 反代真实客户端 IP 解析
验证码改成自签发 / 日志模式后(3.3),"送达"不再是外部约束——注册接口等于裸奔在公网:攻击者拿一堆手机号从同一个 IP 批量注册,就能刷出成千上万个账号。这篇补两道闸门:① 同 IP 注册频控堵批量刷号;② 反代真实客户端 IP 解析,否则频控的 key 拿到的是代理 IP,等于没防。 配套代码:src/service/RegisterGuard.{h,cc}(check / markSuccess + kCheckScript / kMarkScript)、src/controllers/UserController.cc 的 clientIp()、UserService::registerUser 最前的频控闸门。阈值走 config.json 的 register_limit(max_per_ip=5 / window_seconds=3600)。 一、是什么、坑在哪、本质一句话是什么:注册链路在"手机号格式校验之前"先过一道同 IP 固定窗口频控(Registe...
秒杀系统高并发优化实战(C++ / Drogon):3.3 短信验证码接口:生成 / 限流 / 存储 / 腾讯云直连
注册/登录要不要验证码?要——但验证码接口本身就是个高价值攻击面:它既能当"免费短信轰炸机"打别人的手机,又能被脚本爆破出 6 位验证码。这一篇把验证码接口做完整:生成(CSPRNG)→ 限流(冷却 + 每日上限)→ 存储(Redis)→ 发送(腾讯云直连),每一道都对应一个具体的攻击面。 配套代码:src/service/SmsService.{h,cc}(生成/限流/存储/校验)、src/service/SmsSender.{h,cc}(腾讯云直连)、src/controllers/SmsController.cc、src/service/SmsSender.cc 的 TC3 签名。 一、短信验证码接口:是什么、坑在哪、本质一句话是什么:POST /api/sms/send 给指定手机号生成 6 位验证码,限流后存入 Redis(有效期 + 重发冷却 + 每日上限),再通过腾讯云短信 API 发出去。校验在注册(3.1)/登录(3.2)流程里调 verifyCod...
秒杀系统高并发优化实战(C++ / Drogon):3.4 注册验证码校验 & Token 持久化到 Redis
3.1 把注册流程的"验证码分支"留了空(先查重再哈希再落库,requireSmsOnRegister=true 时走 sms_->verifyCode);3.2 把"登录成功写 Redis 会话"说了。这一篇把这两件事的接缝焊死:验证码怎么校验、Token 怎么持久化、退出怎么吊销,以及它们背后的 key 约定与配置开关。 配套代码:src/service/UserService.cc(registerUser 的 verifyCode 分支、login 的 sessions_->save、logout 的 sessions_->revoke)、src/service/SessionStore.{h,cc}、配置项 requireSmsOnRegister / requireSmsOnLogin。 一、本篇在链路里的位置:是什么、坑在哪、本质一句话是什么:把"验证码校验"接到注册/登录流程里(挡脚本批量注册),把"登录签发的 JWT"的 ...
秒杀系统高并发优化实战(C++ / Drogon):3.5 验证码安全加固:Redis Lua 原子校验 + 每日发送上限 + 试错上限
3.3 把短信接口的流程跑通了(生成→限流→存储→发送)。这一篇专门挖安全加固这一层:为什么限流和校验必须写成 Lua、每日上限的 key 为什么要计算"距次日 0 点"的 TTL、6 位验证码在数学上意味着什么、以及"发送失败不回滚配额"这个有意的取舍。 配套代码:src/service/SmsService.cc 的 kSendScript / kVerifyScript / secondsUntilTomorrow / genNumericCode。 一、为什么需要加固:三个攻击面 攻击面 后果 加固手段 短信轰炸 拿接口打别人手机,败坏口碑 + 账单是你的 重发冷却 + 每日上限(都在 kSendScript) 验证码爆破 6 位仅 1M 组合,脚本几十分钟撞出 校验次数上限(kVerifyScript 的 maxVerifyAttempts) 一码多用 并发下同一码被兑换两次 校验与消费同一 Lua 原子完成 二、发送侧 Lua:冷却 + 每日上限原子化kSendScript...
秒杀系统高并发优化实战(C++ / Drogon):3.6 登录安全加固:密码错误次数限制 + 账号临时锁定
登录接口(3.2)能比对密码了,但还差最后一道防线的"门闩":密码错误次数限制 + 账号临时锁定。没有它,攻击者可以对一个手机号无限次试密码(撞库);有了它,连续错 N 次就临时锁几分钟,把撞库成本抬高到不划算。 配套代码:src/service/LoginGuard.{h,cc}(checkLocked / onFailure / onSuccess + kOnFailureScript / kCheckScript)、UserService::login 里埋的 guard_-> 调用。 一、是什么、坑在哪、本质一句话是什么:LoginGuard 用 Redis 记录每个手机号的密码失败次数,连续失败达 max_fail(默认 5)就 SETEX login:lock:{phone} 锁定 lock_seconds(默认 600);登录成功清计数;登录前先查锁定。 坑在哪: 计数放进程内存:多实例部署时每台各记各的,"5 次就锁"变成"5×N 次...
秒杀系统高并发优化实战(C++ / Drogon):3.7 退出登录接口:校验 Token + 删 Redis 会话即吊销
3.2 把"登录签发 + 写 Redis 会话"讲完,3.4 把"退出即吊销"预览了一句。这一篇把退出登录做成完整接口:POST /api/user/logout,从 Authorization: Bearer 取 Token、验签、删 sess:{jti}——删掉那一刻,旧 Token 立即失效。 配套代码:src/controllers/UserController.cc(logout + extractBearerToken)、src/service/UserService.cc(logout / authenticate)、src/service/SessionStore.cc(revoke)。 一、是什么、坑在哪、本质一句话是什么:退出登录 = 客户端带上 Token,服务端验签通过后删除 Redis 里的 sess:{jti}。JWT 本身无法被"改",但此后每次鉴权都会因 EXISTS sess:{jti} 失败而被拒。 坑...
秒杀系统高并发优化实战(C++ / Drogon):3.8 代码细节重构:抽出 seckill::http 统一 JSON 响应 helper
阶段一的代码越写越多,SeckillController / UserController / SmsController 每个 handler 都在手写 newHttpJsonResponse + setStatusCode 的样板,而且"什么业务码配什么 HTTP 状态"各写各的,容易不一致。这一篇做一件小但重要的重构:把统一 JSON 响应抽成 seckill::http 命名空间(src/util/HttpJson.h),所有控制器复用。顺带把重构中踩到的两个 Drogon 1.9.10 编译坑一并记下。 配套代码:src/util/HttpJson.h、src/controllers/SeckillController.cc(三处 handler 改用 reply / replyData)。 一、为什么要重构:重复样板 + 状态码不一致重构前,每个 handler 都是这套: 123456// 重构前:每个 handler 重复且易错Json::Value root; root["code"] =...
秒杀系统高并发优化实战(C++ / Drogon):3.9 验证码接口安全加固:滑块验证码(设计篇,代码推迟到前端阶段)
⚠️ 本文是设计篇,不落地代码。 滑块验证码的完整实现(前端滑块 + 后端校验)推迟到「开始有前端」阶段再做。本篇只把后端必须自写的部分设计清楚、把契约定下来,避免到时候返工。原因见第六节「为什么代码推迟」。 3.3~3.5 做的是短信验证码(防脚本批量注册)。短信验证码有个软肋:它是"知识因子"——6 位数字,真能拦住人肉,但拦不住"养一批号 + 接码平台"的专业黄牛。滑块验证码补的是另一层:行为因子——你拖动滑块的轨迹,机器人很难逼真模仿。这一篇讲它的设计。 一、为什么是滑块:人机区分的本质短信验证码回答"你有没有这个手机号",滑块回答"你是不是真人在操作"。两者维度不同: 短信 = 拥有因子(你有这个号,接得到码) 滑块 = 行为因子(你的拖动轨迹像人) 黄牛可以批量为号接码,但很难批量为每个号生成"像人"的拖动轨迹。所以滑块是挡自动化脚本的更前沿一道,通常放在"发短信/登录/下单"之前做前置人机校验。 二、...
秒杀系统高并发优化实战(C++ / Drogon):4.1 秒杀商品列表接口开发
登录模块(第三章)做完,进入第四章——基础秒杀功能(阶段一:Drogon + MySQL 直连)。先来最轻松的一个:商品列表接口 GET /api/seckill/list。它只读、不碰事务、不校验 token,是秒杀系统里最"安静"的一条路,但有几个 C++/Drogon 细节值得讲。 配套代码:src/service/SeckillService.cc(listSkus)、src/controllers/SeckillController.cc(listSkus)、src/main.cc 路由注册。 一、是什么、坑在哪、本质一句话是什么:商品列表 = 从 seckill_sku 读出所有在售商品(id/name/stock/total/起止时间),前端用来展示"有哪些可以抢"。只读查询,不分页(阶段一 LIMIT 100)。 坑在哪: DATETIME 直接绑成 tm/Date:Drogon ORM 把 DATETIME 列默认当 tm 或 Date 类型,序列...
秒杀系统高并发优化实战(C++ / Drogon):4.3 秒杀下单接口开发(基础版,同步落库)
阶段一的核心来了。秒杀下单 POST /api/seckill 要解决两个会要命的问题:超卖(100 件卖成 137 件)和重复下单(同一人刷多单)。这一篇把"阶段一基础版"的打法讲透:单行原子扣减 + 同一事务 + ON DUPLICATE KEY 幂等。 配套代码:src/service/SeckillService.cc(doSeckill)、src/controllers/SeckillController.cc(seckill)、sql/schema.sql(uk_user_sku)。 一、是什么、坑在哪、本质一句话是什么:下单 = 扣减库存(若还有)+ 落一条订单,两者要么全成、要么全回滚;同一用户对同一商品只能成一单。 坑在哪: 先 SELECT 再 UPDATE:并发下读到的都是旧值,必然超卖——这是秒杀第一大坑。 扣库存和落订单不包事务:中途崩溃留下"库存扣了但没订单"的不一致。 裸 INSERT 做幂等:唯一键冲突抛异常,外层 catch 当成 DB 错误回滚——但此时库存已经被扣了,且这本该是"...
秒杀系统高并发优化实战(C++ / Drogon):4.4 压测:100 用户抢 10 件,核对不超卖
4.3 把"原子扣减 + 事务 + 幂等"写完了,但代码说"不超卖"不算数——压测说不算超卖才算数。这一篇用压测 harness 跑"100 用户抢 10 件",再用 MySQL 直接核对 sold == orders == 10,证明原子 UPDATE ... WHERE stock>0 在高并发下真的扛住了。 配套脚本:scripts/jmeter-baseline.sh(correct 模式)、scripts/smoke-seckill.sh。核对 SQL 见 4.3 / README「快速查看数据」。 一、压测目标:用"卖出去的件数"反证不超卖超卖的定义就一句话:卖出的件数 > 库存。所以验证不超卖,不是看返回码,而是看 MySQL 里的真相源: 1234-- 卖出件数 = 总量 - 剩余;订单数 = 落库行数。两者必须相等,且 ≤ 总量SELECT (SELECT total FROM seckill_sku WHERE id=1) - (SELECT stock ...
秒杀系统高并发优化实战(C++ / Drogon):4.2 秒杀商品详情接口开发
列表是"有哪些可以抢",详情是"这一个还能抢多少"。GET /api/seckill/{skuId} 按 id 查单个商品——它引出一个 Drogon 路由的关键细节:路径参数怎么取、静态路径和参数路径撞名怎么办。 配套代码:src/service/SeckillService.cc(detailSku)、src/controllers/SeckillController.cc(detailSku)、src/main.cc 三参 handler 路由。 一、是什么、坑在哪、本质一句话是什么:商品详情 = 按 skuId 从 seckill_sku 查出单条商品,前端用来展示"这个商品的库存/时间"。只读、按主键查。 坑在哪: 路径参数取错形式:Drogon 三参 handler (req, callback, path) 只在该路由有 {} 占位符时才正确绑定第 3 参;无占位符路由用三参会被 fromRequest<std::string> ...
秒杀系统高并发优化实战(C++ / Drogon):4.5 压测:验证重复下单(uk_user_sku 幂等,0 重复)
超卖防住了(4.4),第二个要命的问题是重复下单——同一用户连点 N 次,刷出 N 单。这一篇专门压"重复":同一个 (userId, skuId) 并发发一堆请求,再用 MySQL 核对"这个用户只有 1 张订单",证明 uk_user_sku + ON DUPLICATE KEY 的幂等兜住了。 配套代码:sql/schema.sql(uk_user_sku)、src/service/SeckillService.cc(步骤 2 的 ON DUPLICATE KEY UPDATE)、scripts/jmeter-baseline.sh 的并发重复场景。 一、重复下单的两层防护重复下单有两层防线,各管一段: 应用层在途闸门(4.8 的 InflightGuard):同一 (userId, skuId) 已有请求在处理时,后续请求连 DB 都不打直接判 DUPLICATE_ORDER。挡的是"并发窗口内的重复"。 数据库唯一键 uk_user_sku:串行发出的两次下单(第一次已完成、订单已落库),应用层闸门拦不住...
秒杀系统高并发优化实战(C++ / Drogon):4.8 应用层锁(mutex / 自旋 / 原子三后端)
4.3 已经靠 uk_user_sku 唯一键保证"不重复下单",但那个兜底发生在 MySQL 行锁之后——每个重复请求都得真打一次 DB 才被拒。这一篇在数据库之前加一道"在途闸门":同一个用户在同一个商品上的并发重复请求,还没进 DB 就被应用层挡掉。我们做了 mutex / 自旋 / 原子三种后端,并实测对比它们的开销与收益。 配套代码:src/service/InflightGuard.h(三后端闸门);压测:scripts/lock-bench.sh 2000 100 4、GET /api/lock/stats。 一、先拆一个致命误区:锁到底该锁在哪最直觉的写法是拿 std::mutex 把整个 doSeckill 包起来,锁在 handler 开头、在 DB 回调里解锁。这在 Drogon 里是灾难,两个独立原因: 阻塞 IO 线程:Drogon 的 handler 跑在 IO 线程(本项目 threads_num=4)。一个请求持锁等 MySQL 往返(几 ms),就把这 1/4 的请求全卡...

