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

资讯详情

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

Envoy AsyncFileManager 异步文件 I/O 框架解析:线程池调度、取消语义与回调线程模型

Envoy AsyncFileManager 异步文件 I/O 框架解析:线程池调度、取消语义与回调线程模型 Envoy AsyncFileManager 异步文件 I/O 框架解析线程池调度、取消语义与回调线程模型【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读本文基于 Envoy 仓库中 source/extensions/common/async_files/README.md 展开系统讲解 Envoy 提供的通用异步文件操作框架AsyncFileManager与AsyncFileHandle它通过线程池把阻塞式文件 I/O打开、读写、截断、链接、删除等从事件循环线程中剥离出去并以“入队 回调 可取消”的模型把结果安全地送回调用方线程。读完本文你将掌握该框架的两个核心抽象、完整的状态机与取消语义、每类动作的底层 POSIX 实现以及如何通过AsyncFileManagerConfig配置线程池并在 Envoy 中以单例方式复用。一、为什么 Envoy 需要一套异步文件框架Envoy 是事件驱动的代理核心循环基于非阻塞 I/O 与 dispatcher事件调度器工作。文件系统操作open、read、write、stat、unlink、mkstemp等通常是阻塞式系统调用如果直接在 dispatcher 线程上执行会卡住整个事件循环进而阻塞同一线程上的所有连接与过滤器处理。为了在不阻塞事件循环的前提下完成文件操作Envoy 在 source/extensions/common/async_files 目录下提供了一套通用的异步文件框架其设计目标是将文件操作投递到专用线程池执行dispatcher 线程不被阻塞操作完成后的回调仍回到发起请求的 dispatcher 线程执行保持线程亲和提供与事件循环语义一致的取消机制避免“取消与回调并发发生”的竞态。从 BUILD 可以看到该框架被拆分为三层库async_files_base抽象接口与基类、async_files_thread_pool线程池实现、async_files单例工厂上层扩展通过依赖这些库获得能力。二、核心抽象一AsyncFileManager线程池的封装2.1 职责与生命周期按 async_file_manager.h 的定义AsyncFileManager应当是单例或类似单例的长生命周期对象它代表一个专门执行文件操作的线程池。当前仓库中的具体实现是AsyncFileManagerThreadPool见 async_file_manager_thread_pool.h 与 async_file_manager_thread_pool.cc。AsyncFileManager面向“文件级”操作提供四个入口方法作用成功回调参数createAnonymousFile(dispatcher, path, on_complete)在指定目录创建并打开一个匿名临时文件absl::StatusOrAsyncFileHandleopenExistingFile(dispatcher, filename, mode, on_complete)以指定模式打开已存在的文件absl::StatusOrAsyncFileHandlestat(dispatcher, filename, on_complete)按文件名获取struct stat元数据absl::StatusOrstruct statunlink(dispatcher, filename, on_complete)删除指定文件absl::Status所有方法统一返回一个CancelFunction取消函数其语义在 async_file_action.h 中有精确定义详见下文“取消语义”一节。2.2 打开文件的三态模式openExistingFile的mode参数使用AsyncFileManager::Mode枚举三态与 POSIX 打开标志一一对应实现见 async_file_manager_thread_pool.ccReadOnly→O_RDONLYWriteOnly→O_WRONLYReadWrite→O_RDWR2.3 createAnonymousFile 的路径语义与两段式实现createAnonymousFile的path参数不是文件名而是一个目录路径通常为/tmp。文档明确指出匿名文件虽然没有文件名、也没有链接但该路径决定文件写入的物理硬件——如果之后要link()这个文件跨设备链接代价高昂也可能出于性能考虑希望把临时文件放在虚拟文件系统或可卸载的临时 SSD 上async_file_manager.h。底层实现async_file_manager_thread_pool.cc采用两段式策略首选O_TMPFILE以O_TMPFILE | O_RDWR标志打开权限S_IRUSR | S_IWUSR。首次调用通过std::call_once探测文件系统是否支持探测成功后用一个supports_o_tmpfile_标志记录之后所有线程直接复用该结论回退mkstemp当目标文件系统不支持O_TMPFILE时回退到在path下创建名为buffer.XXXXXX的临时文件mkstemp随后立刻unlink使其匿名。若unlink失败某些文件系统不允许删除打开中的文件实现会主动close并再次unlink然后返回absl::UnimplementedError避免匿名临时文件堆积占满磁盘。此外如果路径过长path /buffer.XXXXXX超过 4096 字节会直接返回InvalidArgumentError。2.4 队列、状态机与线程池调度线程池实现的核心是AsyncFileManager::QueuedActionasync_file_manager.h以及一个五态原子状态机Queued → Executing → InCallback → Done ↘ ↘ Cancelled Cancelled触发清理动作每个待执行动作由std::shared_ptrstd::atomicState标记状态因此可以在执行线程与调用方线程之间安全地做 CAS 切换。调度逻辑在executeActionasync_file_manager_thread_pool.cc中worker 线程从互斥队列取出动作Queued → Executing的 CAS 失败说明已被取消此时若该动作executesEvenIfCancelled()如 close仍需执行执行完毕后Executing → InCallback再通过dispatcher-post()把回调投递回调用方线程投递后的 lambda 中做InCallback → Done的 CAS成功则调用真正的回调失败则说明已取消进入取消清理流程。waitForIdle()async_file_manager_thread_pool.cc用于测试阻塞直到active_workers_ 0 queue_.empty() cleanup_queue_.empty()即所有动作执行完毕且回调已投递不保证回调已执行。describe()返回线程池大小等描述信息主要用于测试与调试。2.5 析构与线程回收AsyncFileManagerThreadPool的析构async_file_manager_thread_pool.cc先置terminate_ true然后逐个join线程worker 在观察到terminate_且主队列、清理队列均为空时退出。这意味着析构会被阻塞直到所有已入队的文件操作完成确保不会在动作执行中销毁底层资源。三、核心抽象二AsyncFileHandle单文件上下文3.1 与文件的绑定关系AsyncFileHandle实际上就是std::shared_ptrAsyncFileContextasync_file_handle.h。一个AsyncFileHandle代表一个执行异步文件操作的上下文同一时刻至多关联一个文件。它由AsyncFileManager通过createAnonymousFile/openExistingFile创建。3.2 可入队的动作清单async_file_handle.h 定义了在已打开文件上可入队的全部动作动作签名要点成功回调底层实现statfstat当前 fdabsl::StatusOrstruct statposix().fstatreadoffsetlengthabsl::StatusOrBuffer::InstancePtrposix().preadwriteBuffer::Instance contentsoffsetabsl::StatusOrsize_t实际写入字节数posix().pwrite循环写满所有 slicecreateHardLink目标filenameabsl::Status/proc/self/fd/Nlinkat(AT_SYMLINK_FOLLOW)duplicate无absl::StatusOrAsyncFileHandleposix().duplicatetruncate目标lengthabsl::Statusposix().ftruncateclose无absl::Statusposix().close拷贝 fd 后执行3.3 read 与 write位置显式的读写模型readasync_file_context_thread_pool.cc按offset与length执行pread结果封装为Buffer::InstancePtr。其细节先reserveSingleSlice(length_)预留整段内存若实际读取字节数不足请求量则按实际字节数构造新的Buffer::OwnedImpl回调中缓冲区的大小即可告知实际读取了多少。writeasync_file_context_thread_pool.cc在构造动作的瞬间通过contents_.move(contents)按移动语义立即消费传入的Buffer::Instance因此调用方调用后即可安全丢弃该缓冲区不应假定其中数据仍然有效。执行时对每个 raw slice 循环pwrite直到全部写完处理部分写返回累计写入字节数。所有读写都显式指定offsetpread/pwrite不依赖文件指针共享状态因此duplicate出来的句柄与原始句柄共享定位与权限也不会产生歧义——这正是文档所说“Since AsyncFileContext functions are all position-explicit, this should not matter”的缘由。3.4 close 的调用约束与特殊语义async_file_handle.h 对close给出三条硬性约束close之后不能再使用该AsyncFileContext不调用close就销毁AsyncFileHandle是错误的对同一句柄调用两次close是错误的。由于AsyncFileHandle是shared_ptr在句柄持有者的析构函数里调用close也是允许的——close 动作会被入队从而让句柄存活到关闭操作完成之后。实现上async_file_context_thread_pool.ccActionCloseFile在构造时拷贝一份 fd因为close会把上下文的 fd 置为 -1拷贝可避免关闭在途时其他线程误用句柄同时executesEvenIfCancelled()返回 true意味着即使动作被取消close 也一定会完成。close入口本身在入队后立刻将fileDescriptor()置为 -1后续任何操作经checkFileAndEnqueueasync_file_context_thread_pool.cc检查到 fd 为 -1 都会返回FailedPreconditionError(file was already closed)而不再入队。3.5 createHardLink 与取消回滚createHardLink用于把匿名文件“转正”为具名文件通过/proc/self/fd/fd配合linkatAT_SYMLINK_FOLLOW建立硬链接。它是有副作用的动作因此重写了onCancelledBeforeCallbackasync_file_context_thread_pool.cc如果链接已创建但回调尚未执行即被取消则主动unlink刚创建的链接进行回滚。四、取消语义一个精心设计的无竞态保证4.1 CancelFunction 的四档行为按 async_file_action.h每个动作函数返回的取消函数针对动作所处阶段有四种行为动作已完成取消函数什么都不做回调正在执行取消函数阻塞等待直到回调执行完成动作正在执行中取消会移除任何消耗资源的返回值如刚打开的文件句柄并阻止回调被调用——例如 open 正在执行时取消会顺带把文件关掉动作仍在队列中取消直接阻止其执行。4.2 线程约束与保证文档明确了两个关键约定取消函数只能从当初传入请求的 dispatcher 所在线程调用以此保证“取消”与“回调”不可能并发发生README 的 cancellation 一节。在 async_file_manager_thread_pool.cc 中取消闭包以ASSERT(dispatcher nullptr || dispatcher-isThreadSafe())强约束调用线程。回调只在 dispatcher 线程上执行前提是动作未被取消README 的 callbacks 一节。实现保证若取消发生在回调执行之前且来自 dispatcher 线程则回调保证不会被执行不存在竞态。4.3 取消清理通道取消发生时并非简单丢弃动作而是区分两类无副作用动作如 write 被取消无需撤销写入取消后无需清理有副作用动作打开的文件、刚建的硬链接、重复的句柄通过postCancelledActionForCleanup把动作放进专门的cleanup_queue_由线程池 worker 调用其onCancelledBeforeCallback()完成回滚async_file_manager_thread_pool.cc、worker 循环。ActionWithFileResult::onCancelledBeforeCallbackasync_file_manager_thread_pool.cc就是典型例子如果 open 成功但回调未执行就对新句柄发起一次带空回调的close。五、回调执行的约束async_file_action.h 还针对回调在 dispatcher 线程执行的现实给出三条编程建议回调中不应访问可能已出作用域的变量动作在入队时按值捕获所需数据可能被其他线程修改的共享变量需要加锁保护回调内不能阻塞或做重活——耗时的处理应把结果再转交其他线程避免拖住事件循环。六、单例工厂与配置6.1 工厂的单例保证AsyncFileManagerFactoryasync_file_manager_factory.h是注册在 Envoy Singleton 管理器上的单例工厂SINGLETON_MANAGER_REGISTRATION见 async_file_manager_factory.cc。其核心保证同一个config.id()在同一 Envoy 实例中只对应一个AsyncFileManager。工厂内部以flat_hash_mapstring, ManagerAndConfig缓存若同一 id 再次以不同配置请求会抛出EnvoyException(AsyncFileManager mismatched config)。工厂返回的shared_ptr必须由调用方在 manager 生命周期内持有——单例管理器本身不持有对工厂的引用工厂只在仍有活跃引用时存在async_file_manager_factory.h。6.2 配置 Proto配置定义在 api/envoy/extensions/common/async_files/v3/async_file_manager.protoidstringmanager 的可选标识空字符串是合法的默认 id同一实例中复用同一 id 但配置不同属于错误thread_pool.thread_countuint32线程池线程数lte: 1024校验上限未设置或为 0 时默认取std::thread::hardware_concurrency()即硬件支持的并发线程数见 async_file_manager_thread_pool.ccmanager_type为oneof且标记validate.required目前唯一实现是thread_pool未设置会在工厂处抛出EnvoyException(unrecognized AsyncFileManagerConfig::ManagerType)。创建 manager 时还会检查posix.supportsAllPosixFileOperations()不支持全部 POSIX 文件操作的环境如部分 Windows 平台会直接抛出EnvoyException(AsyncFileManagerThreadPool not supported)。6.3 配置示例extensions: common: async_files: v3: AsyncFileManagerConfig: # 空 id 表示默认 manager同一 id 不同配置会报错 id: cache_writer thread_pool: thread_count: 4 # 不设或 0 时回退到硬件并发数上限 1024七、错误处理errno 到 absl::Status 的映射文件操作失败时框架通过 status_after_file_error.cc 把 POSIXerrno映射为语义明确的absl::Status供调用方精准区分失败类别absl 错误类别覆盖的 errnoPermissionDeniedErrorEACCES、EPERM、EROFSFailedPreconditionErrorEBADF、EBUSY、EISDIR、ELOOP、ENOTDIR、ETXTBSY、EWOULDBLOCKResourceExhaustedErrorEDQUOT、EMFILE、ENFILE、ENOMEM、ENOSPCAlreadyExistsErrorEEXISTInvalidArgumentErrorEFAULT、EINVAL、ENAMETOOLONGOutOfRangeErrorEFBIG、EOVERFLOWUnavailableErrorEINTRNotFoundErrorENODEV、ENOENT、ENXIOUnimplementedErrorEOPNOTSUPPOkStatus0UnknownError未识别的错误码同时触发ENVOY_BUG例如读取不存在的文件会得到NotFoundError磁盘满则会得到ResourceExhaustedError便于上层据此决定重试、降级或记录不同告警。八、典型使用流程与最佳实践综合文档与源码一个完整的“异步写临时文件后转正”流程如下通过工厂按AsyncFileManagerConfig获取或创建单例 manager调用createAnonymousFile(dispatcher, /tmp, on_complete)获得匿名句柄——注意保存返回的取消函数在on_complete中拿到AsyncFileHandle后依次入队write(dispatcher, contents, offset)内容在入队时被移动消费、按需truncate内容写完后调用createHardLink(dispatcher, final_path, ...)把匿名文件转正为具名文件或直接close每个动作都在回调里确认absl::Status结合第七节的错误类别做分支处理不再需要句柄时调用close——不要直接丢弃AsyncFileHandle也不要对已关闭句柄再次入队。实践要点可归纳为取消函数只在 dispatcher 线程调用回调也只会回到 dispatcher 线程回调内不做重活动作一旦入队即视为“投递”不要在入队后继续使用被移动走的Buffer对同一句柄的读写操作不要并发堆叠文档明确“There must not already be an action queued for this handle”需要并发时用duplicate拆分句柄AsyncFileManager必须是长生命周期对象单例线程池与队列状态与进程共存。九、小结AsyncFileManager/AsyncFileHandle是 Envoy 将“阻塞式文件操作”安全融入“事件驱动模型”的通用基础设施AsyncFileManager以单例线程池承载文件级操作AsyncFileHandle提供位置显式的单文件读写上下文五态原子状态机配合“队列 清理队列”双队列实现了无竞态的取消语义回调统一回归 dispatcher 线程并辅以errno → absl::Status的语义化映射。理解这套框架既有助于阅读 Envoy 中依赖文件 I/O 的扩展如各类缓存、日志落盘类过滤器的实现也为在 Envoy 中自研需要异步操作文件系统的扩展提供了可直接复用的接口范式。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表