Envoy Redis Proxy 网络过滤器详解:RESP3 协议协商、故障注入与上游认证配置指南
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
本篇以 Envoy 官方文档中的 Redis proxy 网络过滤器配置说明为主体,系统讲解该过滤器在监听器上的配置方式、统计指标体系、故障注入机制、RESP2/RESP3 协议版本协商行为、MOVED/ASK 重定向的 DNS 解析,以及上游 Redis 认证与 AWS IAM 认证的完整配置方法。结合仓库中 api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto 的字段定义与 source/extensions/filters/network/redis_proxy/config.cc 的工厂实现,帮助你在生产环境中正确配置并验证 Redis 代理链路。
过滤器定位与基础配置
Redis proxy 是 Envoy 的一个 L4 网络过滤器,在监听器上对 Redis(RESP 协议)流量进行解码、路由到上游 Redis 兼容后端、统计与故障注入。官方架构说明见 Redis 代理架构概览。
该过滤器需以类型 URLtype.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy配置。以下是一个可直接运行的最小静态配置(取自仓库示例 redis-fault-injection.yaml 的监听器与集群骨架):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 6379 filter_chains: - filters: - name: envoy.filters.network.redis_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats prefix_routes: catch_all_route: cluster: redis_cluster settings: op_timeout: 5s clusters: - name: redis_cluster connect_timeout: 1s type: STRICT_DNS load_assignment: cluster_name: redis_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: redis.example.com port_value: 6379几个必填约束值得注意(对应 redis_proxy.proto 中的 validate 规则):
stat_prefix为必填字符串,是所有过滤器的统计前缀;settings(ConnPoolSettings)为必填消息,其中op_timeout是必填 Duration。从 proto 注释看,计时器在 pipeline 首条命令写入后端连接时启动,之后每收到一个 Redis 响应就重置计时器;若连接尚未建立,则由集群的connect_timeout接管超时逻辑;- 必须至少配置一条前缀路由或 catch-all 路由,否则在过滤器工厂中会直接抛出异常。在 config.cc 中可以看到,当
prefix_routes.routes_size() == 0 && !prefix_routes.has_catch_all_route()时会抛出"cannot configure a redis-proxy without any upstream"。
prefix_routes采用最长前缀匹配:多条路由重叠时 Envoy 始终优先选择最长匹配的前缀。proto 中给出的例子是ab→cluster_a、abc→cluster_b时,get abc:users命中cluster_b、get ab:users命中cluster_a、不匹配的键返回 NoUpstreamHost 错误(若配置了 catch-all 则转发过去)。
统计指标体系
过滤器级统计(redis.<stat_prefix>.*)
每个配置的 Redis proxy 过滤器都在redis.<stat_prefix>.*命名空间下暴露如下统计:
| 名称 | 类型 | 描述 |
|---|---|---|
| downstream_cx_active | Gauge | 当前活跃连接总数 |
| downstream_cx_protocol_error | Counter | 协议错误总数 |
| downstream_cx_rx_bytes_buffered | Gauge | 当前已缓冲的接收字节数 |
| downstream_cx_rx_bytes_total | Counter | 接收字节总数 |
| downstream_cx_total | Counter | 连接总数 |
| downstream_cx_tx_bytes_buffered | Gauge | 当前已缓冲的发送字节数 |
| downstream_cx_tx_bytes_total | Counter | 发送字节总数 |
| downstream_cx_drain_close | Counter | 因 draining 而关闭的连接数 |
| downstream_rq_active | Gauge | 当前活跃请求数 |
| downstream_rq_noproto | Counter | 在protocol_version: RESP3监听器上、先于HELLO 3握手到达而被以-NOPROTO拒绝的数据命令数 |
| downstream_rq_total | Counter | 请求总数 |
其中downstream_rq_noproto是 RESP3 模式特有的观测点:当客户端未完成HELLO 3握手就发送数据命令时(包括未知命令,详见下文协议版本一节),该计数器递增。
命令拆分器统计(redis.<stat_prefix>.splitter.*)
过滤器会为命令拆分器(command splitter)收集如下统计:
| 名称 | 类型 | 描述 |
|---|---|---|
| invalid_request | Counter | 参数个数不正确的请求数 |
| unsupported_command | Counter | 拆分器不识别的命令数 |
按命令统计(redis.<stat_prefix>.command. .*)
过滤器会为每条 Redis 命令收集以下统计,命名空间为redis.<stat_prefix>.command.<command>.*。延迟统计默认以毫秒为单位,将配置参数latency_in_micros设为 true 后可改为微秒(见 RedisProxy.latency_in_micros 字段,其注释说明该设置目前不适用于上游命令统计):
| 名称 | 类型 | 描述 |
|---|---|---|
| total | Counter | 命令数 |
| success | Counter | 成功的命令数 |
| error | Counter | 返回部分或完整错误响应的命令数 |
| latency | Histogram | 命令执行时间,毫秒(含延迟故障注入的时间) |
| error_fault | Counter | 被注入错误故障的命令数 |
| delay_fault | Counter | 被注入延迟故障的命令数 |
注意一个细节:当HELLO由外部认证服务(external auth provider)裁决时,其结果在延迟认证往返完成后才从过滤器发出,因此只会递增command.hello.total,不会递增command.hello.success与command.hello.error。所以对纯HELLO命令,total可能大于success + error;认证结果需通过外部认证服务自身的指标和下游响应来观察。
Runtime 运行时开关
Redis proxy 过滤器支持以下运行时设置:
redis.drain_close_enabled:当服务器处于 draining 状态且本应尝试 drain close 时,对多少比例的连接执行 drain close。默认值为 100(百分比)。
该值可通过 runtime 在运行中动态调整,无需重启,便于在滚动发布/缩容时精细控制下游连接的收敛速度。
故障注入(Fault Injection)
Redis 过滤器支持故障注入,目前支持 Delay 与 Error 两种故障:Delay 故障延迟请求,Error 故障以错误响应应答,且错误故障可以附带延迟。
关键约束(与官方文档一致):
- 过滤器不对配置正确性做校验。由于百分比可以在运行时修改,请求时验证正确性开销太大,因此保证默认百分比与 runtime 百分比都正确是用户的责任。
- 同一命令组合的故障注入百分比之和不应超过 100%。例如两条故障:一条对 GET 以 60% 注入,另一条对所有命令以 50% 注入——这是错误配置,因为 GET 命令将有 110% 的概率被注入故障,等价于每个请求都有故障。
- 延迟是可加和的。如果请求本身耗时 400ms,注入 100ms 延迟,则总延迟为 500ms。另外,由于 Redis 协议的实现约束(代理必须保持收到命令的顺序),被延迟的请求会连带延迟其后到达的所有请求。
- 故障必须显式设置
fault_enabled字段,默认不启用(既不设默认值也不设 runtime key 时故障不生效)。
示例配置(完整示例见 redis-fault-injection.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 6379 filter_chains: - filters: - name: envoy.filters.network.redis_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats prefix_routes: catch_all_route: cluster: redis_cluster settings: op_timeout: 5s faults: - fault_type: ERROR fault_enabled: default_value: numerator: 10 denominator: HUNDRED runtime_key: "bogus_key" commands: - GET - fault_type: DELAY fault_enabled: default_value: numerator: 10 denominator: HUNDRED runtime_key: "bogus_key" delay: 2s该配置创建两条故障:一条仅作用于 GET 命令的错误故障(10%),一条作用于所有命令的延迟故障(10%)。按上文叠加规则,20% 的 GET 命令会被注入故障。
从实现看,faults配置在过滤器工厂中被组装为FaultManagerImpl并交给命令拆分器持有(见 config.cc),延迟/错误的实际执行发生在拆分器路由命令的链路上,这也解释了为什么延迟统计中的latency直方图"包含延迟故障时间"。另外,proto 中RedisFault.commands字段注释指出:未指定commands时故障作用于除 AUTH 与 PING 之外的所有命令,因为这两个命令在 Envoy 内部有专门处理路径。
RESP 协议版本(protocol_version)
protocol_version字段(见 redis_proxy.proto)定义监听器使用的 RESP 协议版本,且该值同时约束下游客户端连接与所有被路由的上游连接池——不存在按集群单独设置 RESP 版本的旋钮,也没有跨集群的隐式最低版本。在上游路由的数据路径上,下游与上游使用同一 RESP 版本(本地生成的响应——AUTH/QUIT/NOPROTO——按下游协商出的版本编码)。
RESP2(默认)
protocol_version未设置或为RESP2(默认)时,协商出的线上版本为 RESP2:不向上游发送HELLO 3,下游的HELLO 3会被以-NOPROTO拒绝。需要强调的是,RESP3 感知能力(本地HELLO应答、CLIENT SETINFO/SETNAME的接受、RESP3 解码器)无论该值取什么都始终存在,RESP2只控制协商出的线上版本。
RESP3 模式的行为约定
当protocol_version为RESP3时:
- 上游必须支持 RESP3。所有被路由的上游 Redis 兼容后端都必须支持
HELLO 3/ RESP3(Redis 6.0+,RESP3 的引入版本)。配置错误时,每条连接的 HELLO 3 协商都会失败,表现为集群作用域下upstream_resp3_hello_failure计数器的递增。 - 上游连接建立时发送 HELLO 3。上游客户端在每条新的上游连接上发送
HELLO 3(配置了静态凭据或 AWS IAM 认证时会与AUTH合并发送)。协商完成前提交的用户请求会被缓冲,在HELLO与(对非 Primary 读策略需要的)READONLY都成功后按序重放。若上游拒绝 RESP3,连接被关闭,缓冲中的请求在上游侧失败,以便调用方在新连接上重试并重新协商。 - 下游客户端必须先显式
HELLO 3握手。在未协商 RESP3 的连接上,除HELLO、AUTH、QUIT之外的任何命令都会被以-NOPROTO拒绝——包括未知命令。未知命令此时也显示为-NOPROTO而非拆分器惯常的ERR unknown command,目的是让运维侧的错误信息始终指向"客户端未完成握手",而不是掩盖缺失握手的问题。 - 裸
HELLO与HELLO N都会与监听器的protocol_version做精确匹配:在刚建立的 RESP3 监听器连接上发送裸HELLO会被拒绝,因为连接当前版本(默认2)与要求的3不匹配;HELLO 3成功后再发裸HELLO则确认(reaffirm)已协商的版本。
HELLO 携带认证与本地合成响应
- 下游
HELLO N AUTH <user> <pass>既支持与本地配置的凭据(downstream_auth_passwords/downstream_auth_username)做匹配,也支持与外部认证服务(external auth provider)交互;后者会延迟往返,并在认证服务响应后发出延迟的HELLOMap(或错误)。 - 返回给下游客户端的
HELLO响应是代理本地合成的,不来自、也不反映任何上游 Redis 服务器。多个字段因此是固定的代理特定值而非后端值:server为envoy-redis-proxy、version是固定的 Redis 兼容版本号(6.0.0,为客户端库兼容性而宣告,而非 Envoy 构建版本)、id为0、mode为standalone、role为master、modules为空。只有proto是动态的——它反映协商出的版本(2或3)。依赖这些字段的客户端(例如按server版本号或连接id做行为分支的客户端)不应期望它们与数据命令实际路由到的上游 Redis 一致。 - 代理不在 RESP2 与 RESP3 之间做上游响应的交叉编码。由于监听器强制上游路由数据路径使用单一 RESP 版本,上游响应总是以它到达时的 RESP 版本原样发往下游;不做透明重构(例如 RESP3 Map → 扁平 RESP2 数组),从而避免
ZRANGE WITHSCORES这类命令在 RESP3 下返回嵌套成对数组、RESP2 下返回扁平数组的结构分歧。 - 唯一的例外是跨分片聚合的集群作用域命令(例如 Redis Cluster 上的
CONFIG GET与KEYS):它们的响应总是以扁平数组发出,即便某个 RESP3 上游分片返回的是 Map。将分片响应聚合进一个 Map 会迫使客户端处理跨分片的重复键,因此对 RESP2 与 RESP3 下游都发出稳定的扁平数组。
MOVED/ASK 重定向上的 DNS 解析
架构概览中说明过:当 Envoy 看到包含主机名的 MOVED 或 ASK 响应时,默认不做 DNS 解析,而是把错误原样冒泡给客户端。以下配置开启了对此类响应的 DNS 解析,从而避免客户端收到错误、由 Envoy 自身完成重定向(完整示例见 redis-dns-lookups.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 6379 filter_chains: - filters: - name: envoy.filters.network.redis_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats prefix_routes: catch_all_route: cluster: redis_cluster settings: op_timeout: 5s enable_redirection: true dns_cache_config: name: dns_cache_for_redis dns_lookup_family: V4_ONLY max_hosts: 100对应字段说明(见 ConnPoolSettings):
enable_redirection(默认 false):接受上游 Redis 服务器返回的 MOVED/ASK 重定向错误并重试到目标服务器;目标服务器无需被集群管理器知晓;若命令无法重定向,原始错误原样向下游传递;dns_cache_config:enable_redirection为 true 时,配置连接池用于解析 MOVED/ASK 响应中主机名的 DNS 缓存。若不提供该配置,则不做 DNS 解析(MOVED/ASK 错误原样传给用户);max_upstream_unknown_connections:控制任意给定 worker 线程对未知主机可创建的在途上游连接上限(默认 100);达到上限时重定向失败,原始重定向错误原样下发。
上游 Redis 认证
Redis proxy 过滤器支持对上游 Redis 集群进行认证。当配置了多个上游集群时,它们可以使用同一组用户名/密码,也可以按集群分别配置(只要凭据能与对应集群关联),过滤器会分别正确认证。
两种方式(对应 RedisProtocolOptions 消息):
- 所有上游集群共用同一凭据:使用集群
typed_extension_protocol_options中RedisProtocolOptions顶层的auth_username与auth_password; - 按端点分别配置凭据:使用
RedisProtocolOptions顶层的credentials字段。每条Credential的address字段用于把凭据关联到load_assignment.endpoints.lb_endpoints.endpoint中的具体上游端点,两处的address取值必须一致,且该模式只支持 socket 地址。若credentials中没有与某端点匹配条目,则回退使用顶层auth_password/auth_username作为默认值;同一address有多条条目时取第一条。
完整示例(见 redis-upstream-auth.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 6379 filter_chains: - filters: - name: envoy.filters.network.redis_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: egress_redis settings: op_timeout: 5s prefix_routes: catch_all_route: cluster: redis_cluster clusters: - name: redis_cluster connect_timeout: 1s type: STRICT_DNS load_assignment: cluster_name: redis_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: endpoint_1 port_value: 6380 - endpoint: address: socket_address: address: endpoint_2 port_value: 6381 - endpoint: address: socket_address: address: endpoint_3 port_value: 6382 typed_extension_protocol_options: envoy.filters.network.redis_proxy: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_username: inline_string: default_username auth_password: inline_string: default_password credentials: - address: socket_address: address: endpoint_1 port_value: 6380 auth_username: inline_string: endpoint_1_username auth_password: inline_string: endpoint_1_password - address: socket_address: address: endpoint_2 port_value: 6381 auth_username: inline_string: endpoint_2_username auth_password: inline_string: endpoint_2_password - address: socket_address: address: endpoint_3 port_value: 6382 auth_username: inline_string: endpoint_3_username auth_password: inline_string: endpoint_3_passwordRedisProtocolOptions挂在集群的typed_extension_protocol_options下,键名为envoy.filters.network.redis_proxy。
AWS IAM 认证(ElastiCache / MemoryDB)
Redis proxy 过滤器还支持使用 AWS IAM 凭据认证到 ElastiCache 与 MemoryDB 实例,相关字段提供在集群的 Redis 设置中(即上一节的typed_extension_protocol_options)。要点(对照 AwsIam 消息定义):
region未指定时,region 会按 AWS 区域提供者链推断;cache_name必填,设置为你的 cache 名称;auth_username与cache_name都会参与 IAM 认证令牌的计算;auth_password在 AWS IAM 配置中不被使用,密码值由 Envoy 自动计算;- 上游集群中
auth_username字段必须配置为已加入你 cache 的用户(按 AWS 侧 IAM 认证 Setup 流程创建 IAM-enabled 用户);不同上游可以使用不同用户名与不同 cache 名称,凭据会按流量目标集群正确生成; service_name对 valkey 或 Redis OSS 模式的 ElastiCache cache 应为elasticache,对 MemoryDB 集群应为memorydb。service_name与关联 IAM 策略中添加的服务一致——例如service_name: memorydb对应包含memorydb:ConnectAction 的 AWS IAM 策略,且该策略必须附加到 Envoy 所使用的 IAM principal 上;- proto 中还定义了
credential_provider(可指定具体的 AWS 凭据提供者链或特定提供者设置)与expiration_time(IAM 令牌到期秒数,默认 60s,最大 900s;到期后自动触发新令牌生成)。
配置示例(见 redis-aws-iam-auth.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 6379 filter_chains: - filters: - name: envoy.filters.network.redis_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: egress_redis settings: op_timeout: 5s prefix_routes: catch_all_route: cluster: redis_cluster clusters: - name: redis_cluster connect_timeout: 1s type: STRICT_DNS load_assignment: cluster_name: redis_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: testcache-7dh4z9.serverless.apse2.cache.amazonaws.com port_value: 6379 typed_extension_protocol_options: envoy.filters.network.redis_proxy: "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_username: inline_string: test aws_iam: region: ap-southeast-2 service_name: elasticache cache_name: testcache expiration_time: 900s从工厂实现看,AWS IAM 认证器在过滤器构建阶段逐集群装配:config.cc 对prefix_routes中出现的每个唯一集群检查其是否携带AwsIam元素;若存在且顶层auth_username非空,则通过AwsIamAuthenticatorFactory::initAwsIamAuthenticator为该连接池创建认证器;若缺少auth_username,则输出警告"No auth_username found for cluster {}, AWS IAM Authentication will be disabled for this cluster"并禁用该集群的 IAM 认证。
实现结构与源码索引
结合仓库源码结构,Redis proxy 过滤器的核心实现集中在 source/extensions/filters/network/redis_proxy/ 目录:
- config.cc:过滤器工厂。收集路由涉及的全部唯一集群(含镜像与读命令策略集群),为每个集群创建
ConnPool::InstanceImpl(注入集群统计作用域cluster.<name>.redis_cluster、DNS 缓存、AWS IAM 认证器、监听器级protocol_version),组装PrefixRoutes路由器、FaultManagerImpl与命令拆分器,最终为每个下游连接挂接ProxyFilter读过滤器。外部认证服务(external_auth_provider)的 gRPC 客户端同样在此创建,默认超时 200ms; - proxy_filter.cc / proxy_filter.h:下游连接的解码/编码与统计计数入口,
redis.<stat_prefix>.*过滤器级统计在此维护; - command_splitter_impl.cc:命令拆分与路由,
splitter.*统计及按命令统计在此收集,故障注入也在这条链路上生效; - conn_pool_impl.cc / router_impl.cc:上游连接池(含 MOVED/ASK 重定向、读策略路由)与前缀路由匹配;
- external_auth.cc:外部 gRPC 认证客户端,对应
RedisExternalAuthProvider配置; - 共享的客户端、编解码器与 AWS IAM 实现位于 source/extensions/filters/network/common/redis/(如
aws_iam_authenticator_impl.h、RESP 编解码器)。
API 定义统一在 redis_proxy.proto,关键消息为RedisProxy(监听器级配置,含stat_prefix、settings、prefix_routes、faults、protocol_version、下游认证与custom_commands等 12 个字段)、RedisProtocolOptions(集群级认证,含credentials与aws_iam)与AwsIam。测试用例覆盖在 test/extensions/filters/network/redis_proxy/ 目录,验证路由、故障注入、重定向等行为的正确性。
适用前提与限制小结
- 本文所有配置基于当前仓库中 v3 API(
envoy.extensions.filters.network.redis_proxy.v3),过滤器注册名为envoy.filters.network.redis_proxy(另保留旧名envoy.redis_proxy的兼容注册,见 config.cc); protocol_version: RESP3要求所有被路由的上游后端支持 RESP3(Redis 6.0+);上游配置错误时以upstream_resp3_hello_failure计数器暴露,缓冲中的下游请求随连接关闭而失败;- 故障注入的百分比正确性由用户负责,过滤器不做运行时校验;延迟注入受命令顺序约束会级联延迟后续请求;
- 按端点区分凭据(
credentials字段)仅支持 socket 地址,且必须与集群lb_endpoints中的地址逐字段一致; latency_in_micros只影响过滤器级按命令延迟统计,不影响上游命令统计的单位。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考