设计解析:双层信用模型、水位线状态机与窗口自动调优)
OpenSSL QUIC 流控Flow Control设计解析双层信用模型、水位线状态机与窗口自动调优【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl导读QUIC 的流控Flow Control负责在连接级connection-level与流级stream-level两个层面约束对端可发送的数据量是保证接收端缓存不溢出、端到端吞吐得以收敛的关键机制。本文以 OpenSSL 仓库中的 QUIC 流控设计文档 doc/designs/quic-design/quic-fc.md 为主体结合其核心实现 ssl/quic/quic_fc.c、include/internal/quic_fc.h 及帧处理与测试代码完整讲解信用credit模型、TX/RX 两侧状态机事件、水位线watermark术语体系以及 RX 窗口的动态自动调优auto-tuning算法。读完本文你将掌握 OpenSSL QUIC 实现中流控的完整数据流与判定规则并能将设计与源码一一对应为深入阅读或二次开发 OpenSSL QUIC 栈打下基础。QUIC 流控概览双层级信用模型QUIC 的流控同时在连接级和流级两个层面生效。任何时刻流数据的发送都可能被连接级流控、流级流控或两者同时限制。流控采用信用credit模型相关的流控上限被表达为自流或连接开始以来允许在单个流上、或跨所有流发送的最大字节数并且该上限可以被周期性地抬高bump。关于 credit 一词在 OpenSSL 源码中credit 即当前还可发送的受控字节数等价于 CWM 与 SWM 之差见 include/internal/quic_fc.h 中ossl_quic_rxfc_get_credit的实现cwm - swm。受控字节Controlled Bytes与重传计数连接级与流级流控都只与QUIC 流数据stream data的传输有关。流级流控统计的是某个流上发送的逻辑字节总数它不统计重传。一个字节如果被发送、丢失、再发送对流控而言仍只计作一个字节因此某条流上已发送的逻辑字节总数等价于该流的当前长度length。本质上相关的量是对于该流上我们曾发送过的所有 STREAM 帧(offset, len)取max(offset len)。正确确定这一数值至关重要——如果发送端误以为自己的流控信用已耗尽而对端认为并未耗尽那么对端可能会无限期等待发送端继续发送数据之后再授予更多流控信用从而造成死锁deadlock。帧类型与层级对应连接级流控由MAX_DATA帧控制流级流控由MAX_STREAM_DATA帧控制RFC 9000 定义的DATA_BLOCKED与STREAM_DATA_BLOCKED帧实际作用比表面看起来小对端不允许依赖它们例如对端不能等我们发出DATA_BLOCKED才提高连接级信用一个合规的 QUIC 实现甚至可以完全不生成这两种帧。它们的两个实际用途是提升流控性能、作为调试辅助手段。因此其实现并非关键。CRYPTO 流不受流控、流控与拥塞控制相互独立由上述定义自然可以推出CRYPTO 帧的流CRYPTO stream不受流控约束。此外流控与拥塞控制congestion control是完全独立的机制——在特定场景下两者之一或两者同时都可能限制我们发送应用数据的能力。在 OpenSSL 源码中也可以看到这种独立性流控实现在 ssl/quic/quic_fc.c而拥塞控制是另一套独立的模块如 ssl/quic/cc_newreno.c二者互不调用。核心术语RWM / SWM / CWM 水位线体系设计文档用一张示意图概括了 RX 侧各水位线与窗口的关系RWM SWM SWM CWM CWM | | | | | | |-- credit| --| | | -|- threshold -|-----| | ----------------- window size文档引入的术语体系如下术语含义Controlled bytes受控字节任何对流控计数的字节STREAM 帧负载中的应用数据字节首次发送时计数重传不计。Retirement退役仅 RX 侧从 QUIC 流中取出一个或多个受控字节并交给应用的过程退役后我们不再对这些字节负责。RX 流控设计非常依赖退役节奏——我们希望对端不仅以 QUIC 实现处理入站数据的速度发送还要以应用能处理的速度发送。Retired WatermarkRWM退役水位线仅 RX 侧自连接或流开始以来退役的受控字节总数。Spent WatermarkSWM已消费水位线我们已发送TX 侧或已接收RX 侧的受控字节数代表已消耗的流控预算它是单调的、永不回退。RX 侧的这些字节不一定已被退役。Credit WatermarkCWM信用水位线目前已被授权可发送的字节数这是自连接或流开始以来的累计值因此同样单调。Credit信用始终等于 SWM 与 CWM 之差CWM - SWM。Threshold阈值仅 RX 侧允许 RWM 逼近 CWM 的程度当 RWM 到达阈值时才选择抬高 CWM 以授予对端更多信用。阈值是相对 CWM 的从 CWM 中减去。Window size窗口大小仅 RX 侧每次到达或超过阈值时我们或对端抬高 CWM 的量。新的 CWM 等于SWM 加窗口大小注意是加到 SWM 上而非旧 CWM 上。两个必须牢记的不变量若可用信用为 0则 TX 侧因缺乏信用而被阻塞若发生任何会导致SWM 超过 CWM的情况则属于流控协议违规flow control protocol violation应终止连接。在源码中这些水位线被直接实现为结构体字段见 include/internal/quic_fc.h 中的QUIC_RXFCuint64_t cwm, swm, rwm, esrwm, hwm, cur_window_size, max_window_size; OSSL_TIME epoch_start;其中esrwm是当前自动调优 epoch 开始时的 RWM 值hwm是迄今从 STREAM 帧中见过的最高流长度offset payload lengthcur_window_size与max_window_size分别是当前窗口大小与窗口大小上限。TX 侧流控简单的状态机TX 侧流控异常简单可建模为以下状态机--- event: On TX (numBytes) --- event: On TX Window Updated (numBytes) --- event: On TX Blocked Get TX Window() - numBytes连接级 TX 状态机事件On TX每当我们发送一个数据包时传入。numBytes是包中发送的受控字节总数即非重传的 STREAM 帧负载字节数累加到 TX 侧 SWM。该值可以为 0此时无需传事件。On TX Window Updated每当我们的 CWM 被提高时传入即收到MAX_DATA帧时或收到initial_max_data传输参数时参数为该帧中的整数值。该事件表达的是 CWM自连接开始以来允许发送的受控字节累计数因此是单调的、永不回退如果传入的值低于之前任何一次事件的值说明对端协议错误或本地编程错误。Get TX Window返回我们的信用值允许发送的受控字节数。它被 On TX 事件减少、被 On TX Window Updated 事件增加。实际上它就是最后一次 On TX Window Updated 的值与迄今所有 On TX 事件numBytes之和的差仅此而已。On TX Blocked在 Get TX Window 返回值从非零变为零的任何边沿时刻发出总是发生在处理某个 On TX 事件期间。该事件用于辅助决定何时生成DATA_BLOCKED帧。我们绝不能超出流控上限否则对端可能以错误终止连接。初始的连接级信用由对端通过initial_max_data传输参数告知其余信用全部来自MAX_DATA帧。流级 TX与连接级完全相同的机制流级 TX 流控与连接级 TX 完全一致仅事件来源不同On TX Window Updated由MAX_STREAM_DATA帧触发或基于相应传输参数initial_max_stream_data_bidi_local、initial_max_stream_data_bidi_remote、initial_max_stream_data_uniOn TX Blocked事件可用于决定何时生成STREAM_DATA_BLOCKED帧。关键约束一条流上可发送的受控字节数同时受连接级与流级流控限制因此实际可发送的受控字节数为连接级与流级两个状态机Get TX Window返回值中的较小者。源码中的 TX 流控实现ssl/quic/quic_fc.c 将上述状态机实现为一组直接的函数ossl_quic_txfc_init初始化 TXFC流级 TXFC 通过parent指针挂接连接级 TXFCconn_txfc ! NULL连接级 TXFC 的 parent 为 NULLossl_quic_txfc_bump_cwm即On TX Window Updated操作若传入的 CWM 不大于当前值则返回 0no-op否则更新并返回 1ossl_quic_txfc_get_credit即Get TX Window对流级 TXFC还会递归调用连接级 TXFC 并返回两者中较小的信用值——这正是文档所述取较小者约束的落地r ossl_quic_txfc_get_credit_local(txfc, 0); if (txfc-parent ! NULL) { conn_r ossl_quic_txfc_get_credit_local(txfc-parent, consumed); if (conn_r r) r conn_r; } return r;ossl_quic_txfc_consume_credit即On TX操作若传入字节数超过当前信用则消耗剩余信用并返回 0提示调用方存在严重编程错误流级调用时同步在连接级 TXFC 上消费ossl_quic_txfc_has_become_blocked即On TX Blocked标志供调用方决定是否需要发送DATA_BLOCKED或STREAM_DATA_BLOCKED帧帧中应携带ossl_quic_txfc_get_cwm()的返回值。在通道层面ssl/quic/quic_channel.c 解析传输参数时将initial_max_data直接映射为ossl_quic_txfc_bump_cwm(ch-conn_txfc, v)见 quic_channel.c 中QUIC_TPARAM_INITIAL_MAX_DATA分支而每个流的 TXFC 则通过ossl_quic_txfc_init(qs-txfc, ch-conn_txfc)与连接级 TXFC 建立父子关系。RX 侧流控以退役节奏驱动信用发放RX 侧连接级流控的状态机如下--- event: On RX Controlled Bytes (numBytes) [internal event] --- event: On Retire Controlled Bytes (numBytes) --- event: Increase Window (numBytes) --- event: Flow Control ErrorRX 侧连接级流控负责指示何时生成MAX_DATA帧以提高对端的连接级发送信用比 TX 侧复杂一些。连接级 RX 状态机事件On RX Controlled Bytes内部事件由流级流控控制器在收到任何受控字节时自动生成调用方不直接传入。numBytes为收到的受控字节数。之所以由流级生成是因为重传的流数据只能计数一次流级流控最擅长判断收到了多少新的、非重传的流负载字节。Flow Control Error若收到的受控字节数超过我们授权的量状态机发出该事件此时应以协议错误终止连接。Increase Window当状态机认为应授予对端更多流控信用即应抬高 CWM时发出。numBytes为新的 CWM 值且相对之前所有 Increase Window 事件是单调的。On Retire Controlled Bytes当一个或多个受控字节从任意流出队并交给应用时传入。状态机依据 On Retire 事件的节奏决定何时增大流控窗口因此该事件必须在受控字节处理完成即已交给应用之后才传入。这里体现了设计文档强调的关键点RX 流控希望对端的发送速率既不超过 QUIC 实现的处理能力也不超过应用消费数据的能力——退役节奏正是把应用消费速率纳入流控决策的桥梁。流级 RXOn RX Stream Frame 事件流级 RX 流控与连接级 RX 类似但有几点差异没有 On RX Controlled Bytes 事件On Retire Controlled Bytes 事件可以由实现决定同时传递给连接级流控控制器——因为这两个事件总是同时发生新增一个替代事件--- event: On RX Stream Frame (offsetPlusLength, isFin)该事件在收到 STREAM 帧时传入offsetPlusLength是 STREAM 帧 offset 字段与帧负载长度的和isFin指示该帧是否设置了 FIN 标志。该事件既用于向连接级流控控制器生成内部 On RX Controlled Bytes 事件也用于流级流控判断对端是否违反流控上限。状态机对offsetPlusLength做单调处理如果之前已有相同或更大的值则忽略该事件。之所以用这种 API 而非On RX (numBytes)风格是因为该 API 是单调的、更易使用——调用方无需记忆某个受控字节是否已在之前的 STREAM 帧中计过数毕竟之前的帧可能重复包含了其中部分字节。源码中的 RX 流控实现与调用链在 ssl/quic/quic_fc.c 中ossl_quic_rxfc_on_rx_stream_frame(rxfc, end, is_fin)实现On RX Stream Frame计算delta end - hwm仅在end hwm时对自身与 parent连接级 RXFC分别调用内部on_rx_controlled_bytes同时处理 FIN 相关的FINAL_SIZE_ERROR——流结束后大小不可改变end与已记录hwm不符时置错内部on_rx_controlled_bytes检查num_bytes cwm - swm若超出则置error_code OSSL_QUIC_ERR_FLOW_CONTROL_ERROR并截断计数——即Flow Control Error事件ossl_quic_rxfc_on_retire(rxfc, num_bytes, rtt)实现On Retire Controlled Bytes累加 RWM并调用rxfc_update_cwm决定是否抬高 CWM抬高后置has_cwm_changed标志供上层生成MAX_DATA/MAX_STREAM_DATA帧。调用链可以在帧解包路径 ssl/quic/quic_rx_depack.c 中观察到每收到一个 STREAM 帧或 RESET_STREAM 帧都会调用ossl_quic_rxfc_on_rx_stream_frame随后用ossl_quic_rxfc_get_error检查是否发生流控违规若违规则通过ossl_quic_channel_raise_protocol_error以FLOW_CONTROL_ERROR终止连接见 quic_rx_depack.c 中 RESET_STREAM 处理段CRYPTO 帧则使用独立的 standalone RXFCcrypto_rxfc违规时报CRYPTO_BUFFER_EXCEEDED。值得一提的复用ossl_quic_rxfc_init_standalone创建的standalone RXFC还被用于流数量stream count强制与CRYPTO 缓冲区强制见 quic_channel.c 中max_streams_bidi_rxfc、max_streams_uni_rxfc的初始化这也是文档之外的一个实现细节。RX 窗口自动调优Auto-Tuning对于 RX 流控我们必须确定窗口大小——即每次 RWM 到达阈值时加到对端 SWM 上以得出新 CWM 的值。窗口大小应根据网络条件动态调整。许多实现采用只增不减的窗口增大机制可以增加窗口大小但不能减少OpenSSL 采纳了这一简单方案。算法原理常见算法是一种自动调优auto-tuning方法测量窗口的消耗速率即 CWM 被抬高后 RWM 向 CWM 逼近的速率与测得的连接RTT比较如果消耗完一个窗口大小所需的时间超过 RTT 的固定倍数则将窗口大小加倍直到达到实现选择的最大窗口大小。自动调优按epoch周期进行每个 epoch 结束时决定是否加倍窗口大小并开启一个新的 epoch。OpenSSL 的实现细节在 ssl/quic/quic_fc.c 中阈值被定义为窗口大小的 3/4WINDOW_THRESHOLD_NUM 3、WINDOW_THRESHOLD_DEN 4rxfc_cwm_bump_desired在cwm - rwm 3/4 * cur_window_size且流未终结!is_fin时判定需要抬高 CWM对极端大的窗口兜底使用 1/2 作为阈值epoch 的起点由rxfc_start_epoch记录epoch_start now()esrwm rwm是否加倍由rxfc_should_bump_window_size决定其推导过程即文档所述b rwm - esrwm 本 epoch 内消耗的窗口字节数 dw b / window_size T_window dt / dw (dt * window_size) / b当T_window 4 * RTTossl_time_compare(t_window, ossl_time_multiply(rtt, 4)) 0时判定应加倍窗口大小。源码注释特别说明把除法b保留在等号左侧可降低 64 位纳秒表示溢出的风险且除法后仍留有充足精度rxfc_adjust_window_size执行加倍new_window_size * 2并夹在min_window_size与max_window_size之间max 优先于 min随后开启新 epoch新 CWM 按文档公式计算new_cwm rwm cur_window_size加到 SWM/RWM 而非旧 CWM 上只有确实大于当前 CWM 时才更新并置has_cwm_changed见rxfc_update_cwm。窗口大小参数通过ossl_quic_rxfc_init的initial_window_size与max_window_size传入也可用ossl_quic_rxfc_set_max_window_size动态调整。帧生成与测试验证帧生成TX 打包器 ssl/quic/quic_txp.c 负责生成MAX_DATA/MAX_STREAM_DATA帧并遵循 RFC 9000 s. 13.3 的建议仅在有意义时生成更多MAX_STREAM_DATA帧连接级与流级的 CWM 变更标志ossl_quic_rxfc_has_cwm_changed驱动帧的按需发送。测试test/quic_fc_test.c 系统验证了 TXFC/RXFC 的行为——例如初始化后 SWM 为 0、bump CWM 到 2000 后信用为 2000、消费 500 后本地信用降为 1500、流级 TXFC 与连接级 TXFC 联合取小、has_become_blocked边沿标志等测试入口由 test/recipes/70-test_quic_fc.t 注册。小结OpenSSL 的 QUIC 流控设计可以浓缩为几个要点双层信用模型连接级由MAX_DATA与initial_max_data驱动流级由MAX_STREAM_DATA与三个initial_max_stream_data_*传输参数驱动实际可发送字节数取两层信用的较小者。三个水位线SWM已消费、CWM已授权、RWM已退役RX 侧——信用恒为CWM - SWM任何导致 SWM 越过 CWM 的情况都是流控违规。TX 极简、RX 以退役驱动TX 侧只是加减法状态机RX 侧由收到受控字节与退役受控字节两个节奏共同决定何时抬高 CWM从而让对端发送速率同时匹配 QUIC 实现处理能力与应用消费能力。窗口自动调优以 3/4 窗口为阈值以T_window 4 * RTT为判据按 epoch 加倍窗口大小且只增不减、受最大窗口上限约束。边界清晰CRYPTO 流不受流控DATA_BLOCKED/STREAM_DATA_BLOCKED仅作性能增强与调试用途流控与拥塞控制相互独立。这套设计与实现可以从设计文档 doc/designs/quic-design/quic-fc.md 出发对照 ssl/quic/quic_fc.c、include/internal/quic_fc.h 与 test/quic_fc_test.c 逐行印证是理解 OpenSSL QUIC 栈以及 RFC 9000 流控语义的一条清晰路径。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考