APISIX 的 proxy-cache 插件可以对上游的查询进行缓存,这样就不需要上游的应用服务自己实现缓存了,或者也能少实现一部分缓存,通用的交给插件来做。

下面的操作都是基于 APISIX 3.2 版本进行配置,关于 proxy-cache 的详细配置的可以参考文档:https://apisix.apache.org/docs/apisix/3.2/plugins/proxy-cache/ 不过文档很多地方说的不是太清楚,这里把重点的地方补充一下,首先是插件的参数:

  1. cache_strategy 这个表示我们插件的缓存策略,支持配置 disk 或 memory,默认是 disk
  2. cache_zone 这个表示我们使用的存储区域,对于内存或者磁盘都可以详细配置,这个需要在配置文件中进行配置。
  3. cache_key 这个是我们要缓存请求的 key,key 是判断是否缓存的依据,可以指定多个 APISIX 或者 nginx 的变量,也可以指定常量字符串。需要注意的是不是所有的变量都可以使用,比如 request_body 变量就是不能使用的,因为如果 body 太大,上下文传递会有比较大的开销,所以设置了结果也是空的。
  4. cache_bypass  这个指定不进行缓存的情况,也是一个数组,可以写多个变量,如果至少有 1 个变量的不为空并且不等于 0,那么就会跳过缓存。这个配置不太好理解,具体是什么变量并没有说,通过查看插件源码发现取的是 ctx.var 中的值,所以其实这个并不是请求的 URL 参数,也不是请求的 Header 内容,而是 APISIX 里面的变量,当然也包括 nginx 的变量,当变量存在时就会自动绕过请求,如果内置变量不满足要求,我们可以通过实现自定义变量来解决。
  5. cache_method 这个好理解,就是哪些方法会被缓存,可以指定一个数组。
  6. cache_http_status 这个表示上游的哪些状态码会被缓存,也是一个数组。
  7. hide_cache_headers  如果设置为 true,会将响应的 Expires 和 Cache-Control 头响应到客户端中,默认是会去掉的。
  8. cache_control 如果设置为 true,将按照 HTTP 规范中的行为进行缓存,这个仅对于内存策略生效。
  9. no_cache  这个和 cache_bypass 非常类似,同样是配置一个变量列表,不过这个是在响应阶段处理,也就是上游服务主动告诉 APISIX 这个请求是否缓存,变量的含义和上面一样,支持内置变量和自定义变量。
  10. cache_ttl  缓存的过期时间,单位是秒,当上面的 cache_control 未启用或者服务器未返回缓存控制头时生效,如果启动了 cache_control 则以响应的控制头为准,同样这个仅对内存策略生效。

根据官网的说明,有下面的几点需要注意:

  1. 如果是基于磁盘的缓存,无法在插件中设置过期时间,默认就是 10s,但是可以通过服务的响应头 Expires 和 Cache-Control 设置过期时间。
  2. 如果上游服务不可用时,那么 APISIX 会返回 502 和 504 状态码,这个时候缓存时间是默认的 10s。
  3. 在 cache_key, cache_bypass 以及 no_cache 中指定的变量,如果变量值不存在,则结果为空字符串。如果其中写了常量,结果会将变量值和常量一块拼接起来。

开启插件之前,首先需要在本地配置文件添加缓存区域的配置,否则启用插件以及后续调用时会报错,首先编辑 config.yaml 添加配置如下:

apisix:
# ...
proxy_cache:
# 磁盘缓存时间 默认是 10s,可以在这里修改
cache_ttl: 10s
zones:
# 磁盘的 cache_zone 的名称
- name: disk_cache_one
# 索引需要在内存中存储,设置内存的大小限制
memory_size: 50m
# 磁盘缓存的大小限制
disk_size: 1G
# 缓存文件的路径
disk_path: "/tmp/disk_cache_one"
# 缓存级别配置
cache_levels: "1:2"
# 内存的 cache_zone 名称
- name: memory_cache_one
# 内存缓存的大小限制
memory_size: 512m

上面就分别配置了磁盘和内存的 cache_zone 当然可以配置多个,比如大小限制不一样或者存储路径不一样,针对于不同插件的配置。再比如我们这里只使用内存作为缓存,所以也可以不配置磁盘的。总之,插件中需要用到的配置,在配置文件中必须找得到才可以,修改好之后我们保存配置,然后重启 APISIX 服务。

