Apache APISIX proxy-cache 插件实战:基于磁盘与内存的响应缓存配置与原理
2026/9/15 20:23:04 网站建设 项目流程

Apache APISIX proxy-cache 插件实战:基于磁盘与内存的响应缓存配置与原理

【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix

导读

proxy-cache是 Apache APISIX 提供的一项核心缓存插件,用于缓存上游服务的响应数据,从而显著降低后端压力并加速客户端访问。本文以官方文档 docs/zh/latest/plugins/proxy-cache.md 为主体,结合仓库中 apisix/plugins/proxy-cache/ 目录下的源码实现,系统讲解插件的属性定义、磁盘与内存两种缓存策略的配置方法、缓存命中状态码(MISS/HIT/BYPASS/EXPIRED/STALE)的语义,以及如何通过PURGE方法清除缓存。读完本文,你将能够独立完成 proxy-cache 插件的部署、调优与排障。

插件能力概述

proxy-cache插件提供缓存后端响应数据的能力,可以与其他插件一起使用,支持基于磁盘和基于内存两种缓存策略。从源码结构看,插件被拆分为多个模块:init.lua(入口与 Schema 校验)、disk_handler.lua(磁盘缓存处理器)、memory_handler.lua(内存缓存处理器)、memory.lua(基于ngx.shared.DICT的共享内存封装)以及 util.lua(通用工具函数)。

插件支持按响应码和**请求模式(method)**来决定哪些数据需要缓存,也支持通过no_cachecache_bypass属性配置更复杂的缓存策略,例如按请求参数动态决定是否绕过缓存。

插件属性详解

插件在 init.lua 中定义了完整的 JSON Schema,官方文档属性表与源码 Schema 一一对应,整理如下:

名称类型必选项默认值有效值描述
cache_strategystringdisk["disk", "memory"]缓存策略,指定缓存数据存储在磁盘还是内存中
cache_zonestringdisk_cache_one-指定使用的缓存区域,不同区域可配置不同路径,需在conf/config.yaml中预定义,否则缓存无效
cache_keyarray[string]["$host", "$request_uri"]-缓存 key,支持 Nginx 变量,如["$host", "$uri", "-cache-id"]
cache_bypassarray[string]--值为非空且非0时跳过缓存检查(不在缓存中查找数据),支持变量,如["$arg_bypass"]
cache_methodarray[string]["GET", "HEAD"]["GET", "POST", "HEAD"]按请求 method 决定是否需要缓存
cache_http_statusarray[integer][200, 301, 404][200, 599]按 HTTP 响应码决定是否需要缓存
hide_cache_headersbooleanfalse-true时不将ExpiresCache-Control响应头返回给客户端
cache_controlbooleanfalse-true时遵守 HTTP 规范中Cache-Control的行为
no_cachearray[string]--值为非空或非0时不缓存数据,支持变量
cache_ttlinteger300(秒)最小值 1cache_control未开启,或开启后服务端未返回缓存控制头时使用的默认缓存时间

从源码 init.lua 可以看到几处 Schema 层面的细节约束:

  • cache_keycache_bypassno_cache的数组元素均通过正则(^[^\$].+$|^\$[0-9a-zA-Z_]+$)校验,即元素要么是普通字符串,要么是$开头的变量表达式;
  • cache_http_status元素取值被限制在200599之间,且要求数组内元素唯一(uniqueItems = true);
  • cache_method仅允许GETPOSTHEAD三种取值,同样要求唯一;
  • cache_ttl最小值为 1。

注意事项(官方文档要点)

  • 对于基于磁盘的缓存,不能动态配置缓存的过期时间,只能通过后端响应头ExpiresCache-Control设置过期时间;当后端响应头中没有这两个字段时,默认缓存时间为 10 秒钟;
  • 当上游服务不可用时,APISIX 会返回502504状态码,此时默认缓存时间同样为 10 秒钟;
  • 变量以$开头,不存在时等价于空字符串。变量与字符串可以结合使用,但需要以数组形式分开书写,最终变量解析后会与字符串拼接在一起。

需要特别说明的是,Schema 中cache_ttl的默认值为300,而磁盘缓存场景下上游未指定缓存时间的默认值为 10 秒,二者的差异源自配置文件中apisix.proxy_cache.cache_ttl的设定,详见下文。

特殊变量限制

init.lua 的check_schema函数中有一个值得注意的限制:当cache_key中包含$request_method变量时,插件会直接返回校验错误"cache_key variable $request_method unsupported",因为该方法变量与缓存的语义存在冲突,配置时应避免使用。

