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_cache与cache_bypass属性配置更复杂的缓存策略,例如按请求参数动态决定是否绕过缓存。
插件属性详解
插件在 init.lua 中定义了完整的 JSON Schema,官方文档属性表与源码 Schema 一一对应,整理如下:
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
|---|---|---|---|---|---|
| cache_strategy | string | 否 | disk | ["disk", "memory"] | 缓存策略,指定缓存数据存储在磁盘还是内存中 |
| cache_zone | string | 否 | disk_cache_one | - | 指定使用的缓存区域,不同区域可配置不同路径,需在conf/config.yaml中预定义,否则缓存无效 |
| cache_key | array[string] | 否 | ["$host", "$request_uri"] | - | 缓存 key,支持 Nginx 变量,如["$host", "$uri", "-cache-id"] |
| cache_bypass | array[string] | 否 | - | - | 值为非空且非0时跳过缓存检查(不在缓存中查找数据),支持变量,如["$arg_bypass"] |
| cache_method | array[string] | 否 | ["GET", "HEAD"] | ["GET", "POST", "HEAD"] | 按请求 method 决定是否需要缓存 |
| cache_http_status | array[integer] | 否 | [200, 301, 404] | [200, 599] | 按 HTTP 响应码决定是否需要缓存 |
| hide_cache_headers | boolean | 否 | false | - | 为true时不将Expires和Cache-Control响应头返回给客户端 |
| cache_control | boolean | 否 | false | - | 为true时遵守 HTTP 规范中Cache-Control的行为 |
| no_cache | array[string] | 否 | - | - | 值为非空或非0时不缓存数据,支持变量 |
| cache_ttl | integer | 否 | 300(秒) | 最小值 1 | 当cache_control未开启,或开启后服务端未返回缓存控制头时使用的默认缓存时间 |
从源码 init.lua 可以看到几处 Schema 层面的细节约束:
cache_key、cache_bypass、no_cache的数组元素均通过正则(^[^\$].+$|^\$[0-9a-zA-Z_]+$)校验,即元素要么是普通字符串,要么是$开头的变量表达式;cache_http_status元素取值被限制在200到599之间,且要求数组内元素唯一(uniqueItems = true);cache_method仅允许GET、POST、HEAD三种取值,同样要求唯一;cache_ttl最小值为 1。
注意事项(官方文档要点)
- 对于基于磁盘的缓存,不能动态配置缓存的过期时间,只能通过后端响应头
Expires或Cache-Control设置过期时间;当后端响应头中没有这两个字段时,默认缓存时间为 10 秒钟; - 当上游服务不可用时,APISIX 会返回
502或504状态码,此时默认缓存时间同样为 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 可以看到插件贯穿了三个执行阶段:
- access 阶段:根据
cache_strategy选择memory_handler或disk_handler,并调用其access方法,先计算缓存 key(写入ctx.var.upstream_cache_key),再执行缓存查找/旁路判断; - header_filter 阶段:调用对应 handler 的
header_filter,决定响应是否可缓存、是否需要回写Apisix-Cache-Status等响应头; - 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全文>。
缓存状态码全解析
除了文档中提到的MISS与HIT,结合源码与测试用例(见 t/plugin/proxy-cache/memory.t 与 t/plugin/proxy-cache/disk.t),Apisix-Cache-Status实际会出现以下取值:
| 状态码 | 触发场景 | 源码依据 |
|---|---|---|
MISS | 缓存中不存在该 key | memory_handler.lua |
HIT | 缓存命中且未过期 | memory_handler.lua |
BYPASS | cache_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 头列表(Connection、Keep-Alive、Transfer-Encoding、Content-Length、Apisix-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、二次请求HIT、cache_bypass触发的BYPASS、PURGE删除缓存、以及hide_cache_headers对响应头的影响; - t/plugin/proxy-cache/memory.t:覆盖内存策略下的
EXPIRED、STALE、BYPASS、PURGE,以及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),仅供参考