Redis 与 MySQL 一致性实战:一次多发 317 张优惠券的故障链

简介: 一次优惠券超发事故揭示缓存与数据库一致性本质:Redis仅作读加速,最终裁决必须由MySQL事务+唯一约束保障。故障源于缓存删除失败+缺乏原子扣减条件,导致317张券超发。

Redis 与 MySQL 一致性实战:一次多发 317 张优惠券的故障链

10 万张限量优惠券在 10 点开抢。监控里 Redis 正常、MySQL 正常,接口成功率也没有突然掉到谷底,活动结束后却多出了 317 条领取记录。

事故复盘发现:最后一张券在数据库里扣减成功后,应用删除 Redis 库存键失败;随后一批请求继续从旧缓存读到“还有 84 张”。更致命的是,数据库写入没有把“库存仍大于 0”作为原子条件,只相信了缓存的预检结果。

image.png

先改一个观念:Redis 不是稀缺资源的最终裁判

缓存适合加速读取和拦截明显无效请求,但限量券、库存、余额和名额一旦产生经济后果,最终裁决必须落在具备事务与唯一约束的存储中。

这意味着缓存短暂不一致可以接受,但不一致只能造成“页面显示还有、提交后告知已领完”,不能造成真实超发。测试目标不是幻想 Redis 与 MySQL 永远同一瞬间相等,而是验证任何故障窗口都无法穿透业务不变量。

image.png

把两个不变量写进数据库,而不是写进注释

第一,同一用户在同一活动只能成功领取一次;第二,活动 remaining 不得小于 0。可以用唯一约束和条件更新共同保证:

CREATE UNIQUE INDEX uq_claim_campaign_user
ON coupon_claim(campaign_id, user_id);

BEGIN;

UPDATE coupon_campaign
SET remaining = remaining - 1,
    version = version + 1
WHERE campaign_id = :campaign_id
  AND status = 'ACTIVE'
  AND starts_at <= NOW()
  AND ends_at > NOW()
  AND remaining > 0;

-- 应用必须检查上一条 UPDATE 是否恰好影响 1 行
INSERT INTO coupon_claim(campaign_id, user_id, status)
VALUES (:campaign_id, :user_id, 'CLAIMED');

COMMIT;

真实实现中,若条件更新影响 0 行,应区分活动不存在、时间不合法、库存耗尽等结果;若唯一约束冲突,应返回“你已经领取”,并回滚本次库存扣减。两个动作必须在同一事务里。

数据库提交和缓存更新之间,故障一定会发生

最危险的窗口是:数据库已经提交,进程还没来得及删缓存就崩溃。把删缓存写在 try 的下一行,并不能让两者原子化。

一种可落地方案是在同一事务里写 Outbox 事件,后台工作器按事件刷新或删除缓存。即使进程重启,事件仍可重放。缓存值附带数据库版本,旧事件晚到时也不能覆盖新版本。

def apply_cache_event(cache, event):
    key = "coupon:" + str(event["campaign_id"])
    current = cache.get(key) or {
   "version": -1}
    if current["version"] >= event["version"]:
        return "ignored_old_event"
    cache.set(key, {
   
        "remaining": event["remaining"],
        "version": event["version"],
    }, ttl_seconds=30)
    return "applied"

TTL 是最后一道自愈手段,不是主要一致性方案。TTL 设得再短,也存在业务高峰中的错误窗口;设得太短还会造成大量回源,把数据库压垮。

image.png

缓存一致性应该怎样测试

正常读写只是起点。至少要在四个位置注入故障:数据库提交前崩溃、提交后删缓存前崩溃、Outbox 已发送但确认丢失、旧缓存事件晚于新事件到达。再叠加 100 路同用户并发和 1000 路不同用户争抢最后 50 张券。

最终不要只数 HTTP 200,而要查询数据库终态:成功领取数不超过初始库存;remaining 不小于 0;同一用户最多一条成功记录;所有成功记录都能追溯到活动版本。缓存可以暂时落后,但应在约定时间内收敛。

def assert_campaign_invariants(repo, campaign_id, initial_stock):
    campaign = repo.get_campaign(campaign_id)
    claims = repo.successful_claims(campaign_id)
    users = [row.user_id for row in claims]

    assert 0 <= campaign.remaining <= initial_stock
    assert len(claims) + campaign.remaining == initial_stock
    assert len(users) == len(set(users))

监控也要从组件可用升级到业务一致

Redis 命中率和 MySQL QPS 都健康,不代表券没多发。生产上还要观察:缓存版本落后量、Outbox 最老积压时间、库存差值、唯一约束冲突率、提交后回写失败数。它们才能提前暴露一致性正在恶化。