我们这里打算通过自定义一个 header 头来判断请求是否走缓存,由于变量在 APISIX 或 nginx 的内置变量中不存在,所以我们编写一个自定义变量的插件来解决,插件内容如下:

--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local ngx = ngx
local core = require("apisix.core")
local plugin = require("apisix.plugin") local schema = {
type = "object",
properties = {
name = {type = "string"},
label = {type = "integer"}
},
required = {"name"},
} local plugin_name = "custom-vars" local _M = {
version = 0.1,
priority = 99,
name = plugin_name,
schema = schema,
} defined_var_names = {"custom_username", "cache_bypass"} core.ctx.register_var("custom_username", function(ctx)
return get_custom_username()
end) core.ctx.register_var("cache_bypass", function(ctx)
local bypass = core.request.header(ctx, "cache-bypass")
if not bypass then
return ""
end
return bypass
end) function get_custom_username()
local req_headers = ngx.req.get_headers()
local username = req_headers.user
if username ~= "" then
return username
end
return nil
end function _M.check_schema(conf, schema_type)
if schema_type == core.schema.TYPE_METADATA then
return core.schema.check(metadata_schema, conf)
end
return core.schema.check(schema, conf)
end function _M.init()
-- call this function when plugin is loaded
local attr = plugin.plugin_attr(plugin_name)
if attr then
core.log.info(plugin_name, " get plugin attr val: ", attr.val)
end
end function _M.destroy()
-- call this function when plugin is unloaded
end -- sorted phase:
-- rewrite -> access -> before_proxy -> header_filter -> body_filter -> delayed_body_filter -> log
function _M.rewrite(conf, ctx)
-- core.log.warn("plugin rewrite phase, conf: ", core.json.encode(conf))
-- core.log.warn("plugin rewrite phase, ctx: ", core.json.encode(ctx, true))
-- core.log.warn("plugin rewrite phase, username: ", get_username())
end function _M.access(conf, ctx)
-- core.log.warn("plugin access phase, conf: ", core.json.encode(conf))
-- core.log.warn("plugin access phase, ctx: ", core.json.encode(ctx, true))
-- core.log.warn("plugin access phase, ngx headers: ", core.json.encode(ngx.req.get_headers()))
end function _M.before_proxy(conf, ctx)
-- After access and before the request goes upstream
end function _M.header_filter(conf, ctx)
-- Response header filter
end function _M.body_filter(conf, ctx)
-- Response body filter
end function _M.delayed_body_filter(conf, ctx)
-- delayed_body_filter is called after body_filter
-- it is used by the tracing plugins to end the span right after body_filter
end function _M.log(conf, ctx)
-- Log processing after response
end local function list_vars()
local args = ngx.req.get_uri_args()
if args["json"] then
return 200, defined_var_names
else
return 200, table.concat(defined_var_names, "\n") .. "\n"
end
end function _M.control_api()
return {
{
methods = {"GET"},
uris = {"/v1/plugin/custom-vars"},
handler = list_vars,
}
}
end return _M

这里插件名称我们叫 custom-vars,是专门注册自定义变量的插件,我们注册了 custom_usernamecache_bypass 这两个变量,并且添加了 Control API,我们将源码保存为 custom-vars.lua 并放到 APISIX 的 plugins 目录下,然后在配置文件中添加插件,如果之前没有添加过需要复制 config-default.yaml 中所有的插件,然后再补充上我们的插件。

具体如何加载插件可以参考之前写过的插件开发的文章。

由于我们在插件中注册了全局变量,只要插件被加载就可以,我们无需使用它也可以使用其中的自定义变量,但是假如我们要访问插件的 Control API 那么则必须在某个路由上启用插件。

使用专门的自定义变量插件的好处是我们不需要修改 proxy-cache 的源码在里面注册变量,这样假如 APISIX 升级了并且 proxy-cache 的源码有所变化我们也不需要再进行更新,只需要加入我们的 custom-vars 插件即可,对 APISIX 原有插件不会有任何影响,也是为了解耦。

加入插件后不要忘记重启 APISIX,然后我们来添加一个路由:

