Envoy 的 Buffer
(Buffer::OwnedImpl
) 是其高性能设计的基石之一。传统网络编程中常见的 char*
数组或 std::vector<char>
这类连续内存缓冲区,在需要频繁增长或移动数据时,会涉及大量的内存分配和数据拷贝,从而导致严重的性能开销。
为了解决这个问题,Envoy 的 Buffer
设计遵循了以下几个核心原则:零拷贝(Zero-Copy)、分片管理(Slice-based Management) 和 水位线流控(Watermark-based Flow Control)。
Envoy Buffer 最核心的设计是,它不是一块连续的内存。相反,它内部维护了一个双向队列(Buffer::SliceDeque
),队列中的每个元素是一个称为 Slice
的内存切片。
Slice
?
Slice
是一个独立的、连续的内存块。它通常包含一个指向内存的指针和数据长度。Buffer::OwnedImpl
本质上就是 Buffer::SliceDeque
。std::vector
那样在空间不足时重新分配一个更大的连续内存块,然后将旧数据拷贝过去。它只需要在队列末尾添加一个新的 Slice
即可。这使得追加(append)操作非常高效。Slice
或调整第一个 Slice
的起始指针,而无需移动后面所有的数据。基于切片链的设计,Envoy 可以实现高效的零拷贝操作,这对于代理性能至关重要。
为了进一步提升效率和减少内存碎片,Envoy 的 Buffer 在分配 Slice
时也做了优化。
Slice
通常是从一个内存池中分配的,并且大小是固定的(例如 16KB)。当需要添加少量数据时,会复用最后一个 Slice
的剩余空间;如果空间不足或没有 Slice
,则会分配一个全新的、标准大小的 Slice
。malloc
调用: 通过池化和固定大小的分配策略,Envoy 减少了对系统调用 malloc
/free
的频繁请求,从而降低了内存管理的开销。Buffer 的大小是 Envoy 实现网络流控的关键。
这种“启停式”的机制确保了 Envoy 在处理快慢不一的上下游连接时能够保持稳定和健壮。
尽管非连续内存设计非常高效,但某些场景(如与需要连续内存的库或系统调用交互)仍然需要一块完整的内存。为此,Envoy 提供了 linearize()
方法。
linearize(size)
: 这个方法会开辟一块新的连续内存,并将 Buffer 中指定长度的数据从各个 Slice
中拷贝到这块新内存里。总而言之,Envoy Proxy 的 Buffer
设计是一个高度优化的实现,其精髓在于:
Slice
链(非连续内存)代替传统连续内存,从根本上避免了昂贵的内存重分配和数据移动。move()
等操作实现高效的零拷贝,极大提升了数据在代理内部流转的性能。上图信息量比较大,有兴趣的学习可以细心参透。简要罗列以下几方面:
Buffer::Instance
的基本 add/prepend 等等读写操作Buffer::SliceDeque
的队列设计在 Envoy Proxy 中,流的 Buffer 限制主要通过流量控制机制以及与 HTTP/2 和 HTTP/3 连接相关的设置来管理。
以下是 Stream Buffer 处理方式和相关配置的详细说明:
Envoy 通过其内部 Buffer 的高水位线 (high watermark) 和低水位线 (low watermark) 来实现流量控制。当某个 Buffer (例如,用于某个流的 Buffer )超过其高水位线时,Envoy 会向数据源(upstream 或 downstream)发出信号,要求暂停发送数据。当 Buffer 排空并低于低水位线时,数据流将恢复。
当 Envoy 使用非流式 L7(http) filter(例如 transcoder 或 http buffer filter),并且请求或响应体超出 L7 缓冲区限制时,流量控制可能会导致问题(hard limits)。
对于请求,如果请求体必须缓冲且超出配置的限制,Envoy 将向用户返回 413 错误,并增加 downstream_rq_too_large
指标。
在响应路径上,如果响应体必须缓冲且超出限制,Envoy 将增加 rs_too_large
指标,并可能:
概念上,流控可以分为两种层次,分别是:
listener limits 适用于每次 read() 调用从 downstream 读取的原始数据量,以及 Envoy 和 downstream 之间在用户空间中缓冲的数据量。
listener limits 也会传播到 HttpConnectionManager,所以:
initial_connection_window_size
将应用于所有 L7 buffer 。请注意,对于所有版本的 HTTP,Envoy 可以在所有 L7 filter 都是流式传输的路由路径(routes)上代理任意大的 http body ,但许多 http filter(例如 transcoder 或 http buffer filter)需要缓冲完整的 HTTP body,这种情况下, listener limits 会限制请求和响应的大小。
static_resources:
listeners:
name: http
address:
socket_address:
address: '::1'
portValue: 0
filter_chains:
filters:
name: envoy.filters.network.http_connection_manager
...
per_connection_buffer_limit_bytes: 1024
per_connection_buffer_limit_bytes
很重要。这是 cluster 级别上的一个设置,定义了 cluster 连接的读写 Buffer 的软限制。如果未指定,则会应用一个实现定义的默认值(通常是 1MiB)。此限制适用于整个连接,因此会影响该连接上的所有多路复用流。如果一个 http connection buffer 已满,最终也会影响各个 http stream。
cluster 限制会影响每次 read() 调用从 upstream 读取的原始数据量,以及在 Envoy 和 upstream 之间的用户空间中 buffer 的数据量。
per_connection_buffer_limit_bytes
的配置示例(集群配置):
clusters:
- name: my_upstream_cluster
connect_timeout: 5s
type: LOGICAL_DNS
per_connection_buffer_limit_bytes: 32768 # 32 KB (32千字节), 对不受信任的 upstream 很有用
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: my_upstream_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: example.com
port_value: 80
作用对象: 这个参数是针对 Envoy 集群 (Cluster) 的,它定义了 Envoy 与 upstream 集群中每个连接的读写缓冲区大小。
目的: 这个设置控制的是 Envoy 在其内部为每个 TCP 连接(无论是 HTTP/1.1 还是 HTTP/2 连接)分配的用户空间缓冲区的软限制。它主要用于防止 Envoy 自身因缓冲过多的数据而耗尽内存。
性质: 这是一个Envoy 内部的资源管理和背压机制。当 Envoy 与 upstream 建立的某个连接的读或写缓冲区达到这个限制时,Envoy 会触发内部的流量控制回调(例如,停止从套接字读取数据),从而将背压传递给下游或上游。
影响: 它影响 Envoy 进程的内存使用,尤其是在有大量并发连接或慢速上游/下游的情况下。如果未指定,Envoy 会使用一个默认值(通常是 1MiB)。
对于 HTTP/2 和 HTTP/3,控制每 Stream Buffer 的主要机制是初始流级别流量控制接收窗口大小 (initial stream-level flow-control receive window size)。此设置规定了 Envoy 在从对端收到窗口更新之前,允许对端在一个流上发送多少数据。
initial_stream_window_size
: 此参数位于 http2_protocol_options
或 quic_protocol_options
(用于 HTTP/3)配置中。
http_protocol_options
或 http2_protocol_options
中配置此项。它作为 Envoy 在发送和接收 Buffer 中为每个 Stream Buffer 的字节数的软限制。initial_stream_window_size
在 cluster 或 listener 的 quic_protocol_options
下可用。initial_connection_window_size
决定了在不收到对端窗口更新帧的情况下,Envoy 允许在一个 HTTP/2 连接上发送或接收的总字节数。HTTP/2 的配置示例(cluster 配置):
clusters:
- name: my_upstream_cluster
connect_timeout: 5s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options:
initial_stream_window_size: ...
# max_concurrent_streams: ...
initial_connection_window_size: ...
load_assignment:
cluster_name: my_upstream_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: example.com
port_value: 80
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。