Redis 与 MySQL 一致性真正要守住的,不是每次读取都完全相同,而是任何缓存旧值、消息重复和进程崩溃,都不能让稀缺资源的业务账失真。这才是缓存一致性测试的终点。

相关文章
|
1天前
|
SQL 人工智能 自然语言处理
长上下文代码评审的错觉:模型读完仓库,不等于读懂变更半径
本文揭示长上下文模型在代码评审中的认知盲区:读完仓库不等于读懂变更半径。枚举值修改引发多系统故障,暴露静态理解与真实影响间的鸿沟。提出四层影响图(静态依赖、运行调用、数据血缘、业务责任)和证据驱动的风险评估范式,强调测试工程师需从“写用例”转向“组织变更证据”。
长上下文代码评审的错觉:模型读完仓库,不等于读懂变更半径
|
1天前
|
缓存 JSON API
阿里云千问Qwen3.8‑Flash深度解析:多模态能力、订阅计费规则、API接入配置与生产落地完整教程
Qwen3.8‑Flash是千问系列主打高速、高性价比的原生多模态基座,百万Token上下文窗口,支持图文视频输入,具备不错的代码能力、中等复杂度Agent工具调用能力,延迟表现优秀,非常适合高并发线上业务、知识库问答、轻量智能体、图文解析、代码辅助开发场景。
88 1
|
19天前
|
SQL 人工智能 算法
AI岗位渗透率升到37.56%:测试岗正在分成“新旧两种人”
2026秋招AI岗位渗透率达37.56%,测试岗正加速分层:传统执行岗溢价消失,懂AI应用、Agent工程、LLM评估的全栈测开人才需求暴增340%,薪资高30%-50%。转型,刻不容缓。
|
2月前
|
设计模式 人工智能 安全
从代码生成到需求交付:一个开发 Skill 的工程化实践
腾讯团队提出AI编程新范式:将需求交付拆解为8阶段工程流程,融合项目知识库、自动化工具与质量门禁。虽代码生成率达94%,但核心突破在于把研发经验转化为可执行规则——AI不再仅写代码,而是在严格约束下完成端到端交付。
Nuxt中服务端请求无法获取LocalStorage和Cookie的解决办法!
Nuxt中服务端请求无法获取LocalStorage和Cookie的解决办法!
|
4月前
|
人工智能 前端开发 JavaScript
AI Agent(智能体)的输出格式应该从 Markdown 转向 HTML吗?
Anthropic工程师提出AI输出应从Markdown转向HTML,强调其高信息密度、交互性与易分享优势;但HTML存在Token消耗大、Diff困难等短板。未来更可能是“分工协作”:HTML主导前端交互(UI/Artifact),Markdown坚守后端通信(Prompt/知识结构),二者共存演进。
341 0
|
10月前
|
编解码 语音技术 数据安全/隐私保护
企业微信协议语音链路的技术实现
企业微信移动端语音采用0x0602指令,通过长连接传输Silk压缩流,降低30%首包延迟。协议含固定帧头与TLV结构,支持AES加密与实时解码,网关可透明转码对接ASR系统,整体延迟约8ms,CPU占用低。
532 0
|
7月前
|
人工智能 缓存 自然语言处理
告别Demo|手把手教你构建可用的LangChain测试智能体
市面上从不缺少能跑通 Demo 的 AI 测试脚本,缺的是能在企业级复杂场景下真正“抗住事”的测试智能体。今天我们不谈概念,直接动手:基于 LangChain 从零构建一个具备测试设计、自主执行、结果分析能力的生产级 Agent。它将证明,AI 自动化测试的价值,不在于“看起来智能”,而在于能为你省下多少真实工时。
|
人工智能 算法 安全
MCP提示词工程:上下文注入的艺术与科学
作为一名深耕AI技术领域多年的技术博主摘星,我深刻认识到提示词工程(Prompt Engineering)在现代AI系统中的核心地位,特别是在Model Context Protocol(MCP)框架下,提示词工程已经演进为一门融合艺术直觉与科学严谨的综合性学科。在我多年的实践经验中,我发现MCP不仅仅是一个简单的协议标准,更是一个革命性的上下文管理平台,它通过精密的提示词机制和动态上下文注入技术,彻底改变了AI系统与外部资源的交互方式。本文将深入探讨MCP中提示词的作用机制,从底层协议设计到高层应用策略,全面剖析动态提示词生成与模板化的技术实现,详细阐述上下文长度优化与截断策略的核心算法,并
832 0
MCP提示词工程:上下文注入的艺术与科学
先抓西瓜,后抓芝麻———柏拉图工具
先抓西瓜,后抓芝麻———柏拉图工具
739 0
先抓西瓜,后抓芝麻———柏拉图工具