这是 Beta 探索课程,内容结构、实验步骤和示例可能会继续调整。
流量洪峰:热门短链接带来的流量冲击
场景
想象一下这个场景:
某明星突然宣布结婚 📱
- 粉丝疯狂转发相关链接
- 一小时内点击量从 1000 爆发到 1000万+
重大新闻发布 📰
- 权威媒体报道重要新闻
- 相关短链接在社交媒体病毒式传播
限时优惠活动 🎁
- 网络红人发布限时折扣
- 短链接瞬间被几十万人同时点击
热门话题讨论 💬
- 热点话题引发热议
- 相关讨论和分享激增
真实案例:
- 某电商平台黑五活动期间,短链接点击量单日突破 1 亿次
- 某新闻客户端热门新闻的短链接,3 分钟内被访问 500 万次
- 某娱乐明星绯闻相关链接,2 小时内访问量破千万
问题:系统崩溃的痛苦
当流量突然暴增时,我们的短链接系统会面临什么?
现状问题分析
让我们先看看我们的”完美”系统是如何设计的:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
这个简单系统在流量洪峰面前的表现:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
具体故障表现
| 故障类型 | 具体表现 | 用户感受 |
|---|---|---|
| 数据库连接池耗尽 | ”too many connections” 错误 | 页面无法打开 |
| 响应时间过长 | 每次请求等待 5-10 秒 | 点击后长时间无反应 |
| 请求队列堆积 | 请求积压在队列中 | 点击后数分钟才跳转 |
| 系统内存溢出 | OOM Killer 杀死进程 | 服务完全不可用 |
应对策略:层层递进的防御体系
策略 1:缓存层防护 🛡️
首先,我们需要在最前面加上缓存层:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
缓存策略配置:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
策略 2:负载均衡 ⚖️
单个服务器扛不住?那就用多个!
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
策略 3:流量削峰 📊
防患于未然,在流量达到峰值前就采取措施:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
策略 4:降级机制 🔧
系统扛不住时,优雅降级:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
性能对比测试
让我们对比不同策略的效果:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
实战策略拆解
Redis 缓存配置
验证要点
- 命令只用于验证系统状态,读者不需要记具体参数。
入口层限流策略
入口配置要点
- 入口层负责接住流量,并把请求转发到后端服务。
- 限流和连接控制放在入口层,可以更早保护后端。
- 代理配置的重点是保留必要请求信息,方便后端识别来源和生成日志。
本章小结
面对流量洪峰,我们的防御体系包括:
| 防御层 | 作用 | 效果 |
|---|---|---|
| 缓存层 | 减少数据库查询 | 80%+ 命中率,降低 80% 数据库负载 |
| 负载均衡 | 分散请求压力 | 线性扩展,支持更多并发 |
| 流量削峰 | 防止系统过载 | 优雅降级,避免崩溃 |
| 熔断机制 | 快速故障转移 | 快速恢复,减少影响范围 |
关键指标
| 指标 | 目标值 | 监控方法 |
|---|---|---|
| 缓存命中率 | > 80% | Redis INFO |
| 平均响应时间 | < 100ms | 应用监控 |
| 错误率 | < 1% | 日志分析 |
| 服务器负载 | < 70% | 系统监控 |
设计原则
- 缓存优先:尽量使用缓存,减少数据库访问
- 降级处理:宁可降级也不崩溃
- 监控预警:及时发现异常,主动处理
- 弹性伸缩:根据流量动态扩缩容
下一步
我们解决了流量洪峰问题,但还有一个关键挑战:如何在分布式环境中生成全局唯一的短链接 ID?
答案:分布式 ID 生成器。我们将在下一章深入探讨。
练习题
练习 1
如果缓存命中率只有 60%,流量从 1000 QPS 增加到 10000 QPS,数据库负载增加了多少?
参考答案
计算过程:
无缓存时的数据库负载
- 1000 QPS:1000 次数据库查询/秒
- 10000 QPS:10000 次数据库查询/秒
- 增长:10000 - 1000 = 9000 次/秒
60% 缓存命中率时的数据库负载
- 1000 QPS:1000 × (1 - 0.6) = 400 次数据库查询/秒
- 10000 QPS:10000 × (1 - 0.6) = 4000 次数据库查询/秒
- 增长:4000 - 400 = 3600 次/秒
负载对比
- 无缓存:数据库负载增长 9000 次/秒
- 有缓存:数据库负载增长 3600 次/秒
- 减少:9000 - 3600 = 5400 次/秒(减少 60%)
结论: 即使只有 60% 的缓存命中率,数据库负载仍然减少了 60%。从 9000 次增长减少到 3600 次增长。
练习 2
熔断器机制的原理是什么?为什么要在系统中实现熔断?
参考答案
熔断器原理:
熔断器模式类似于家庭电路中的保险丝,当电路过载时自动切断电路,保护设备和安全。
工作机制:
- 关闭状态(Closed):正常调用,统计失败次数
- 打开状态(Open):直接返回降级结果,不执行真实调用
- 半开状态(Half-Open):允许少量请求尝试恢复
为什么要实现熔断:
防止级联故障🔥
- 当下游服务故障时,防止大量请求堆积
- 避免故障向整个系统扩散
快速恢复⚡
- 避免等待超时(可能 30-60 秒)
- 立即降级,用户体验更好
资源保护💪
- 避免浪费资源在已故障的服务上
- 让健康的服务能正常处理请求
故障隔离🚧
- 将故障服务隔离,不影响其他功能
- 便于运维人员快速定位问题
实际案例:
- 微服务调用中,A 服务调用 B 服务
- B 服务故障时,大量请求超时
- 使用熔断器后,直接返回缓存数据
- A 服务仍然可以正常工作,用户体验不受影响
练习 3
设计一个滑动窗口限流算法,如何实现?
参考答案
滑动窗口限流设计:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
滑动窗口 vs 固定窗口:
| 特性 | 滑动窗口 | 固定窗口 |
|---|---|---|
| 精度 | 精确(1秒内) | 粗略(60秒内) |
| 实现 | 较复杂(需要维护时间戳) | 简单(计数器) |
| 性能 | 稍低(时间戳操作) | 较高(简单计数) |
| 公平性 | 更公平 | 不公平(边界问题) |