curl -X PUT http://127.0.0.1:9180/apisix/admin/routes/100 \
-H 'X-API-KEY: <api-key>' -d '
{
"uri": "/hello",
"name": "示例路由",
"plugins": {
"custom-vars": {
"name": "vars"
},
"proxy-cache": {
"cache_bypass": [
"$cache_bypass"
],
"cache_control": false,
"cache_http_status": [
200
],
"cache_key": [
"$uri",
"-cache-id"
],
"cache_method": [
"GET",
"PURGE"
],
"cache_strategy": "memory",
"cache_ttl": 30,
"cache_zone": "memory_cache_one",
"hide_cache_headers": false
}
},
"upstream": {
"nodes": [
{
"host": "10.0.1.12",
"port": 1980,
"weight": 1
}
],
"type": "roundrobin",
"hash_on": "vars",
"scheme": "http",
"pass_host": "pass"
},
"status": 1
}'

现在我们就添加了路由,然后我们访问路由添加 -i 参数就可以看到 APISIX 响应的字段,比如:

curl localhost:9080/hello -i

第一次会看到 APISIX-Cache-Status: MISS 因为数据未缓存,然后再次请求就可以看到 APISIX-Cache-Status: HIT 表示缓存已经命中,同时会返回 Age 响应头,表示当前缓存的存活时间,当时间超过 TTL 时,缓存就会被删除。

然后我们也可以选择不使用缓存,比如:

curl localhost:9080/hello -i -H 'Cache-Bypass: 1'

这时候我们会看到 APISIX-Cache-Status: BYPASS 表示没有使用缓存,而是直接请求上游服务。

假如我们要缓存 POST 之类的请求,那么这个时候 $request_body 肯定也要作为 cache_key 的一部分,但是这个时候上下文中又没有这个变量,那么怎么办呢?可以换一种方式,由于 $request_body 本身可能比较大,我们可以使用它做一个 Hash,只要请求体内容不变,那么 Hash 结果也是确定的,而且缓存的 key 也比较小,由于同时有 $uri 进行区分,选用 md5 这样的函数完全够了,碰撞的概率也是极小的,我们可以在上面插件中注册一个标识请求体的变量,比如:

core.ctx.register_var("request_body_uuid", function(ctx)
local body = core.request.get_body()
if not body then
return ""
end
return ngx.md5(body)
end)

这样我们就可以使用 $request_body_uuid 这样的变量的,那么我们在创建路由的时候 cache_key 配置如下:

{
"cache_key": [
"$uri",
"$request_body_uuid"
]
}

这样就可以缓存 POST 请求了,如果要缓存带参数的 GET 请求可以将 $uri 变量替换为 $request_uri 变量,后者是包含参数并且未规范化的。

最后我们还可以删除路由的缓存,使用 HTTP 的 PURGE 方法发起请求:

curl localhost:9088/hello -X PURGE -i

如果成功删除缓存会返回 200 OK,否则如果不存在缓存则会返回 404,但是前提路由配置中一定要允许 PURGE 方法,我们上面创建时就指定了,如果不指定则无法使用上面的命令删除缓存,并且启用了之后这个请求也是用于 APISIX 删除缓存,并不会请求上游的服务。

另外下面会给出 OpenResty 中两个 Lua 模块的仓库,其中有很多好用的函数可以参考,并且由于 APISIX 是基于 OpenResty 的,所以在 APISIX 插件开发中都是可用的。

Reference:

  1. APISIX 官方中文版文档参考
  2. OpenResty 提供的 Lua 模块
  3. OpenResty 的 Lua core API 参考

