本文将从原理、配置、实战案例到注意事项,全面解析 Envoy 的镜像流量能力,助你高效落地这一关键运维实践。
在现代云原生架构里,服务变更的“安全上线”始终是运维团队的核心挑战。新版本上线前,心里总得打个问号:逻辑对不对?性能扛不扛得住?会不会有兼容性问题?直接拿用户当“小白鼠”风险太高,可光靠模拟数据测试又总觉得差点意思。这时候,Envoy 提供了一种堪称优雅的解决方案——请求镜像(Request Mirroring)。通过几行配置,就能把线上真实的流量,悄无声息地复制一份到测试环境,实现所谓的“影子测试”(Shadow Testing),为系统的稳定性上了一道实实在在的保险。
接下来,我们就从原理到实战,把 Envoy 这项关键能力掰开揉碎了讲清楚。

1. 什么是 Request Mirror?
简单来说,请求镜像就是 Envoy 在处理主请求的同时,异步地复制一份完全相同的 HTTP 请求(包括 headers、body、method 等所有细节),发送到你指定的另一个“镜像后端”。
这里有三个关键点需要把握:
不影响主请求:镜像请求是异步执行的,它的响应会被直接丢弃,因此完全不会干扰原始请求的处理路径和返回结果,对用户而言无感。
100% 流量复制(可选):默认情况下,所有匹配路由规则的请求都会被镜像。当然,你也可以通过运行时配置或百分比控制,只采样一部分流量,灵活性很高。
适用于调试与验证:这个功能最常见的用途,就是在新服务上线前进行影子测试。用真实的生产流量来“喂养”测试环境,得出的验证结果可信度自然高出一大截。
2. 实战:Request Mirror
光说不练假把式,下面我们分几种常见场景来实战演练。主要分为两大类:复制所有流量,以及按需复制部分流量。
(1) 复制所有流量
这是最基础的用法。首先,准备并重启 Envoy 配置文件,核心在于路由配置中的 request_mirror_policies 字段:
static_resources:
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
...
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: simple_prod
# 关键两行配置
request_mirror_policies:
- cluster: simple_test
clusters:
- name: simple_prod
...
# 定义复制目标集群
- name: simple_test
lb_policy: ROUND_ROBIN
type: STATIC
load_assignment:
cluster_name: simple_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address: { address: 172.139.20.3, port_value: 8090 }
配置完成后,发起请求并观察日志:
$ curl localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
$ curl localhost:10000/who/hostname
e56a6705cd3c
# prod环境日志
{"time":"2025-11-20 15:28:16.6123","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:49252","cost":"534.582µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:28:21.7515","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/who/hostname","RemoteAddr":"172.17.0.1:49252","cost":"65.323µs","UserAgent":"curl/7.29.0"}
# test环境日志(可以看到相同的请求也被处理了)
{"time":"2025-11-20 15:28:16.6125","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.139.20.170:33888","cost":"625.003µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:28:21.7522","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/who/hostname","RemoteAddr":"172.139.20.170:33888","cost":"64.864µs","UserAgent":"curl/7.29.0"}
可以看到,生产环境和测试环境都收到了完全相同的请求,但主请求的响应并未受影响。
(2) 复制部分流量
实际场景中,我们往往不需要复制所有流量。Envoy 提供了多种精细化的控制方式。
① 只复制特定接口的流量
例如,只复制路径前缀为 /who/ 的接口流量:
static_resources:
listeners:
- filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
...
route_config:
name: local_route
virtual_hosts:
# 只复制某个接口下的流量
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/who/"
route:
cluster: simple_prod
# 重要配置:仅匹配此路由的请求会被镜像
request_mirror_policies:
- cluster: simple_test
- match:
prefix: "/"
route:
cluster: simple_prod
clusters:
- name: simple_prod
...
- name: simple_test
...
验证一下,只有访问 /who/hostname 的请求被复制了:
$ curl localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
$ curl localhost:10000/who/hostname
e56a6705cd3c
# prod日志(两个请求都正常处理)
{"time":"2025-11-20 15:36:02.4522","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:56804","cost":"2.323416ms","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:36:07.7700","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/who/hostname","RemoteAddr":"172.17.0.1:56804","cost":"333.145µs","UserAgent":"curl/7.29.0"}
# test日志(只有 /who/hostname 请求被镜像过来)
{"time":"2025-11-20 15:36:07.7725","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/who/hostname","RemoteAddr":"172.139.20.170:41676","cost":"501.9µs","UserAgent":"curl/7.29.0"}
② 按照百分比复制流量
当流量巨大时,全量镜像可能给测试集群带来压力。这时可以按比例采样。配合运行时(Runtime)配置,还能实现动态调整:
# 启用运行时动态配置层
layered_runtime:
layers:
- name: admin
admin_layer: {}
static_resources:
listeners:
- filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
...
route_config:
name: local_route
virtual_hosts:
# 复制百分之五十流量,百分比可动态修改
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: simple_prod
# 主要配置
request_mirror_policies:
- cluster: simple_test
runtime_fraction:
default_value:
numerator: 50 # 分子,即50%
denominator: HUNDRED # 分母为100
runtime_key: mirror.simple.percentage
clusters:
- name: simple_prod
...
- name: simple_test
...
初始配置为50%采样,发起两次请求,可能只有一次被镜像:
$ curl localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
$ curl localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
# prod日志(两次请求都处理了)
{"time":"2025-11-20 15:48:25.6834","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:37872","cost":"89.564µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:48:27.5683","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:37872","cost":"46.865µs","UserAgent":"curl/7.29.0"}
# test日志(仅第一次请求被镜像)
{"time":"2025-11-20 15:48:25.6843","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.139.20.170:52362","cost":"395.113µs","UserAgent":"curl/7.29.0"}
更强大的是,我们可以通过 Admin API 动态修改这个百分比,比如实时调整为100%全量镜像:
## 动态修改百分比,改成100%
$ curl -XPOST https://localhost:9901/runtime_modify?mirror.simple.percentage=100
OK
# 再次发起请求,prod和test日志都会记录所有请求,证明已生效
# prod日志
{"time":"2025-11-20 15:54:48.8263","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:50366","cost":"680.113µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:54:50.2126","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:50366","cost":"64.996µs","UserAgent":"curl/7.29.0"}
# test日志(现在两次请求都被镜像了)
{"time":"2025-11-20 15:54:48.8291","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.139.20.170:55688","cost":"1.664482ms","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 15:54:50.2131","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.139.20.170:55688","cost":"38.692µs","UserAgent":"curl/7.29.0"}
③ 基于 Header 复制流量
有时我们希望通过请求头来动态控制是否镜像。可以配置 cluster_header:
static_resources:
listeners:
- filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
...
route_config:
name: local_route
virtual_hosts:
# 只复制header包含"x-mirror-cluster"的流量
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: simple_prod
request_mirror_policies:
- cluster_header: "x-mirror-cluster"
clusters:
- name: simple_prod
...
- name: simple_test
...
这里有个关键细节:cluster_header 只读取 Header 的 key,其 value 需要填写目标集群的名称(例如 simple_test)。
$ curl --header "x-mirror-cluster: simple_test" localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
$ curl --header "x-mirror-cluster: simple_test-non-existent" localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
$ curl localhost:10000/version
{"BuildTime":"2025-09-11","CommitID":"668bb13195e51a0020c416901a3c5ef38264b945","Version":"v1.4.3"}
# prod日志(三个请求都正常处理)
{"time":"2025-11-20 16:10:32.6750","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:47028","cost":"44.075µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 16:11:01.6139","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:47028","cost":"94.634µs","UserAgent":"curl/7.29.0"}
{"time":"2025-11-20 16:11:08.4170","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.17.0.1:47028","cost":"42.22µs","UserAgent":"curl/7.29.0"}
# test日志(只有第一个请求,其header的key和value都正确匹配,流量才被复制过来)
{"time":"2025-11-20 16:10:32.6753","level":"INFO","source":"app/run.go:64","msg":"","Proto":"HTTP/1.1","Code":200,"Method":"GET","URL":"/version","RemoteAddr":"172.139.20.170:56760","cost":"125.112µs","UserAgent":"curl/7.29.0"}
3. 典型使用场景
掌握了配置方法,我们来看看这项技术能用在哪些刀刃上:
测试新版本:这是最经典的场景。将生产流量镜像到待发布的新版本服务,在真实数据面前验证功能、稳定性和兼容性,真正做到“心中有数”再上线。
性能测试:用镜像的真实流量对测试环境进行压测,得到的性能基准远比模拟数据可靠,能提前发现潜在的性能瓶颈。
调试和监控:将特定流量(如出错请求、慢请求)镜像到专门的调试集群,可以深入分析请求模式、追踪 Bug,而不影响生产服务。
数据收集:镜像流量到数据分析服务,用于用户行为分析、业务指标计算等,数据来源真实且全面。
4. 结语
总的来说,request_mirror_policies 是 Envoy 提供的一项低调却极其强大的功能。它就像在“真实战场”旁边搭建了一个完全相同的训练场,让我们能安全地演练新兵,极大降低了线上变更的风险。从全量复制到精细化的条件镜像,灵活的配置方式足以应对各种复杂场景。合理运用镜像流量,不仅能显著提升系统稳定性,更能为研发和运维团队带来实实在在的效率和信心提升。
