TT Lab
开始
学习 学习路径 课程

Envoy 内部结构

让它变慢比直接切断更重要

在 TT Lab 中继续学习

一句话总结

请求在到达路由之前,要先经过过滤器链。链是有顺序的,末尾必须是真正把请求发出去的终端过滤器(router)。在它前面放什么,决定了故障注入、速率限制、认证、头操作能够不修改应用代码就附加上去。

为什么需要它

回答“支付服务变慢时,订单服务会怎样”这个问题,有两种方法。一种是读代码并推理,另一种是真的把它变慢试一试。前一种方法会自信地给出错误的答案——因为超时设在哪里、连接池有多少个、重试几次,这些散落在代码的许多地方。

在代理中插入过滤器,就有可能采用后一种方法。应用甚至不知道自己成了实验对象,也不需要部署。只要改配置,再恢复原样即可。

速率限制也是出于同样的原因放在这里。如果每个服务各自实现速率限制,每种语言会使用不同的库,行为也会有细微的差别。在代理中做一次,所有服务就都得到了相同的规则。

工作原理

顺序有意义。过滤器按书写的顺序查看请求。所以认证过滤器通常放在速率限制之前(没有理由给连认证都没通过的请求消耗令牌),而 router 必须放在最后。router 是把请求发往上游并取回响应的终端过滤器,在它后面放任何东西,都永远不会被执行。Envoy 不会放任这种状态在运行时存在,而是在读取配置时就拒绝。

故障注入(fault)可以做两件事。

两者都可以用 percentage 设置比例,用 headers 设置条件。设置条件才是关键。不设条件就启用,所有用户都会成为实验对象。而且比例是对每个请求独立抽取的概率,并不是“二十次中恰好十次”。

这里有一个经常被误解的地方——让请求变慢的实验比中断请求的实验更重要。中断会立刻发现,而变慢则是连接堆积、线程被占用之后才变成故障。超时和断路器是否设置得当,只有通过 delay 才会暴露出来。

本地速率限制(local_ratelimit)是令牌桶。最多装入 max_tokens 个令牌,每隔 fill_interval 补充 tokens_per_fill 个。没有令牌时返回 429。重要的性质在名称里的“本地”二字——只在该代理内部计数。如果有十台代理,整体限额实际上就是十倍。作为交换,它不向外部查询,所以没有延迟,该服务挂掉也不受影响。

同一个过滤器在不同位置使用不同配置。过滤器挂在监听器上,但配置可以按路由或虚拟主机为单位覆盖(typed_per_filter_config)。只对登录路径严格、对查询路径宽松,这样的需求就是这样解决的。把全局配置关闭、只在路由上开启,也是常见的做法。

在现场相遇的样子

开启故障注入后忘记了的情况。没有设置条件、以 5% 开启的 abort,几周之后会以“偶尔出现 500”的形式找上门来。故障注入一定要设置条件,把启用的事实记录在某个地方,结束后就删除。

速率限制的限额与实际不符的情况。如果计算时没有把代理的台数考虑进去,限额就会是本意的数倍。反过来,如果按台数除开,流量集中到某一台时又会被冤枉地拦住。要根据统计数据中 enabled 和 rate_limited 的比例来调整。

开启了速率限制,却没有人被拦住的情况。大多是混淆了两个旋钮。filter_enabled 是“是否把这个请求作为该过滤器的对象”,filter_enforced 是“是否真的拦截成为对象的请求”。把后者设为 0,令牌会被计数但不会拦截——这就成了在确定限额之前用真实流量测量的观察模式。统计数据中会单独保留它的数量。

调整过滤器顺序后性能变差的情况。如果把开销大的过滤器(例如向外部查询的认证)放在链的前面,那么对最终也会被拦住的请求,也要付出这份开销。基本原则是把便宜且能筛掉很多请求的过滤器放在前面。

官方文档:Fault Injection · Local rate limit · HTTP filters

下一项实验要做什么

先观察把终端过滤器放在非末尾位置时配置被拒绝,然后亲手加入带条件的中断和固定延迟。接着只按比例中断,统计二十次中有几次命中,用令牌桶制造 429,再为每条路由设置不同的过滤器配置,让其中一条变得严格。最后读取过滤器留下的统计数据。