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

在百亿流量面前,让“不存在”无处遁形——Redis 缓存穿透的极限攻防实录

一、开场白:凌晨 01:47 的“幽灵峰值”
2024 年 3 月 18 日,某头部内容平台的监控大屏突然飘红:网关 502 比例从 0.01% 蹿至 17%,Redis 集群命中率仍保持 99.3%,数据库 CPU 却逼近 95%。值班同学第一反应是“有热点 key 过期”,但查看 Keyspace 后发现过期事件寥寥。十分钟后,根源浮出水面:攻击者利用“内容 ID 自增 + 随机偏移”的方式,瞬时灌入 2300 万个根本不存在的 ID,导致缓存穿透。本文以这次真实事件为蓝本,拆解百亿流量场景下穿透攻防的极限实践。

二、业务画像:内容 ID 的结构化特征
平台内容 ID 为 64 位 long,高 24 位是业务线,中 24 位是时间戳(天级),低 16 位是随机序列。合法 ID 总量约 120 亿,但每日新增仅 8000 万。攻击者只需在时间戳区间外随机生成,即可 100% 命中“不存在”。该特征决定了传统布隆过滤器难以覆盖全部 key 空间,必须引入“时间窗口 + 分片”策略。

三、第一道闸:网关层的“零成本”拦截

  1. 正则黑名单
    在 Nginx 层加入 Lua 脚本,校验 ID 时间戳是否在未来 1 天或早于 90 天前,直接返回 400。该规则零内存占用,拦截 80% 的伪造流量。

  2. Token Bucket 限速
    对“空结果”响应单独建桶,阈值设为正常用户均值的 5 倍,超过即滑块验证码。

  3. 负反馈标记
    连续触发 3 次空结果的用户 Cookie 打标,后续 10 分钟所有请求降级到“只读缓存”,禁止回源数据库。

四、第二道闸:Redis 侧的“分片布隆”

  1. 空间模型
    120 亿 key 若用单层布隆,需要 14.4 Gb 内存,远超单机预算。采用“业务线分片 + 时间滚动”方案:

    • 每个业务线独享一个 64 Mb 的 Bloom Filter;

    • 以“天”为单位滚动,过期 90 天的 filter 直接丢弃;

    • 总内存占用 = 业务线数量 × 64 Mb ≈ 1.1 Gb。

  2. 并发写入
    内容发布时同步写 MySQL 与布隆过滤器,写过滤器使用 Redis 的 BF.ADD,失败时通过 MQ 补偿,保证最终一致。

  3. 假阳性治理
    假阳性概率 p=0.01%,每日误判 12 万请求。引入“二次确认”策略:过滤器返回存在时,先查 Redis,miss 后再查 MySQL;过滤器返回不存在时,直接返回 404。这样只有 12 万请求多一次 Redis 查询,成本可接受。

五、第三道闸:空值缓存的“动态 TTL”

  1. 分层 TTL
    将空值缓存划分为 L1(Redis,30 秒)、L2(Caffeine 本地,5 秒)。L1 miss 后回源 DB,DB 返回空则写 L1 并携带 TTL=30 秒;L2 用于抗突发热点。

  2. TTL 自适应
    引入 PID 控制器:

    • 输入:过去 1 分钟空结果 QPS 与数据库 CPU;

    • 输出:TTL 在 5~300 秒之间动态调整;

    • 目标:CPU 保持在 60% 以下,Nil Ratio 低于 2%。
      实践表明,自适应后穿透峰值降低 92%,平均 TTL 收敛在 45 秒。

六、第四道闸:数据库侧的“最后一击”

  1. 空结果表
    创建表 fake_id_log(id bigint primary key, gmt_create datetime),空结果写入该表,替代直接回主库。

  2. 合并写
    使用 MySQL 的 INSERT IGNORE 批量 1000 条,减少行锁冲突。

  3. 异步回填
    消费者发现空结果 ID 存在于正式表时,删除空结果记录并刷新缓存,闭环纠错。