APISIX proxy-cache 插件用法的更多相关文章

  1. toastr.js插件用法

    toastr.js插件用法 toastr.js是一个基于jQuery的非阻塞通知的JavaScript库.toastr.js可以设定四种通知模式:成功.出错.警告.提示.提示窗口的位置.动画效果等都可 ...

  2. jquery uploadify文件上传插件用法精析

      jquery uploadify文件上传插件用法精析 CreationTime--2018年8月2日11点12分 Author:Marydon 一.参数说明 1.参数设置 $("#fil ...

  3. nginx Proxy Cache 配置

    总结一下 proxy cache 设置的常用指令及使用方法: proxy_cache proxy_cache zone | off 配置一块公用的内存区域的名称,该区域可以存放缓存的索引数据.注意:z ...

  4. 浏览器 Proxy SwitchyOmega 插件设置代理访问内网服务器

    使用Proxy SwitchyOmega 插件通过代理 直接访问到内网网站 一.使用场景 如下图所示,如果在电脑的网络设置中开启代理,每次更换代理就需要进入这里设置改变代理.且我们可能回需求到两个网页 ...

  5. ASP.NET MVC5+EF6+EasyUI 后台管理系统(86)-日程管理-fullcalendar插件用法

    前言 本文分享fullcalendar用法,最后面提供代码下载 说到日程管理,基于JQuery的插件FullCalendar当之无愧,完整的API稳定和调用方式,非常易于扩展!可以用于系统的个人历程管 ...

  6. jQuery的下拉选select2插件用法

    1转自:https://www.jb51.net/article/95561.htm 用了这么久的Select2插件,也该写篇文章总结总结.当初感觉Select2不是特别好用,但又找不到比它更好的下拉 ...

  7. Nginx使用教程(七):使用Nginx缓存之proxy cache

    定义缓存目录 <br\>使用您喜欢的文本编辑器打开/etc/nginx/nginx.conf,并在http {区域加入: proxy_cache_path  /var/www/cache ...

  8. jquery.Inputmask 插件用法(中文API文档)

      jquery.Inputmask 可以算是input文本输入限制的神器了,内部融合了多种输入限制, 如金额,电话号码,身份证号,网关等..,并且还可以自定义规则. inputmask 据说最早起源 ...

  9. 01:jQuery的下拉选select2插件用法

    1.1 select2插件基本使用 1.下载select2插件 1. 下载地址:https://github.com/select2/select2 2.官网地址:https://select2.or ...

  10. nginx proxy cache配置和清理

    1.nginx需要编译Purge模块 2.nginx.conf 配置cache: proxy_cache_path  /home/cache/xxx levels=1:2  keys_zone=cac ...

随机推荐

  1. Java8 函数式编程stream流

    1.初始环境准备 ​ 场景:现在有一个公司,公司部门有一级部门,二级部门甲和二级部门乙(其中二级部门甲和二级部门乙是一级部门的子部门), 一级部门下面有有001号员工小明,二级部门甲下面有002号员工 ...

  2. [kvm]cpu内存硬盘配置

    修改CPU配置 如果配置了最大CPU # 临时 virsh setvcpus test 2 # 永久 virsh setvcpus test 2 --config 热增加虚拟机的CPU数后,使用lsc ...

  3. AI绘画创意文字全流程揭秘,你的终极文字艺术实操宝典

    本教程收集于:AIGC从入门到精通教程汇总 AIGC技术不断更新迭代,国内出现了越来越多的新玩法,比如最近大家都在热议的AI绘画创意文字. 过去的一周,我把这些新玩法都研究了一遍,并总结了一套完整的制 ...

  4. 通过AOP拦截Spring Boot日志并将其存入数据库

    本文分享自华为云社区<Spring Boot入门(23):[实战]通过AOP拦截Spring Boot日志并将其存入数据库>,作者:bug菌. 前言 在软件开发中,常常需要记录系统运行时的 ...

  5. 初识Redis与桌面客户端

    Redis介绍 什么是Redis Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库. Redi ...

  6. Redis系列21:缓存与数据库的数据一致性讨论

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  7. 创建Anaconda虚拟Python环境的方法

      本文介绍在Anaconda环境下,创建.使用与删除Python虚拟环境的方法.   在Python的使用过程中,我们常常由于不同Python版本以及不同第三方库版本的支持情况与相互之间的冲突情况, ...

  8. 图解 LeetCode 算法汇总——回溯

    本文首发公众号:小码A梦 回溯算法是一种常见的算法,常见用于解决排列组合.排列问题.搜索问题等算法,在一个搜索空间中寻找所有的可能的解.通过向分支不断尝试获取所有的解,然后找到合适的解,找完一个分支后 ...

  9. 【krpano】淘宝buy+案例

    这是一个类似淘宝buy+的案例,是基于krpano全景开发工具二次开发的全景视频.WebVR.360°环物.全景视频热点添加于一身的综合性案例.现在将案例上传网站供krpano技术人员和爱好者大家共同 ...

  10. 支持JDK19虚拟线程的web框架,之三:观察运行中的虚拟线程

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<支持JDK19虚拟线程的web ...