为什么你写的路由不生效
一句话总结
一个请求找到目的地,要经过两次选择。先由 Host 头选出虚拟主机,再从该虚拟主机的路由表中自上而下扫描,使用第一个匹配项。前一次选择按具体程度决定,后一次选择按顺序决定——这两者不同,就是本文的全部内容。
为什么需要它
“路由明明写了,却没有命中”这类报告,有一半并不是路由的问题,而是选错了虚拟主机。
一个路由表(route_config)中可以有多个虚拟主机。每个虚拟主机都有 domains 列表,请求的 Host 头(HTTP/2 中为 :authority)必须匹配其中之一,才会去查看该虚拟主机的路由表。这里的匹配规则与路由不同。路由是自上而下扫描并使用第一个匹配项,而虚拟主机使用的是最具体的那一个。
1. 정확한 이름 www.foo.com
2. 접미 와일드카드 *.foo.com (더 긴 와일드카드가 먼저)
3. 접두 와일드카드 foo.*
4. 특별한 * 아무 이름에나
因此,即使把 *.example.com 虚拟主机放到文件最上面,精确写了 www.example.com 的虚拟主机仍然会获胜。要想不在调换顺序上白白耗掉一整天,就必须先了解这个区别。
* 在整个路由表中只能放一个。如果有两个,就无法确定谁更具体,所以 Envoy 不会放任它在运行时造成混乱,而是在读取配置时就拒绝。
工作原理
虚拟主机确定之后,会自上而下查看其中的 routes,在第一个匹配处停止。可用于匹配的不只有路径。
| 条件 | 含义 |
|---|---|
prefix |
路径的开头相同 |
path |
路径必须完全相同 |
safe_regex |
匹配正则表达式 |
headers |
头的值条件(精确、存在、前缀、正则) |
query_parameters |
查询字符串条件 |
同一个匹配中的条件必须全部满足。所以像“只让内部测试人员使用新版本”这样的需求,就要同时写上 prefix 和 headers 来实现。条件不满足时,会跳过该路由并继续向下扫描。这不会返回 404,而是由下一条路由接收——所以要把带条件的窄路由放在上面,把宽泛的 prefix: "/" 放在最下面。
目的地也不只有一个集群。
route.cluster—— 发往一个集群route.weighted_clusters—— 按比例分给多个集群direct_response—— 不去上游,由 Envoy 直接响应redirect—— 转到其他地址(默认是 302,永久迁移用MOVED_PERMANENTLY)
添加头的位置有三处——路由、虚拟主机、路由表。它们不是覆盖,而是各自追加。所以同名的响应头可能会留下多个。
在现场相遇的样子
权重没有按比例分配的报告。weighted_clusters 是概率,而不是分配表。而且 Envoy 的每个工作线程都保存各自的状态,在默认的 concurrency(核心数)下统计四十个请求,每次结果都不同。验证金丝雀比例时,要么把样本量放大,要么把工作线程减到一个。在生产环境中,不要说“比例对不上”,而要用统计数据来看。
用 direct_response 在不碰应用的情况下收尾。健康检查路径、维护提示、拦截机器人的响应这类应用不需要知道的事情,最好在代理处结束。这样不用部署就能修改,应用挂掉了也能应答。
滥用正则表达式导致变慢。prefix 和 path 是字符串比较,而 safe_regex 要经过正则引擎。在有几百条路由的表中,如果前面全部用正则填充,每个请求都要把它们全部跑一遍。正则只用在其他方式做不到的地方。
官方文档:HTTP routing · HTTP route components
下一项实验要做什么
放置四个虚拟主机,亲自确认具体程度的顺序,然后观察放两个 * 时配置被拒绝。接着用 path、safe_regex、headers、query_parameters 分流,用四十次请求统计 75 比 25 的权重,再加上 direct_response 和 redirect,最后确认头在三个层次上各自追加。