七、演练与度量:如何证明防御有效

  1. 红蓝对抗
    每月随机挑选 2 台云主机模拟攻击,工具可配置 QPS、ID 区间、随机度。对抗后输出三项指标:

    • 拦截率 = 1 - 到达数据库的请求 / 总攻击请求;

    • 误杀率 = 正常请求被 404 的比例;

    • 恢复时长 = 从攻击结束到系统指标恢复常态的用时。

  2. 影子过滤器
    生产环境并行运行一套“影子布隆”,参数与正式完全一致,但只记录日志不拦截。对比两者指标,可量化假阳性漂移。

  3. 混沌工程
    利用 ChaosBlade 随机下线 30% Bloom 节点,验证剩余节点能否承担流量;同时观测 TTL 自适应算法的收敛速度。

八、踩坑日记:三次血与泪的教训

  1. Lua 正则回溯
    早期使用 ngx.re.match 贪婪模式,遇到 128 位超长 ID 时 CPU 爆涨,改为 ngx.re.find 非回溯后解决。

  2. 过滤器重建抖动
    某次全量重建布隆时,采用 BF.LOADCHUNK,因网络抖动导致 3 秒阻塞,引发雪崩。后改为“滚动双缓冲”:新过滤器在后台构建,构建完成后原子替换。

  3. PID 控制器震荡
    初期 PID 参数激进,TTL 在 5 秒和 300 秒之间来回跳,造成缓存颠簸。引入一阶滞后滤波后,曲线平滑。

九、尾声:穿透的尽头是成本博弈
在 120 亿 key 面前,100% 拦截是不经济的。最终目标是把穿透概率压到“可忽略”区间,同时保证内存、CPU、人力成本线性可控。经过 6 个月迭代,平台 Nil Ratio 稳定在 0.7%,误杀率 0.05%,单条请求新增 RT 0.8 ms,全年节省数据库费用 120 万元。幽灵仍在,但已被关进笼子。

http://www.xdnf.cn/news/1128889.html

相关文章:

  • 原点安全签约金网络数科,共建一体化数据安全防护体系
  • 设计模式二:策略模式 (Strategy Pattern)
  • AiPy+豆包:数据分析可视化,一键生成GUI工具
  • Vue.js 动画与过渡:让你的界面“活”起来,提升用户体验的视觉魔法!
  • : $ operator is invalid for atomic vectors
  • 【SpringBoot】实战-开发模式及环境搭建
  • 前端面试专栏-工程化:25.项目亮点与技术难点梳理
  • Python 操作Excel工作表:添加、删除、移动、隐藏
  • omniparser v2 本地部署及制作docker镜像(20250715)
  • Linux 环境下安装 Node.js v16.13.0 完整指南
  • Unity沉浸式/360View/全景渲染
  • LeetCode--46.全排列
  • 深度学习 Pytorch图像分类步骤
  • Linux部署Python服务
  • sglang笔记1: 后端详细阅读版
  • 文心一言4.5开源模型测评:ERNIE-4.5-0.3B超轻量模型部署指南
  • Halcon双相机单标定板标定实现拼图
  • Java线程池深度解析与Spring Boot实战指南
  • resources为什么是类的根目录
  • 策略设计模式分析
  • AI辅助Python编程30天速成
  • 死锁问题以及读写锁和自旋锁介绍【Linux操作系统】
  • LeetCode|Day13|88. 合并两个有序数组|Python刷题笔记
  • MySQL数学函数
  • HALCON+PCL混合编程
  • 从抽象函数到可计算导数 ——SymPy 中占位、求导、代入的完整闭环
  • JVM——编译执行于解释执行的区别是什么?JVM使用哪种方式?
  • K型热电偶电动势以及温度对照表
  • 从基础到进阶:MyBatis-Plus 分页查询封神指南
  • BPE(字节对编码)和WordPiece 是什么