Envoy Stateful Session 过滤器全解析:基于可扩展会话状态实现强会话粘性
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
Envoy 的 Stateful Session(有状态会话)HTTP 过滤器允许基于可扩展的会话状态(Session State)覆盖最终选择的上游主机,并将最终选中的上游主机写回会话状态,从而在不依赖哈希负载均衡的前提下实现"强"会话粘性(strong stickiness)。本文以官方文档 stateful_session_filter.rst 为主线,结合仓库中的 proto 定义、示例配置与 C++ 源码实现,系统讲解该过滤器的工作原理、Cookie/Header 两种会话状态扩展的配置方法、严格模式(strict)的行为差异以及统计指标体系,帮助你在一线接入网关或网格代理中正确启用并运维这一能力。
什么是 Stateful Session:从弱粘性到强粘性
会话粘性(Session Stickiness)的核心诉求是:属于同一会话(Session)的请求,应被一致地路由到同一个上游主机。Envoy 中传统的实现方式是哈希负载均衡(Hash-based Load Balancing,如envoy.lb_policy.maglev、envoy.lb_policy.ring_hash)。但哈希粘性被认为是"弱"粘性——因为粘性建立在主机集合(Host Set)的哈希映射上,一旦上游主机集合发生变化(扩缩容、摘除等),原有会话就可能被重新映射到不同的主机。
Stateful Session 过滤器实现的是强粘性:会话状态中显式记录"该会话应去往哪个上游主机",后续请求直接按记录的主机路由。原文档指出,它主要面向两类场景:
- 需要更稳定粘性的场景:例如某主机已被标记为 degraded(降级),但仍希望现有会话继续路由到该主机(此时哈希负载均衡会因为健康检查或主机集合变化而改变映射)。
- 使用非哈希负载均衡器却仍需要粘性的场景:例如使用 Random、Round Robin 等负载均衡策略时,新会话按负载均衡结果选择上游主机,而既有会话固定路由到会话记录的主机。
从源码看,该过滤器通过decoder_callbacks_->setUpstreamOverrideHost(...)将会话解析出的上游地址注入负载均衡上下文,实现对负载均衡结果的覆盖(override),且覆盖优先级高于负载均衡本身的选择(见 stateful_session.cc)。
安全与可靠性提醒
原文档特别给出note警告:Stateful Session 可能导致上游之间负载不均衡,并可能允许外部参与者将请求定向到特定的上游主机。因此在启用该功能前,运维人员必须仔细评估其安全性与可靠性影响——它本质上是把路由决策的"一部分控制权"交给了会话载体(Cookie/Header)中的内容。
配置方式
该过滤器通过 HTTP Connection Manager 下的 HTTP 过滤器链装配,必须使用类型 URL:
type.googleapis.com/envoy.extensions.filters.http.stateful_session.v3.StatefulSession对应的完整 proto 定义为 stateful_session.proto,核心配置字段如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
session_state | config.core.v3.TypedExtensionConfig | 必填(语义上最核心) | 指定会话状态实现,用于存取分配给会话的上游主机地址 |
strict | bool | false | 是否严格路由到请求的目标主机。true时若目标不存在则按status_on_strict_destination_not_found返回;若目标存在但不健康则始终返回503。false时回退到普通负载均衡 |
stat_prefix | string | 空 | 可选统计前缀;为空则不输出任何统计 |
status_on_strict_destination_not_found | uint32 | 503 | 严格模式下目标主机不在可用端点集合中时返回的 HTTP 状态码;strict为false时该字段被忽略;设为0或不设置时取默认503 |
此外还提供StatefulSessionPerRoute用于路由级覆盖(见 stateful_session.proto),它通过oneof override支持两种形式:
disabled:在特定 vhost 或 route 上显式禁用该过滤器(多个 per-filter-config 同时存在时取最具体的配置);stateful_session:为路由提供一份独立的StatefulSession配置(可通过 RDS 下发)。
工作原理:会话状态扩展点
该过滤器最关键的配置项是session_state这个可扩展会话状态(Extensible Session State)。处理请求时,过滤器会基于请求检索对应的会话及其上游主机,检索结果将影响最终的负载均衡结果;若未找到既有会话,则创建会话用于存储选中的上游主机。需要强调的是,这里的"会话"是抽象概念,具体的存储细节完全取决于会话状态实现(例如存在 Cookie 里,还是存在响应 Header 里)。
结合 stateful_session.cc 的源码,可梳理出完整的请求/响应处理链路:
- 请求阶段(
decodeHeaders):解析出最具体的 per-route 配置(若路由级禁用则直接Continue);通过session_state中指定的工厂创建会话状态实例;若会话中解析出上游地址(upstreamAddress()有值),则调用setUpstreamOverrideHost注入覆盖主机、strict 开关与严格模式失败状态码。 - 响应阶段(
encodeHeaders):若请求阶段没有会话状态,且过滤器处于激活状态(未被 per-route 禁用)、请求已到达上游,则累加no_session统计;若有会话状态且拿到了最终上游主机,则调用会话状态的onUpdate(host_address, headers)——若最终主机与会话记录不一致(说明覆盖失败发生了回退),则根据 strict 模式标记failed_open或failed_closed;若一致则标记routed。
会话状态是通过扩展工厂(SessionStateFactory)按session_state.name动态查找并实例化的,具体机制在 stateful_session.cc。若配置中完全未指定session_state,则会使用EmptySessionStateFactory(返回空会话),此时过滤器不产生任何粘性行为(见 stateful_session.cc)。
官方示例:Cookie 型会话状态
目前官方支持三类会话状态扩展,其中文档详细讲解的是Cookie 型(envoy.http.stateful_session.cookie)与Header 型(envoy.http.stateful_session.header)。以下为文档内嵌的 Cookie 型完整示例(原样取自 stateful-cookie-session.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: service1 http_filters: - name: envoy.filters.http.stateful_session typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.stateful_session.v3.StatefulSession session_state: name: envoy.http.stateful_session.cookie typed_config: "@type": type.googleapis.com/envoy.extensions.http.stateful_session.cookie.v3.CookieBasedSessionState cookie: name: global-session-cookie path: /path ttl: 120s - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: service1 load_assignment: cluster_name: service1 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8080该配置的行为:Cookie 型会话状态从名为global-session-cookie的 Cookie 中解析当前会话应覆盖的上游主机;若该主机确实存在于上游集群中,请求将被路由到该主机。若请求没有有效 Cookie,则负载均衡器正常挑选一个新上游主机,并在响应阶段把选中的上游主机地址写入名为global-session-cookie的 Cookie(通过Set-Cookie响应头)。
Cookie 值格式与生命周期细节
从 cookie.proto 可以看到,Cookie 型扩展把负载均衡器选中的上游地址编码进Set-Cookie响应头;收到新请求时按 Cookie 名解析上游地址,若地址对应有效上游主机则优先选择。其编码形式为 Base64(文档示例中sticky-host="MS4yLjMuNDo4MA=="即1.2.3.4:80的 Base64 表示)。需要说明的是,根据 cookie.cc 的当前实现,写入 Cookie 的内容实际是将上游地址(连同可选的过期时间expires)序列化后的消息再做 Base64 编码,并且仅在最终选择的主机与会话记录不一致(或原会话不存在)时才更新 Cookie。
stateful_session_filter.rst 中演示的cookie字段支持:
name:Cookie 名(示例为global-session-cookie);源码 cookie.cc 会校验其非空,否则抛异常;path:Cookie 的路径属性(示例为/path),同时它还被用作请求路径匹配器:空路径或/匹配所有请求;以/结尾的路径前缀匹配;否则要求请求路径与 Cookie 路径相同,或紧随其后是/、?、#之一(见 cookie.cc);ttl:Cookie 有效期(示例为120s),源码中ttl为 0 时不写入expires(会话永不过期,见 cookie.cc);此外还支持attributes附加 Cookie 属性。
官方示例:Header 型会话状态
Header 型扩展的配置同样内嵌于文档(原样取自 stateful-header-session.yaml):
static_resources: listeners: - name: listener_0 address: socket_address: address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: service1 http_filters: - name: envoy.filters.http.stateful_session typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.stateful_session.v3.StatefulSession session_state: name: envoy.http.stateful_session.header typed_config: "@type": type.googleapis.com/envoy.extensions.http.stateful_session.header.v3.HeaderBasedSessionState name: session-header - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: service1 load_assignment: cluster_name: service1 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 127.0.0.1 port_value: 8080Header 型配置仅需一个字段name(示例为session-header),用于从下游请求中读取会话值、并在响应中生成同名响应头。其 proto 定义见 header.proto:请求中若携带形如session-header: "MS4yLjMuNDo4MA=="(即1.2.3.4:80的 Base64)的头部,Envoy 会优先选择1.2.3.4:80作为上游;处理上游响应时,若最终选择的主机与会话记录不一致,则把新选中主机地址 Base64 编码后写回session-header响应头(见 header.cc)。源码同样会校验name非空(header.cc)。
使用 Header 型实现时需要注意两点(原文档note原文):
- Header 型实现假定客户端会使用最后一次提供的会话头值,并在后续每个请求中携带它;
- 若需要基于路径匹配来启用/禁用粘性,应使用
StatefulSessionPerRoute进行路由级配置。
扩展生态:Envelope 型会话状态
除文档主讲的 Cookie/Header 两种外,仓库还提供了第三种官方扩展——Envelope 型(envoy.http.stateful_session.envelope,见 envelope.proto)。它适用于"会话上下文由上游服务器初始化"的场景:上游在会话首个响应中生成会话上下文(如会话 ID 头或 Cookie),客户端在后续请求中原样携带。
其处理逻辑为:处理上游响应时,若响应中包含会话上下文(无论新旧),Envoy 会把该上下文与当前上游主机"拼接"成新的会话上下文;处理下游请求时,若请求包含会话上下文,则从中剥离出上游主机。Header 模式下编码格式形如:
session-header: "MS4yLjMuNDo4MAo=;UV:eHh4eHh4Cg==" # base64(1.2.3.4:80);UV:base64(xxxxxx)其中UV(upstream value)段用于保存上游原始头值,Envoy 据此把上游地址与会话值解耦,回程时用UV段还原原始请求头。
统计指标(Statistics)
该过滤器在http.<stat_prefix>.stateful_session.命名空间下输出统计,其中第一个stat_prefix来自所属 HTTP Connection Manager 的stat_prefix配置。注意两条关键规则(原文档note):
- 过滤器自身未配置
stat_prefix时,不输出任何统计; - 若配置了过滤器级
stat_prefix,会在stateful_session之后追加一段以区分多个实例,例如http.<stat_prefix>.stateful_session.my_prefix.routed(源码中统计前缀拼接逻辑见 stateful_session.cc); - per-route 配置覆盖不支持统计——即使在 per-route 的
StatefulSession中设置了stat_prefix也不会输出统计(对应实现见 stateful_session.cc,per-route 配置传入空前缀)。
支持的统计指标定义于 stateful_session.h,汇总如下:
| 名称 | 类型 | 说明 |
|---|---|---|
routed | Counter | 尝试了会话覆盖且成功应用、最终选中的上游与会话请求目标一致的请求总数 |
failed_open | Counter | 尝试覆盖但目标不可用、随后按默认负载均衡继续处理(strict为false)的请求总数 |
failed_closed | Counter | 尝试覆盖但目标不可用、以503关闭请求(strict为true)的请求总数 |
no_session | Counter | 过滤器激活但请求到达上游时没有会话状态的请求总数,包括无会话 Cookie/Header 或会话提取失败的情况;不包含过滤器在 per-route 被显式禁用的请求 |
小结与实践建议
Stateful Session 过滤器把"粘性"从负载均衡算法中彻底解耦出来:哈希负载均衡的弱粘性依赖主机集合的稳定性,而本过滤器通过session_state扩展在请求中显式携带目标上游主机,实现了覆盖优先级高于负载均衡的强粘性。实践时的关键决策点包括:
- 会话载体选择:Cookie 型适合浏览器类客户端(自动携带、TTL 可控、支持路径匹配);Header 型适合 API/微服务间调用(需客户端配合回传头值);Envelope 型适合上游已自带会话上下文的场景。
- strict 模式取舍:追求粘性可靠性可开启
strict(目标不可达时 fail-closed 返回503),追求可用性则保持false(fallback 到负载均衡,但会积累failed_open统计)。 - 监控与安全:务必为过滤器配置
stat_prefix以获得routed/failed_open/failed_closed/no_session四类指标;同时牢记文档警告——该特性可能造成上游负载不均、并允许外部控制请求目标,需结合网络隔离与信任边界审慎启用。
如需深入验证上述行为,可直接查阅本文引用的 proto 定义、示例配置与源码文件,并参考仓库中的实现与测试代码进行二次确认。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考