配置缓存区域:conf/config.yaml

启用插件前,需要先在 APISIX 配置文件conf/config.yaml中预定义缓存区域。参考 conf/config.yaml.example 中的官方示例:

apisix: proxy_cache: cache_ttl: 10s # 如果上游未指定缓存时间,则为默认磁盘缓存时间 zones: - name: disk_cache_one memory_size: 50m disk_size: 1G disk_path: /tmp/disk_cache_one cache_levels: 1:2 # - name: disk_cache_two # memory_size: 50m # disk_size: 1G # disk_path: "/tmp/disk_cache_two" # cache_levels: "1:2" - name: memory_cache memory_size: 50m

各字段含义如下:

  • cache_ttl:上游未指定缓存时间时的默认磁盘缓存时间(示例为10s);
  • zones:缓存区域列表,每个区域通过name唯一标识;
  • memory_size:用于存储缓存索引(index)的共享内存大小;
  • disk_size:磁盘缓存数据的总容量上限;
  • disk_path:磁盘缓存文件存放路径;
  • cache_levels:磁盘缓存的目录层级(如1:2表示两级目录,第一级取 1 个字符、第二级取 2 个字符)。

check_schema(init.lua)在插件配置提交时会校验cache_zone是否存在于apisix.proxy_cache.zones中,同时校验缓存策略与区域类型的匹配性:内存策略(memory)对应的区域不能带有disk_path,磁盘策略(disk)对应的区域必须带有disk_path,否则返回"invalid or empty cache_zone for cache_strategy"。若cache_zone根本不在配置中,则返回"cache_zone xxx not found"

启用插件

准备工作:获取 admin_key

通过 Admin API 配置路由时,需要先取得管理员密钥。可从conf/config.yaml中提取并存入环境变量:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

使用基于磁盘的缓存

以下示例在路由上启用proxy-cache插件,使用默认的磁盘策略cache_strategy: "disk"与默认区域disk_cache_one

curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/ip", "plugins": { "proxy-cache": { "cache_key": ["$uri", "-cache-id"], "cache_bypass": ["$arg_bypass"], "cache_method": ["GET"], "cache_http_status": [200], "hide_cache_headers": true, "no_cache": ["$arg_test"] } }, "upstream": { "nodes": { "httpbin.org": 1 }, "type": "roundrobin" } }'

该示例演示了以下组合用法:

  • cache_key使用$uri变量与字符串-cache-id拼接,形成形如/ip-cache-id的缓存键;
  • cache_bypass监听请求参数bypass,当?bypass=1之类取值非空且非0时直接绕过缓存查找;
  • no_cache监听请求参数test,非空且非0时不写入缓存;
  • hide_cache_headers: true表示不向客户端透传Expires/Cache-Control响应头。

使用基于内存的缓存

内存缓存需要显式指定cache_strategy: "memory",并选用配置文件中对应的内存区域(如memory_cache):

curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/ip", "plugins": { "proxy-cache": { "cache_strategy": "memory", "cache_zone": "memory_cache", "cache_ttl": 10 } }, "upstream": { "nodes": { "httpbin.org": 1 }, "type": "roundrobin" } }'

与磁盘缓存不同,内存缓存可直接通过cache_ttl指定缓存时间(此处为 10 秒)。从源码看,内存策略的 TTL 解析逻辑位于 memory_handler.lua:当cache_control开启时使用Cache-Control指令解析出的资源 TTL,否则使用conf.cache_ttl

调用链简析

从 init.lua 可以看到插件贯穿了三个执行阶段:

  1. access 阶段:根据cache_strategy选择memory_handlerdisk_handler,并调用其access方法,先计算缓存 key(写入ctx.var.upstream_cache_key),再执行缓存查找/旁路判断;
  2. header_filter 阶段:调用对应 handler 的header_filter,决定响应是否可缓存、是否需要回写Apisix-Cache-Status等响应头;
  3. body_filter 阶段:仅内存策略实现(memory_handler.lua),通过core.response.hold_body_chunk暂存响应体,将状态码、响应体、响应头、TTL 与时间戳序列化后写入共享内存字典。

测试插件

按上述任一配置启用插件后,请求该路由:

curl http://127.0.0.1:9080/ip -i

首次请求时数据未缓存,返回200状态码且响应头中包含Apisix-Cache-Status: MISS

HTTP/1.1 200 OK ··· Apisix-Cache-Status: MISS hello

再次请求同一路由:

