限流的桶该按什么划分:全局、用户、接口还是实例?吵闹的邻居怎样才不连累别人?每个实例各自限流会放行多少?这篇用 Go 的令牌桶中间件和虚拟时钟模拟,把几种划分方式量出来。
做法 | 用户 | 发送速率 | 请求数 | 放行 | 放行比例 --------+----+-------+------+-----+----- 全局一个桶 | 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,代价是每次判断都要多一次对共享存储的访问(见下一篇)
各实例本地限流,总额度取决于流量怎么分布:轮询时等于没限,倾斜时又限得过紧;全局共享的桶不受影响,代价是访问共享存储。