API 上线后,为什么最先出问题的常常不是算错,而是扛不住?

缓存与限流

约 8 分钟5 道自测更新于 2026-10-30

一句话记住缓存减少重复工作,限流控制请求速度;两者都必须和数据新鲜度、身份和错误重试策略一起设计。

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 道自测

先在心里答一遍,再展开对照。能说清理由,比答对数字更重要。

  1. 01缓存击穿、穿透、雪崩分别是什么?

  2. 02429 和 Retry-After 分别表达什么?

  3. 03令牌桶的容量和速率分别控制什么?

  4. 04为什么不同数据类型不能统一使用同一个 TTL?

  5. 05ETag 和 If-None-Match 能减少什么?

已看 0/5 题

练动手练习

任务:将令牌桶的容量从 10 改为 1,速率保持每秒 5 个,观察短时间突发请求的放行数量;再为缓存 key 增加复权方式和分页参数,验证不同参数不会共享错误结果。

完成这一课

完成这一课

读完讲义、看完自测答案,这一课就算学完了。

  • 读完讲义
  • 看完全部自测

本课为投资者教育内容,案例数字均为示意,不构成任何投资建议,也不承诺收益。规则和费率会变,实际操作前请以交易所、监管机构和产品的最新公告为准。