ARTICLE DETAIL

资讯详情

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

Envoy HTTP Cache Filter 存储插件开发指南:深入理解 HttpCache、LookupContext 与 InsertContext 接口

Envoy HTTP Cache Filter 存储插件开发指南:深入理解 HttpCache、LookupContext 与 InsertContext 接口 Envoy HTTP Cache Filter 存储插件开发指南深入理解 HttpCache、LookupContext 与 InsertContext 接口【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoyEnvoy 的 HTTP Cache Filter缓存过滤器承担了 HTTP 缓存语义中最复杂的部分——缓存有效性判断、请求合并、条件验证Revalidation、Range 请求处理等而把响应内容到底存在哪里这一职责完全委托给HttpCache接口的实现。本文以仓库文档 cache_filter_plugins.md 为主线结合 simple_http_cache 示例实现与 CacheFilter 源码讲解如何为 Envoy 编写自定义缓存存储插件、四个核心接口的职责划分以及查找与插入的完整调用流程。读完本文你将具备独立实现并注册一个 Envoy HTTP 缓存后端的能力。一、为什么把存储层设计成插件HTTP 缓存过滤器把缓存语义与缓存存储彻底解耦。缓存语义何时可缓存、条目何时过期、何时需要回源验证、如何处理 Range 请求由过滤器统一实现而实际存储由HttpCache接口的实现承担。这些实现可以覆盖持久化、性能与分布式的整个谱系——从本地内存缓存到全球分布式持久缓存可以是完全自研的缓存也可以是对本地或远程开源/专有缓存如 Redis、Memcached、各类 CDN 存储的包装或适配层。从源码可以看到HttpCache接口的定义位于 http_cache.h它只要求实现四个方法class HttpCache { public: virtual LookupContextPtr makeLookupContext(LookupRequest request, Http::StreamFilterCallbacks callbacks) PURE; virtual InsertContextPtr makeInsertContext(LookupContextPtr lookup_context, Http::StreamFilterCallbacks callbacks) PURE; virtual void updateHeaders(const LookupContext lookup_context, const Http::ResponseHeaderMap response_headers, const ResponseMetadata metadata, UpdateHeadersCallback on_complete) PURE; virtual CacheInfo cacheInfo() const PURE; virtual ~HttpCache() default; };文档对插件作者提出一条重要建议如果你编写了新的缓存存储实现请尽可能将它贡献回 Envoy 仓库。这是保证它跟随 Envoy 演进持续更新的唯一方式也能让其他开发者帮助你修复缺陷与改进功能。二、四个核心接口的职责按照文档指引实现一个缓存后端需要实现四个小而明确的接口HttpCache、HttpCacheFactory、LookupContext与InsertContext。仓库中的 simple_http_cache.h 是官方示例实现阅读本部分时建议对照该文件。2.1 HttpCache——缓存的门面示例实现SimpleHttpCache见 simple_http_cache.cc职责HttpCache代表一个实际存储响应的缓存实例。它提供方法建立缓存查找makeLookupContext、插入makeInsertContext以及更新已缓存响应的头部updateHeaders。makeLookupContext接收一个LookupRequest携带请求头、时间戳、Vary 允许列表等并返回管理单次查找状态的对象。makeInsertContext接收一次查找的LookupContext缓存未命中时并返回管理单次插入状态的对象。updateHeaders用于过期条目被成功验证如回源得到 304后只更新条目的头部而不触碰 body 与 trailers。cacheInfo()返回缓存的静态信息例如SimpleHttpCache::cacheInfo()返回名称为envoy.extensions.http.cache.simple的CacheInfo见 simple_http_cache.cc。CacheInfo结构中还包含supports_range_requests_字段用于声明缓存是否原生支持 Range 请求见 http_cache.h。2.2 HttpCacheFactory——插件注册与工厂示例实现SimpleHttpCacheFactory见 simple_http_cache.cc职责HttpCacheFactory负责根据缓存过滤器配置中给出的名字创建HttpCache实现。该工厂继承Config::TypedFactory其类别category固定为envoy.http.cache核心方法是getCache接收过滤器的CacheConfig与ServerFactoryContext可用于获取异步客户端、统计作用域等基础设施返回一个std::shared_ptrHttpCache其生命周期至少与使用它的 CacheFilter 一样长见 http_cache.h。SimpleHttpCacheFactory的注册过程可以看作插件的标准模板constexpr absl::string_view Name envoy.extensions.http.cache.simple; class SimpleHttpCacheFactory : public HttpCacheFactory { public: std::string name() const override { return std::string(Name); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { return std::make_uniqueSimpleHttpCacheConfig(); } std::shared_ptrHttpCache getCache(const envoy::extensions::filters::http::cache::v3::CacheConfig, Server::Configuration::ServerFactoryContext context) override { return context.singletonManager().getTypedSimpleHttpCache( SINGLETON_MANAGER_REGISTERED_NAME(simple_http_cache_singleton), createCache); } }; static Registry::RegisterFactorySimpleHttpCacheFactory, HttpCacheFactory register_;这里值得注意的设计细节插件名envoy.extensions.http.cache.simple是配置中用来选择后端的唯一标识通过SINGLETON_MANAGER_REGISTRATION与singletonManager().getTyped...()将缓存实例注册为单例保证同进程内多个过滤器共享同一个缓存存储实例这是内存型缓存后端共享数据的关键工厂类无需写构造函数通过静态register_对象完成向 Envoy 扩展注册表的登记。作为对比仓库中另一个更接近生产级实现的FileSystemHttpCacheFactory以envoy.extensions.http.cache.file_system_http_cache为名其getCache会通过MessageUtil::unpackTo解析typed_config并借助AsyncFileManager与缓存驱逐线程构建基于文件系统的持久化缓存见 config.cc。2.3 LookupContext——单次查找的状态容器示例实现SimpleLookupContext见 simple_http_cache.cc职责LookupContext代表一次独立的查找操作是存放查找过程中各种 per-lookup 状态如当前已取回的部分 body、trailers、取消标志等的理想位置。它必须实现三个数据获取方法与一个生命周期钩子class LookupContext { public: virtual void getHeaders(LookupHeadersCallback cb) PURE; virtual void getBody(const AdjustedByteRange range, LookupBodyCallback cb) PURE; virtual void getTrailers(LookupTrailersCallback cb) PURE; virtual void onDestroy() PURE; virtual ~LookupContext() default; };各方法语义完整注释见 http_cache.hgetHeaders从缓存取出头部只能调用一次。回调携带LookupResult与end_stream标志getBody按AdjustedByteRange读取下一段 body。缓存可以按自己的分片粒度返回少于请求的字节数回调返回的缓冲区指针不能为空只有在共享流式条目缓存条目尚未写完即可读配合 trailers 的场景下才允许传入空缓冲区以提示过滤器去取 trailersgetTrailers当 body 读完且end_stream为 false 时被调用返回 trailersonDestroy在LookupContext被销毁前调用负责取消所有未完成的异步操作如 RPC、定时器。接口注释中以RPC 完成回调与析构函数并发导致数据竞争为例解释了为什么必须有onDestroy而非仅在析构函数里清理连接突然中断时过滤器线程可能在析构对象的同时另一线程的异步回调恰好到达见 http_cache.h。SimpleLookupContext的实现展示了两个关键约束的落地方式回调必须投递到过滤器的 dispatcher 上。SimpleLookupContext把结果包装进dispatcher_.post([...])并利用std::shared_ptrbool cancelled_做取消保护——onDestroy将其置为 true若回调执行时已取消则直接丢弃见 simple_http_cache.cc查找结果的生成要交给LookupRequest::makeLookupResult。该方法会依据 HTTP 缓存校验逻辑计算条目的age、注入Age响应头、判定条目状态是Ok还是RequiresValidation并根据请求头生成 Range 详情见 http_cache.cc。2.4 InsertContext——单次插入的状态容器示例实现SimpleInsertContext见 simple_http_cache.cc职责InsertContext代表一次独立的插入操作存放 per-insert 状态如正在累积的 body 片段、trailers、提交标志等。它的三个方法与一个钩子与查找侧对称class InsertContext { public: virtual void insertHeaders(const Http::ResponseHeaderMap response_headers, const ResponseMetadata metadata, InsertCallback insert_complete, bool end_stream) PURE; virtual void insertBody(const Buffer::Instance fragment, InsertCallback ready_for_next_fragment, bool end_stream) PURE; virtual void insertTrailers(const Http::ResponseTrailerMap trailers, InsertCallback insert_complete) PURE; virtual void onDestroy() PURE; virtual ~InsertContext() default; };接口文档特别强调的流控契约见 http_cache.h插入是按片段流式进行的片段大小由客户端过滤器决定但节奏由缓存掌控。客户端必须等缓存回调ready_for_next_fragment之后才发送下一个片段以免数据涌入过快客户端可以通过丢弃InsertContextPtr中止插入缓存可以通过向ready_for_next_fragment传入false主动中止实现必须将回调投递到过滤器的 dispatcher任何可能静默失败的异步操作都必须带超时避免内存泄漏onDestroy负责在销毁前取消未完成的异步活动防止回调与析构竞争。SimpleInsertContext::commit()展示了带Vary响应与普通响应在存储上的差异若响应头含Vary调用cache_.varyInsert(...)否则调用cache_.insert(...)见 simple_http_cache.cc。SimpleHttpCache内部用absl::flat_hash_mapKey, Entry, MessageUtil, MessageUtil加互斥锁存放条目Entry由响应头、元数据、body 字符串与 trailers 组成见 simple_http_cache.h。三、查找与插入的完整流程3.1 查找流程Cache Hit 路径文档给出的流程如下要发起一次查找过滤器调用HttpCache::makeLookupContext得到一个LookupContextPtr过滤器调用LookupContext::getHeaders询问是否存在缓存响应若找到结果LookupContext实现必须调用LookupRequest::makeLookupResult把结果传给回调随后过滤器发起一系列getBody请求需要时再调用getTrailers若回调中的LookupResult表明未找到响应过滤器放行请求回源若源站返回可缓存响应过滤器调用HttpCache::makeInsertContext并通过其方法插入响应。这段流程在 cache_filter.cc 中有清晰的一一对应实现。CacheFilter::decodeHeaders在请求可缓存时创建LookupRequest并调用cache_-makeLookupContext(...)随后调用getHeaders见 cache_filter.ccCacheFilter::getHeaders把回调包装后传给lookup_-getHeaders(...)并断言缓存必须把回调投递到过滤器的 dispatcher见 cache_filter.cc。回调返回后CacheFilter::onHeaders根据cache_entry_status_分流Ok条目新鲜可直接命中进入handleCacheHit若请求带 Range 且命中条目支持进入handleCacheHitWithRangeRequest单区间请求会被包装为 206 Partial Content 响应并调整 Content-Range 与 Content-Length见 cache_filter.ccRequiresValidation条目过期handleCacheHitWithValidation会依据缓存的ETag注入If-None-Match、依据Last-Modified或Date注入If-Modified-Since条件头后回源验证见 cache_filter.ccUnusable作为未命中直接回源LookupError转为NotServingFromCache状态放行请求。命中后的 body 读取同样体现按片读取的契约CacheFilter::getBody每次以编码缓冲区上限encoder_callbacks_-bufferLimit()为 0 时退化为内部常量MAX_BYTES_TO_FETCH_FROM_CACHE_PER_REQUEST为限向缓存请求一个区间收到数据后裁剪剩余区间直到全部取完再取 trailers见 cache_filter.cc 与 cache_filter.cc。文档末尾的示意图展示了一个典型场景对缓存中新鲜存在、约 5M、无 trailers的资源发起 GET 请求。对于同步内存缓存这一切都发生在CacheFilter::decodeHeaders之内实线箭头表示同步函数调用虚线箭头表示异步函数调用或其回调属于缓存实现的对象HttpCache与LookupContext以蓝色标注其余对象属于缓存过滤器或 Envoy 本身。这张图完整体现了缓存语义在过滤器、存储细节在插件的分工。3.2 插入流程Cache Miss 路径当查找未命中且源站返回可缓存响应时插入路径启动过滤器调用HttpCache::makeInsertContext注意文档明确说明插入上下文由未命中的那次LookupContext构造而来SimpleHttpCache::makeInsertContext正是对SimpleLookupContext做dynamic_cast后复用其 key、请求头与 Vary 允许列表见 simple_http_cache.cc按顺序调用insertHeaders、多次insertBody、可能有一次insertTrailers每个回调返回true表示成功可继续false表示中止end_stream标志表明这是最后一个片段实现此时应执行真正的提交如SimpleInsertContext中的commit()。另外需要指出SimpleHttpCache::updateHeaders是验证后更新的具体实现它锁定互斥量找到条目后按VaryHeaderUtils处理变体键再调用applyHeaderUpdate只合并需要更新的头部、更新元数据body 与 trailers 保持不变见 simple_http_cache.cc。headersNotToUpdate常量列出的头部被跳过因为这些头要么由其他应用逻辑更新要么属于 IETF HTTP 缓存草案中规定的类别见 simple_http_cache.h。四、配置缓存后端从 simple 到 file_system4.1 CacheConfig 关键字段缓存后端的配置入口是 cache.proto 中的CacheConfig关键字段包括字段说明typed_config存储实现专属配置Any 类型除非disabled为 true 否则必填扩展类别为envoy.http.cachedisabled为 true 时过滤器成为 no-op可用于通过 ECDS 动态开关过滤器allowed_vary_headers定义允许的Vary响应头匹配规则。插入时作为白名单响应Vary提及了任何未被规则匹配的头名则不入缓存查找时控制哪些请求头会被传给存储实现key_creator_params未实现缓存键定制可排除 scheme/host、通过 QueryParameterMatcher 限定参与建键的查询参数max_body_bytes过滤器允许插入缓存的最大 body 大小0 表示不限存储实现仍可有自己的上限ignore_request_cache_control_header为 true 时忽略请求中的cache-control: no-cache与pragma: no-cache避免每次强制回源验证其中ignore_request_cache_control_header与LookupRequest构造函数的同名参数一一对应见 http_cache.h并在 cache_filter.h 的CacheFilterConfig中被解析后传给过滤器。4.2 配置示例使用官方示例simple后端的最小配置如下http_filters: - name: envoy.filters.http.cache typed_config: type: type.googleapis.com/envoy.extensions.filters.http.cache.v3.CacheConfig typed_config: type: type.googleapis.com/envoy.extensions.http.cache.simple_http_cache.v3.SimpleHttpCacheConfig - name: envoy.filters.http.router选择后端时typed_config中type的类型名称必须与工厂name()返回的插件名对应——SimpleHttpCacheFactory的createEmptyConfigProto返回SimpleHttpCacheConfig因此配置中的类型即...simple_http_cache.v3.SimpleHttpCacheConfig见 simple_http_cache.cc。生产级文件系统缓存后端的类型为envoy.extensions.http.cache.file_system_http_cache.v3.FileSystemHttpCacheConfig配置项包含cache_path、max_cache_size_bytes等详见 FileSystemHttpCacheConfig可参考其 DESIGN.md 了解驱逐线程与文件块布局等实现细节。4.3 缓存的正确性约束从源码的断言与契约中可以提炼出插件作者必须遵守的硬性约束回调投递getHeaders、getBody、getTrailers及全部插入回调都必须通过dispatcher_.post(...)投递并包裹已取消则不执行的保护逻辑cache_filter.cc 中有显式断言回调有界性getBody回调的字节数不得超过请求的AdjustedByteRangeonBody会对超长片段触发断言并 reset 流cache_filter.cc无界 Range 语义缓存条目的 body 与 trailers 应可分别提供body 读完而end_stream为 false 时过滤器会继续请求 trailers键的稳定性对于持久化缓存建议使用stableHashKey生成跨重启、跨架构稳定的 64 位哈希同时提供缓存响应时必须确保 key而不只是其哈希完全匹配见 http_cache.h。五、如何验证你的插件实现仓库为示例实现提供了完整的单元测试可作为新插件测试的模板simple_http_cache_test.cc 覆盖了SimpleHttpCache的查找、插入、Vary 变体键与头部更新逻辑此外cache_filter_test.cc位于 test/extensions/filters/http/cache 目录通过 mock 的HttpCache验证过滤器侧的查找/插入编排行为包括未命中回源、命中直出、过期验证等状态机转换。编写测试时至少应覆盖缓存命中getHeaders返回Ok状态body 按分片被getBody完整取回无 trailers 时以end_stream收尾缓存未命中LookupResult为空 → 请求回源 → 源站可缓存响应被makeInsertContext完整插入需要验证过期条目触发updateHeaders仅头部更新、body 不变异步取消查找/插入过程中连接中断onDestroy后不再有任何回调执行。六、小结实现一个 Envoy HTTP 缓存存储插件本质就是回答四个问题缓存实例是什么HttpCache、如何创建它HttpCacheFactory、单次查找怎么做LookupContext、单次插入怎么做InsertContext。HTTP 缓存语义新鲜度计算、验证、Range、Vary 处理由 CacheFilter 统一完成插件只需聚焦于存储本身并严格遵守回调投递到 dispatcher、异步操作可取消、流控由缓存主导三条契约。无论是简单的内存缓存还是接入远程分布式存储simple_http_cache 都是一个小而完整的参考起点值得逐行研读。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表