curl http://127.0.0.1:9080/ip -i

此时响应头中Apisix-Cache-Status变为HIT,表示数据已被缓存并命中:

HTTP/1.1 200 OK ··· Apisix-Cache-Status: HIT hello

若将cache_zone设置为无效值(例如"cache_zone": "invalid_disk_cache",即与conf/config.yaml中预定义区域不一致),则返回404状态码。

清除缓存:PURGE 方法

清除缓存数据只需将请求 method 指定为PURGE

curl -i http://127.0.0.1:9080/ip -X PURGE

返回200表示删除成功;若缓存数据不存在则返回404

HTTP/1.1 200 OK

从源码看,两种策略对PURGE的处理路径不同但语义一致:

  • 磁盘策略(disk_handler.lua):根据缓存 key 计算文件路径(MD5 后按cache_levels分层),文件存在则os.remove并返回200,否则返回404
  • 内存策略(memory_handler.lua):调用共享字典的purge方法,未命中时返回404

磁盘缓存文件的命名与目录分层算法位于 util.lua:先用ngx.md5(cache_key)生成 32 位 MD5 字符串,再按cache_levels(如1:2)从末尾向前切分作为各级子目录名,最终路径形如/tmp/disk_cache_one/a/bc/<md5全文>

缓存状态码全解析

除了文档中提到的MISSHIT,结合源码与测试用例(见 t/plugin/proxy-cache/memory.t 与 t/plugin/proxy-cache/disk.t),Apisix-Cache-Status实际会出现以下取值:

状态码触发场景源码依据
MISS缓存中不存在该 keymemory_handler.lua
HIT缓存命中且未过期memory_handler.lua
BYPASScache_bypass/no_cache命中,或Cache-Control: no-store/no-cache,或方法不匹配memory_handler.lua
EXPIRED缓存条目已过期(内存策略,通过get_stale感知)memory_handler.lua
STALE缓存条目超过 TTL 仍在窗口期内(配合max-age/max-stale/min-fresh指令)memory_handler.lua

Cache-Control 指令支持

cache_control: true时,插件通过 parse_directive_header 解析上游返回的Cache-Control响应头,支持以下指令:

  • no-cache/no-store/private:响应不可缓存(cacheable_response);
  • max-age/s-maxage:作为资源 TTL 计算依据(parse_resource_ttl);
  • max-stale/min-fresh:控制过期窗口,命中时返回STALE
  • 请求方携带only-if-cached且缓存未命中时,直接返回504(memory_handler.lua)。

同时 memory_handler.lua 定义了 hop-by-hop 头列表(ConnectionKeep-AliveTransfer-EncodingContent-LengthApisix-Cache-Status等),这些响应头在缓存命中回放时会被过滤,避免与当前连接语义冲突。

删除插件

需要移除插件时,通过 Admin API 将路由的plugins置空即可,APISIX 会自动热加载新配置,无需重启服务:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \ -H "X-API-KEY: $admin_key" -X PUT -d ' { "uri": "/ip", "plugins": {}, "upstream": { "type": "roundrobin", "nodes": { "httpbin.org": 1 } } }'

测试用例佐证

仓库中的测试文件对本文所述行为提供了完整验证:

  • t/plugin/proxy-cache/disk.t:覆盖磁盘策略下的首次请求MISS、二次请求HITcache_bypass触发的BYPASSPURGE删除缓存、以及hide_cache_headers对响应头的影响;
  • t/plugin/proxy-cache/memory.t:覆盖内存策略下的EXPIREDSTALEBYPASSPURGE,以及Cache-Control各指令的解析与生效场景。

这些测试直接印证了上文状态码表格与Cache-Control指令支持范围的正确性,可作为二次开发或排障时的行为基准。

小结

proxy-cache插件以极低的接入成本为 APISIX 提供了完整的响应缓存能力:通过conf/config.yaml预定义缓存区域,在路由上按需声明缓存 key、缓存方法与状态码,即可同时获得磁盘与内存两种策略;PURGE方法解决了缓存主动失效的问题;而MISS/HIT/BYPASS/EXPIRED/STALE状态码体系则为缓存效果观测与问题定位提供了清晰的语言。深入源码后可以看到,插件的核心逻辑集中封装在 apisix/plugins/proxy-cache/ 目录的 5 个模块中,结构清晰、易于扩展,是理解 APISIX 插件机制与 HTTP 缓存语义的绝佳范本。

【免费下载链接】apisixThe Cloud-Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/ap/apisix

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询