bigywater.tech
微服务 · 10

限流的维度:按用户、按接口、按实例

限流的桶该按什么划分:全局、用户、接口还是实例?吵闹的邻居怎样才不连累别人?每个实例各自限流会放行多少?这篇用 Go 的令牌桶中间件和虚拟时钟模拟,把几种划分方式量出来。

14% 对 100%
全局一个桶与每用户一个桶时,守规矩的用户 B 的放行比例(B 只发 20 个/秒,限额 100)
59 个
按 (用户, 接口) 分桶后,/report(限额 10/s、burst 10)5 秒内放行的数量,上界 60;/ping 同时 100% 放行
200 个/秒
3 个实例各限 100/s 时,200 个/秒轮询流量被放行的速率(目标 100)

全局桶 vs 用户桶

实测 · 本机执行输出
  做法      | 用户 | 发送速率  | 请求数  | 放行  | 放行比例
  --------+----+-------+------+-----+-----
  全局一个桶   | D  | 20/s  | 100  | 17  | 17% 
  全局一个桶   | C  | 20/s  | 100  | 19  | 19% 
  全局一个桶   | B  | 20/s  | 100  | 14  | 14% 
  全局一个桶   | A  | 300/s | 1500 | 468 | 31% 
  每个用户一个桶 | B  | 20/s  | 100  | 100 | 100%
  每个用户一个桶 | C  | 20/s  | 100  | 100 | 100%
  每个用户一个桶 | D  | 20/s  | 100  | 100 | 100%
  每个用户一个桶 | A  | 300/s | 1500 | 519 | 35% 
  (连接类错误 0 次;不是超时,应为 0)

全局桶让一个用户把额度吃光,所有人一起被限;每用户一个桶只限住吵闹的那个。

按接口分桶

实测 · 本机执行输出
  做法                           | 接口      | 请求数 | 放行  | 放行比例
  -----------------------------+---------+-----+-----+-----
  每个用户一个桶(两个接口共用 100/s)        | /ping   | 500 | 287 | 57% 
  每个用户一个桶(两个接口共用 100/s)        | /report | 500 | 232 | 46% 
  每个 (用户, 接口) 一个桶,/report 10/s | /report | 500 | 59  | 12% 
  每个 (用户, 接口) 一个桶,/report 10/s | /ping   | 500 | 500 | 100%
  (连接类错误 0 次;不是超时,应为 0)

贵的接口和便宜的接口共用额度时互相挤占;分桶后贵接口被限在自己的额度里。

按实例限流

实测 · 本机执行输出
  流量分布               | 每个实例限 100/s | 每个实例限 100/3 | 全局共享一个桶
  -------------------+-------------+-------------+--------
  轮询 1/3 : 1/3 : 1/3 | 200 个/秒     | 110 个/秒     | 110 个/秒
  倾斜 60% : 30% : 10% | 192 个/秒     | 93 个/秒      | 110 个/秒
  粘性 100% : 0 : 0    | 110 个/秒     | 37 个/秒      | 110 个/秒
  目标是 100 个/秒(令牌桶开头有 burst 个存量,10 秒里多放行 burst 个,所以“正好限住”时显示 110 个/秒)。每个实例各限 100/s:轮询时放行 200 个/秒(请求只有 200/s,3 个桶的额度加起来是 300,等于没限);倾斜时 192;粘性时 110(只有一个实例收到流量,它的桶正好限住)。每个实例限 100/3:轮询时 110;倾斜时 93,低于目标(被打得多的实例先限住,别的实例的额度用不上);粘性时 37。全局共享的桶不受流量分布影响,三行都是 110,代价是每次判断都要多一次对共享存储的访问(见下一篇)

各实例本地限流,总额度取决于流量怎么分布:轮询时等于没限,倾斜时又限得过紧;全局共享的桶不受影响,代价是访问共享存储。

分步演示

限流的维度
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。第一、二节用第 9 篇的 Go 手写令牌桶(net/http,sync.Mutex)加不同的 key 取法,Python 开环客户端走环回 HTTP;第三节是虚拟时钟下的模拟(令牌桶,burst = 限额,轮询按带种子的随机数分配)
    • 没有验证:真实负载均衡器(nginx / Envoy)的分配算法对第三节三种分布的接近程度;共享存储(Redis)做全局桶时的延迟与原子性(见下一篇);用户数很大时桶的内存回收;按租户、按 API Key 等其它维度
    京ICP备2026067502号-1京公网安备11010502063487号