十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenSSL QUIC 并发架构解析:Concurrency Models 与 Concurrency Management Layer 设计指南

OpenSSL QUIC 并发架构解析:Concurrency Models 与 Concurrency Management Layer 设计指南 OpenSSL QUIC 并发架构解析Concurrency Models 与 Concurrency Management Layer 设计指南【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl导读QUIC 是一种运行于 UDP 之上、内置多路复用、迁移与 0-RTT 等能力的新一代传输协议但其状态机 I/O 分离的天然属性给集成方带来了巨大的工程负担。本文基于 OpenSSL 仓库中的 QUIC Concurrency Architecture 设计文档系统讲解 OpenSSL QUIC 提出的四类并发模型UCM / CCM / TA-CCM / WCM以及介于 API 层与 QUIC 核心之间的 Concurrency Management LayerCML架构并辅以仓库源码quic_impl.c、quic_engine.c、quic_channel.c、quic_thread_assist.c加以印证。读完本文你将掌握 OpenSSL QUIC 并发模型的取舍逻辑、CML 的 Pipe/Selector 抽象与核心 API 语义并能据此判断自己的应用该选择哪种集成方式。背景为什么 QUIC 需要一套并发架构大多数用 C 语言编写的 QUIC 实现仅仅提供一个简单的状态机而不附带任何 I/O 解决方案。应用程序必须自己完成大量集成工作才能为 QUIC 实现提供所需的基础设施同时在应用层面使用阻塞式 I/O 也可能不被支持。OpenSSL QUIC 的目标是同时服务两类截然不同的使用场景状态机模型提供简单的状态机模型并通过 BIO 提供完全可定制的网络路径供高级应用按需使用开箱即用turnkey方案提供内置的 I/O 与轮询polling解决方案以类似 Berkeley sockets 的方式支持阻塞式 API 调用。这两种模式在本质上是对立的第一种模式下libssl 除了应用给予的资源外不消耗任何资源同步责任与可能自定义的网络 I/O 路径全部由应用负责。这种模式不聪明网络流量被接入状态机状态由应用在纯非阻塞的基础上按需输入输出何时做事基本由应用决定。第二种模式下libssl 在内部管理更多事务以提供更易用的方案例如启动后台线程来确保连接被定期服务即现有的客户端线程辅助模式。为了在同一套实现中兼顾这些需求设计文档引入了concurrency model并发模型这一概念并发模型定义了 QUIC 引擎将以多聪明的方式运行以及为支撑运行需要建立多少后台资源线程、其他操作系统资源等。四大并发模型Unsynchronised Concurrency ModelUCM在**无同步并发模型UCM**下对 SSL 对象的调用不被同步。任何 APL 调用上都没有锁省略锁纯粹是一种优化。应用要么是单线程的要么自己负责同步。不支持阻塞式 API 调用主要面向单线程、把 QUIC 当作简单状态机使用的高级应用许多应用很可能会禁用 autoticking自动 tick把定时器事件的驱动完全掌握在自己手中。Contentive Concurrency ModelCCM在**争用式并发模型CCM**下对 SSL 对象的调用被包在锁中同一 QUIC 连接的多线程使用例如并行向属于同一连接的多个 QUIC 流 SSL 对象写入由 mutex 同步。之所以叫contentive争用是因为如果大量线程同时向同一连接的不同流写入会产生大量锁竞争。因此在并发使用单个连接的语境下该模型不会随规模扩展并提供良好性能。该模型下应用的 APL 调用会在同一线程上产生对 QUIC 核心对象QUIC_CHANNEL、QUIC_STREAM等的锁包裹lock-wrapped变更。CCM 有两个变体NB-CCM不支持阻塞non-blockingB-CCM支持阻塞但必须额外启动操作系统资源以正确实现阻塞语义。Thread Assisted Contentive Concurrency ModelTA-CCM线程辅助争用并发模型TA-CCM即当前客户端 QUIC 用法中的线程辅助模式。它没有实现下面 Worker Concurrency ModelWCM的全部状态分离与性能优势而是简单地派生一个后台线程来确保 QUIC 定时器事件被及时处理。该后台线程通过**争用式CCM**的方式执行处理它在 tick 一个 QUIC 连接时与应用发起的任何调用一样需要先获得锁。其同步细节可参见同目录下的 QUIC Thread Assisted Mode Synchronisation Requirements其中明确了选择方案 2握手层始终归属应用线程作为实现基础。从演进方向看TA-CCM 很可能会被完整的 WCM 取代被其自然吸收未来存在弃用deprecate计划。Worker Concurrency ModelWCM在**工作线程并发模型WCM**下会派生一个后台 worker 线程来管理连接处理。与 SSL 对象的所有交互都以某种方式经过该线程与 SSL 对象的交互本质上被翻译成命令commands由 worker 线程处理为优化性能、最小化锁竞争强调用消息传递message passing替代加锁应用数据的内部数据流可以零拷贝zero-copy方式管理以降低消息传递的开销。该模型下QUIC 核心对象QUIC_CHANNEL、QUIC_STREAM等只存活于 worker 线程上应用线程对这些对象的访问被完全禁止。阻塞式 API 调用在该模型下得到支持。模型总览与图例说明设计文档给出了以下汇总表ModelSophisticationConcurrencyBlocking SupportedOS ResourcesTimer EventsRX SteeringCore State AffinityUCMLowestST onlyNoNoneApp ResponsibleNoneApp ThreadCCMMT (Contentive)OptionalMutex, (Notifier)App ResponsibleTBDApp ThreadsTA-CCM†MT (Contentive)OptionalMutex, Thread, (Notifier)ManagedTBDApp Assist ThreadsWCMHighestMT (High Performance)YesMutex, Thread, NotifierManagedFutureproofWorker Thread† 表示未来将弃用、被 WCM 取代。表中各列含义如下Blocking Supported是否支持对SSL_read等调用的阻塞式使用。若标注为 optional意味着在该模型下支持阻塞需要额外资源而这些资源可以在应用于初始化阶段声明不需要该功能时省略。OS ResourcesMutex 指 mutex 与 condition variable 资源Notifier 指用于唤醒正在 OS socket 轮询调用如poll(2)中阻塞的另一线程的资源例如 eventfd 或 socketpair。表中括号内列出的资源仅在需要阻塞支持时才需要。Timer Events应用是否有责任确保 QUIC 超时事件被及时处理。RX Steering关于同一本地端口上多个不同 QUIC 连接的入站流量例如服务器场景是否能由操作系统向量化到不同线程还是必须在进程内手动完成不同连接入站流量的解复用demux。WCM 最易于支持 RX steering 且在这一点上具备前瞻性futureproofUCM/CCM 支持 RX steering 的可行性留待未来分析。Core State Affinity允许哪些线程触碰 QUIC 核心对象QUIC_CHANNEL、QUIC_STREAM等。与仓库代码的对应关系QUIC_CHANNEL、QUIC_STREAM、QUIC_ENGINE、QUIC_DOMAIN等类型定义可分别在 include/internal/quic_predef.h 中查到QUIC_CHANNEL的完整实现位于 ssl/quic/quic_channel.c引擎/域的实现位于 ssl/quic/quic_engine.c 与 ssl/quic/quic_port.c。架构APL、CML 与 QUIC Core 的三层划分回顾 APL 与 QUIC Core 的分离**API Personality LayerAPL**指 quic_impl.c 中实现 libssl API 个性SSL_write等的代码。APL 与 QUIC 核心实现QUIC_CHANNEL等被清晰地分离。由于 UCM 本质上是在 CCM 基础上省略不必要加锁的微小优化设计文档在讨论架构时以 CCM 和 WCM 为主仅在有具体差异时才提及 UCM。同时支持 CCM 与 WCM 的架构挑战同时支持 CCM 与 WCM 会带来显著的架构挑战在 CCM 下QUIC 核心对象的状态由任意应用线程在锁内变更且这些变更发生在 APL 调用过程中而一个高性能的 WCM 架构要求 APL 调用被记录并以异步方式服务涉及向 worker 线程传递消息。这两者威胁到要求为两种并发模型提供高度分歧divergent的分发架构。Concurrency Management LayerCML为此设计文档引入Concurrency Management LayerCMLCML 位于 APL 与 QUIC 核心代码之间负责在 CCM 下分发对 QUIC 核心对象的线程内in-thread变更在 WCM 下将消息分发到 worker 线程。CML 分为两种实现Direct CMLDCML核心对象在发起 APL 调用的同一线程上、在锁内被操作Worker CMLWCML核心对象由 worker 线程管理通过消息传递通信WCML 进一步拆分为前端WCML-FE与后端WCML-BE。此外遗留的线程辅助模式TA-CCM使用一种与 DCML 思路类似的特有方法。CML 设计Pipe、Selector 与核心 API设计原则尽可能小的 API 表面积CML 被设计为拥有尽可能小的 API 表面积以便尽可能统一地处理多种 APL API 操作。复杂的 APL 调用被翻译成对 CML 的简单操作。Pipe单向字节流管道CML 的核心是若干pipe管道。可通过 CML 访问的 pipe 数量会随连接与流的创建/销毁而变化。pipe 是字节流的**单向unidirectional**传输通道。零拷贝优化预计在未来实现但目前被推迟。CML 的类型为QUIC_CML调用方通过不透明的 pipe 句柄QUIC_CML_PIPE引用一个 pipe若 pipe 是发送型sending调用方可使用ossl_cml_write尝试向其中添加字节若 pipe 是接收型receiving调用方可使用ossl_cml_read尝试从中读取字节。ossl_cml_block_until允许调用方阻塞直到所给 pipe 句柄中至少有一个就绪——对发送型 pipe 而言是至少可写一个字节对接收型 pipe 而言是至少可读一个字节。Selector选择要获取 pipe 的逻辑对象调用方通过ossl_cml_get_pipe获取 pipe 句柄。该函数依据两个值获取 pipe一个CML pipe class一个CML selector。CML selector 是一个**带标签的联合tagged union**结构用于指明要获取哪个 pipe。抽象地看selector 的例子包括Domain () Listener (listener_id: uint) Conn (conn_id: uint) Stream (conn_id: uint, stream_id: u64)换言之selector 选中要从哪个对象上获取 pipe。Pipe Class四类管道CML pipe class 是下列值之一Request请求Notification通知App Send应用发送App Recv应用接收不同 selector 可用的 pipe class 各不相同。例如 App Send 与 App Recv pipe 只存在于流stream上因此与不同类型的 selector 组合请求这类 pipe 是非法的。方向性上Request 与 App Send 类暴露只发送的流Notification 与 App Recv 类暴露只接收的流。对于任意给定的 selectorRequest pipe用于发送与被选中实体相关的、待异步处理的序列化命令Notification pipe返回异步通知可能是对先前命令的响应例如指示命令是否成功也可能是关于其他事件的主动通知。底层模式是控制消息有一个双向通道应用数据也有一个双向通道两者又各自由两条单向 pipe 组成。控制与数据由此实现分离control/data separation。pipe 句柄在其引用的 pipe 存续期间保持稳定因此 APL 对象可按需缓存 pipe 句柄。所有 CML 方法都是线程安全的CML 实现在内部处理任何必要的加锁。并发语义容量查询与竞态保证ossl_cml_write_available与ossl_cml_read_available分别确定当前可写入只发送 pipe、或可从只接收 pipe 读取的字节数。关于竞态由于对ossl_cml_write/ossl_cml_read是独立调用这些函数返回的值可能在调用方读取前就已过时。但此类变化被保证单调地有利于调用方ossl_cml_write_available的返回值只会异步增加且只会因ossl_cml_write调用而减少同理ossl_cml_read_available的返回值只会异步增加且只会因ossl_cml_read调用而减少。假设在给定 pipe上同一时刻只有一个线程调用 CML 函数这对调用方不构成问题。同一 pipe 上的并发ossl_cml_write/ossl_cml_read是不被预期的任何情况下也没有意义调用方需自行同步这类调用。管道使用示例应用数据 pipe 用于序列化 QUIC 流上收发send/receive的实际应用数据request/notification pipe 的用法更多样用于控制活动。二者传输带标签的联合抽象地看命令与通知可能包括Request:Reset Stream (error code: u64)Notification:Connection Terminated by PeerSSL_write的 CML 实现示例设计文档给出了一个SSL_write-like API 在 APL 中的实现思路int do_write(QUIC_CML *cml, QUIC_CML_PIPE notification_pipe, QUIC_CML_PIPE app_send_pipe, const void *buf, size_t buf_len) { size_t bytes_written 0; for (;;) { /* e.g. connection termination */ process_any_notifications(notification_pipe); /* state checks, etc. */ if (...-conn_terminated) return 0; if (buf_len 0) return 1; if (!ossl_cml_write(cml, app_send_pipe, buf, buf_len, bytes_written)) return 0; if (bytes_written 0) { if (!should_block()) break; ossl_cml_block_until(cml, {notification_pipe, app_send_pipe}); continue; /* try again */ } buf bytes_written; buf_len - bytes_written; } return 1; }该循环展示了典型的 APL 写路径先处理通知例如连接终止再做状态检查然后尝试写入若未写入任何字节则在非阻塞模式下直接退出在阻塞模式下调用ossl_cml_block_until等待管道就绪后重试。这正对应 CCM 下锁内线程内变更核心对象与 WCM 下消息传递到 worker 线程的统一抽象——APL 层无需感知底层使用 DCML 还是 WCML。CML 完整 API 清单设计文档给出了 CML 的完整接口当前为设计阶段的头文件级说明/* * Creates a new CML using the Direct CML (DCML) implementation. need_locking * may be 0 to elide mutex usage if the application is guaranteed to synchronise * access or is purely single-threaded. */ QUIC_CML *ossl_cml_new_direct(int need_locking); /* Creates a new CML using the Worker CML (WCML) implementation. */ QUIC_CML *ossl_cml_new_worker(size_t num_worker_threads); /* * Starts the CML operating. Idempotent after it returns successfully. For the * WCML this might e.g. start background threads; for the DCML it is likely to * be a no-op (but must still be called). */ int ossl_cml_start(QUIC_CML *cml); /* * Begins the CML shutdown process. Returns 1 once shutdown is complete; may * need to be called multiple times until shutdown is done. */ int ossl_cml_shutdown(QUIC_CML *cml); /* * Immediate free of the CML. This is always safe but may cause handling * of a connection to be aborted abruptly as it is an immediate teardown * of all state. */ void ossl_cml_free(QUIC_CML *cml); /* * Retrieves a pipe for a logical CML object described by selector. The pipe * handle, which is stable over the life of the logical CML object, is written * to *pipe_handle. class_ is a QUIC_CML_CLASS value. */ enum { QUIC_CML_CLASS_REQUEST, /* control; send */ QUIC_CML_CLASS_NOTIFICATION, /* control; recv */ QUIC_CML_CLASS_APP_SEND, /* data; send */ QUIC_CML_CLASS_APP_RECV /* data; recv */ }; int ossl_cml_get_pipe(QUIC_CML *cml, int class_, const QUIC_CML_SELECTOR *selector, QUIC_CML_PIPE *pipe_handle); /* * Returns the number of bytes a sending pipe can currently accept. The returned * value may increase over time asynchronously but will only decrease in * response to an ossl_cml_write call. */ size_t ossl_cml_write_available(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle); /* * Appends bytes into a sending pipe by copying them. The buffer can be freed * as soon as this call returns. */ int ossl_cml_write(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle, const void *buf, size_t buf_len); /* * Returns the number of bytes a receiving pipe currently has waiting to be * read. The returned value may increase over time asynchronously but will only * decrease in response to an ossl_cml_read call. */ size_t ossl_cml_read_available(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle); /* * Reads bytes from a receiving pipe by copying them. */ int ossl_cml_read(QUIC_CML *cml, QUIC_CML_PIPE pipe_handle, void *buf, size_t buf_len); /* * Blocks until at least one of the pipes in the array specified by * pipe_handles is ready, or until the deadline given is reached. * * A pipe is ready if: * * - it is a sending pipe and one or more bytes can now be written; * - it is a receiving pipe and one or more bytes can now be read. */ int ossl_cml_block_until(QUIC_CML *cml, const QUIC_CML_PIPE *pipe_handles, size_t num_pipe_handles, OSSL_TIME deadline);要点归纳ossl_cml_new_direct(need_locking)DCML 构造need_locking 0可在应用保证同步或纯单线程时省去 mutex——这正是 UCM 相对 CCM 的实现落点ossl_cml_new_worker(num_worker_threads)WCML 构造可指定 worker 线程数量ossl_cml_start/ossl_cml_shutdown/ossl_cml_free生命周期管理。注意 shutdown 是分阶段的可能需多次调用直到完成而free是立即拆除可能粗暴中止连接处理ossl_cml_block_until支持OSSL_TIME截止时间是阻塞语义的关键支撑。模型选择建议与工程含义综合设计文档与仓库现状可以得出以下工程结论UCM 适合高级单线程用户需要自行驱动一切包括定时器与同步追求零额外资源开销。设计上它只是 CCM 去掉加锁的优化变体因此实现成本低。CCM 是默认的多线程基础多线程共享同一连接时用 mutex 同步语义简单但在大量线程争用同一连接时会遇到锁竞争瓶颈不适用于追求单连接高并发的场景。TA-CCM 是过渡方案仓库中 ssl/quic/quic_thread_assist.c 即该模式的实现载体其同步约束在 QUIC Thread Assisted Mode Synchronisation Requirements 中有专门讨论未来将被 WCM 取代。WCM 是高性能演进方向以消息传递为核心、核心对象只归属 worker 线程天然支持阻塞调用并为将来由 OS 支持的 RX steering 留出空间。代价是需要额外的线程与 notifier 资源以及更复杂的前端/后端WCML-FE/WCML-BE拆分。对于计划深度集成 OpenSSL QUIC 的开发者本文的架构图quic-concurrency-models.svg与同目录下的 quic-overview.md、quic-api.md、quic-io-arch.md 构成了一套完整的阅读路径先理解并发模型与 CML 抽象再对照 quic_impl.c 的 APL 实现与 quic_engine.c 的引擎实现即可在源码层面验证本文所述的三层架构。结论OpenSSL QUIC 通过引入concurrency model与Concurrency Management Layer把简单状态机与开箱即用的阻塞式 I/O 方案这对矛盾统一在同一架构之下UCM/CCM/DCML 服务于轻量、可控的线程内模型TA-CCM 提供过渡性的后台定时服务而 WCM/WCML 以 worker 线程 消息传递 pipe 抽象支撑未来的高性能多线程与 RX steering。CML 的请求/通知/应用发送/应用接收四类单向管道与 selector 机制构成了 APL 与 QUIC 核心之间稳定、线程安全且可扩展的接缝——这正是本设计文档最核心、也最值得读者深入的部分。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表