当前位置: 首页 > 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.lryc.cn/news/588622.html

相关文章:

  • 【Ubuntu22.04】repo安装方法
  • 1.2 vue2(组合式API)的语法结构以及外部暴露
  • 如何把手机ip地址切换到外省
  • 【深度学习优化算法】06:动量法
  • 从springcloud-gateway了解同步和异步,webflux webMvc、共享变量
  • iOS V2签名网站系统源码/IPA在线签名/全开源版本/亲测
  • iOS 抓包工具精选对比:不同调试需求下的工具适配策略
  • 项目总体框架(servlet+axios+Mybatis)
  • 【解决】联想电脑亮度调节
  • iOS高级开发工程师面试——多线程
  • Axios 和 Promise 区别对比
  • Supervisor 使用教程:进程守护的最佳实践指南
  • 【Git】详解git commit --amend用法以及使用遇到的问题
  • eVTOL分布式电推进(DEP)适航审定探究
  • Python 操作Excel工作表:添加、删除、移动、隐藏
  • redis集群的部署
  • 线性代数小述(三)
  • Pitaya 是一个简单、快速、轻量级的游戏服务器框架,它为分布式多人游戏和服务器端应用程序提供了一个基本的开发框架
  • 【橘子分布式】Thrift RPC(编程篇)
  • Vim多列操作指南
  • 028_分布式部署架构
  • 淘宝扭蛋机小程序开发:重构电商娱乐化体验的新范式
  • GaussDB 数据库架构师修炼(四) 备份容量估算
  • 【轨物洞见】光伏运维的“无人区”突围战,数据智能是唯一航标
  • Python Docker SDK库详解:从入门到实战
  • docker 方式gost代理搭建以及代理链实施
  • Linux VFS 抽象层全解析:统一接口的力量
  • JAVA学习笔记 使用notepad++开发JAVA-003
  • 微信小程序进度条cavans
  • 虚拟主机CPU占用100导致打不开的一次处理