01学完你能做到
- 区分缓存击穿、穿透和雪崩,并选择对应的处理方式。
- 用 TTL、ETag、单飞和令牌桶改善 API 的性能与稳定性。
- 设计带 429 和 Retry-After 的限流响应。
02这课解决什么问题
热门股票在开盘时被大量重复查询,或者某个脚本失控后每秒发出几百个请求,怎样避免数据库和上游一起被拖垮?
03缓存
缓存把已经读取或计算过的结果放在更近、更快的位置。TTL(Time To Live)规定结果保持新鲜的时间。历史日 K 收盘后变化较少,可以缓存更久;盘中最新价变化快,TTL 应更短,或者由实时流主动更新。
HTTP 缓存还可使用 Cache-Control: max-age=60 表达新鲜时间,用 ETag 与 If-None-Match 做条件请求。数据未变化时返回 304 Not Modified,可以避免重新传输响应体。对过期数据采用“先返回旧值、后台刷新”时,必须明确标记数据的时间和新鲜度。
三个常见问题:
- 击穿:热点 key 刚过期,大量请求同时回源。可用单飞或分布式锁,让同一 key 同时只有一个请求回源。
- 穿透:请求本不存在的对象,永远无法命中。可短暂缓存“确实不存在”,并先做参数校验。
- 雪崩:大量 key 同时过期。可给 TTL 加随机抖动,或分批刷新。
缓存 key 必须包含影响结果的全部参数,例如代码、日期、复权方式、分页和权限范围;否则可能返回错误或越权的数据。
04限流
令牌桶用两个参数控制请求节奏:桶容量决定允许的突发量,补充速率决定长期平均速度。请求消耗令牌,没有令牌时拒绝或等待。固定窗口、滑动窗口和并发数限制也常见。
被限流时通常返回:
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json
{"code":"RATE_LIMITED","retryable":true,"detail":"请在 2 秒后重试"}限流应优先按 API Key、账号或租户设计,IP 作为兜底;多台服务实例需要共享计数,否则每台机器各自放行会让总额度失控。客户端重试应采用指数退避和随机抖动,避免重试风暴。
05一个最小令牌桶
class TokenBucket:
def __init__(self, rate, capacity):
self.rate = rate
self.capacity = capacity
self.tokens = capacity
self.last = time.monotonic()
def allow(self):
now = time.monotonic()
self.tokens = min(self.capacity,
self.tokens + (now - self.last) * self.rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False示例中 rate=5, capacity=10 表示长期平均每秒约 5 个请求,同时允许最多 10 个令牌积累出的突发;实际并发实现还需要锁或原子操作。
06延伸阅读
07一分钟讲给别人
试着用自己的话讲一遍缓存是把常见答案提前放在近处,避免每次都回源;限流是控制进入服务的速度,避免少数调用者拖垮所有人。历史数据可以缓存久一点,实时数据只能缓存很短。热点过期要防击穿,批量过期要防雪崩,被限流要回 429 和 Retry-After,客户端还要退避重试。
5 道自测
先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。
01缓存击穿、穿透、雪崩分别是什么?
02429 和 Retry-After 分别表达什么?
03令牌桶的容量和速率分别控制什么?
04为什么不同数据类型不能统一使用同一个 TTL?
05ETag 和 If-None-Match 能减少什么?
已看 0/5 题
练动手练习
任务:将令牌桶的容量从 10 改为 1,速率保持每秒 5 个,观察短时间突发请求的放行数量;再为缓存 key 增加复权方式和分页参数,验证不同参数不会共享错误结果。
完成这一课
读完讲义、看完自测答案,这一课就算学完了。
- 读完讲义
- 看完全部自测
本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。