目录
一、缓存项目目录
二、配置
1. 配置nginx.conf
主要配置:
lua_package_path:加载第三方或自定义Lua库
lua_package_cpath:加载C模块
include:http配置下包含其他server配置文件(不同server不同配置)
init_by_lua_file:nginx Master进程加载配置时执行,用于全局初始化配置
lua_shared_dict:定义lua共享字典及字典大小
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
#TCP四层缓存
stream {
upstream mysql {
server 192.168.1.55:3307 max_fails=2 fail_timeout=10s weight=1;
}
server {
listen 8201;
proxy_pass mysql;
}
}
http {
include mime.types;
default_type application/octet-stream;
#加载lua模块
lua_package_path "/usr/local/openresty/lualib/?.lua;/usr/local/openresty/MyLib/?.lua;"; #lua模块
lua_package_cpath "/usr/local/openresty/lualib/?.so;;"; #c模块
include lua.conf;
include goods.conf;
#共享全局变量,在所有worker间共享
lua_shared_dict shared_data 16m;
# Nginx的Master进程加载时执行
#init_by_lua_file lua/nginx/init.lua;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
error_log logs/LOG_COMMON.log debug;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
gzip on;
#开启缓存
proxy_buffering on;
proxy_cache_path /data/proxy_cache levels=1:2 keys_zone=cache:512m inactive=1m max_size=8g;
#某个key总的连接数限流
#limit_conn_zone $binary_remote_addr zone=addr:10m;
#某个key总的请求数限流
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
limit_conn_log_level error;
limit_conn_status 503;
upstream backend {
hash $consistent_key consistent;
server 192.168.1.55:9013 max_fails=2 fail_timeout=10s weight=1;
server 192.168.1.55:9013 max_fails=2 fail_timeout=10s weight=1;
check interval=3000 rise=1 fall=3 timeout=2000 type=http;
check_keepalive_requests 100;
check_http_send "HEAD /instance-test/hystrix/timeOut?mills=10 HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
upstream goodsUpstream {
server 192.168.1.55:9014 max_fails=2 fail_timeout=10s weight=1;
server 192.168.1.55:9014 max_fails=2 fail_timeout=10s weight=1;
check interval=3000 rise=1 fall=3 timeout=2000 type=http;
check_keepalive_requests 100;
check_http_send "HEAD /instance-test/hystrix/timeOut?mills=10 HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
include goods.conf;
init_by_lua_file lua/goods/init.lua;
lua_shared_dict local_cache 16m;
server {
listen 8200;
server_name localhost;
set $consistent_key '';
#charset koi8-r;
#access_log logs/host.access.log main;
location ~ ^/test {
error_log logs/monitor.log debug;
content_by_lua_block {
ngx.say(ngx.INFO, "http 获fsfsgrhgs:", ngx_var.request_uri);
}
set_by_lua_file $consistent_key lua/consistent_key.lua;
proxy_cache cache;
add_header Cache-Control max-age=no-cache;
proxy_cache_valid 30s;
proxy_ignore_headers Set-Cookie Cache-Control;
proxy_hide_header Cache-Control;
proxy_hide_header Set-Cookie;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
proxy_send_timeout 5s;
proxy_next_upstream error timeout;
proxy_next_upstream_timeout 10s;
proxy_next_upstream_tries 2;
#limit_conn addr 1;
limit_req zone=one burst=5 nodelay;
proxy_pass http://backend;
}
location /hello {
error_log logs/monitor.log debug;
default_type 'text/html';
#关掉lua缓存,修改lua文件不用重启nginx
lua_code_cache off;
#获取消息体,否则lua脚本返回nil
lua_need_request_body on;
set_by_lua_file $consistent_key lua/consistent_key.lua;
content_by_lua_file lua/test.lua;
#content_by_lua_file lua/consistent_key.lua;
}
location /status {
# 查询上游服务器状态
check_status;
access_log off;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
2. 缓存服务配置goods.conf
主要配置:
lua_code_cache:off关闭缓存,每次修改lua代码不需要reload nginx(线上on开启)
lua_need_request_body:on获取消息体,否则lua脚本返回nil
server {
listen 8203;
server_name goods;
default_type application/json;
location ~ /proxy2/(.*) {
internal;
proxy_pass http://goodsUpstream/$1$is_args$args;
}
location ~ ^/goodsCache/(.*) {
default_type application/json;
charset utf-8;
lua_code_cache off;
#获取消息体,否则lua脚本返回nil
lua_need_request_body on;
content_by_lua_file lua/goods/goods_cache.lua;
}
}
3. 全局配置init.lua
-- 初始化耗时的模块
local redis = require 'resty.redis'
local cjson = require 'cjson'
-- 共享全局内存
local local_cache = ngx.shared.local_cache
-- 开启共享字典
on_dict_cache = true
-- 开启redis分布式缓存
on_redis_cache = true
4. 下载dkjson
在./lualib下执行以下命令。引入require("dkjson")
wget http://dkolf.de/src/dkjson-lua.fsl/raw/dkjson.lua?name=16cbc26080996d9da827df42cb0844a25518eeb3 -O dkjson.lua
5. 自定义MyRedis.lua
-- 创建redis、关闭redis连接
local redis = require("resty.redis")
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_INFO = ngx.INFO
local _M = {
_VERSION = "1.0.0",
_AUTHOR = "tcm"
}
-- 派生类的方法 new
function _M:new(myConfig)
myConfig = myConfig or {}
-- 元表参数
local localConfig = {
host = myConfig.host or "127.0.0.1",
port = myConfig.port or 6379,
password = myConfig.password or "",
dbIndex = myConfig.dbIndex or 0,
timeout = myConfig.timeout or 10000,
pool_size = myConfig.pool_size or 100,
pool_max_idle_time = myConfig.pool_max_idle_time or 60000
}
-- 设置元表
return setmetatable(localConfig, { __index = _M })
end
-- 关闭redis连接
function _M:close_redis(red)
if not red then
return
end
--释放连接(连接池实现)
local ok, err = red:set_keepalive(self.pool_max_idle_time, self.pool_size)
if not ok then
ngx_log(ngx_ERR, "set redis keepalive error : ", err)
end
end
function _M:create_redis(red)
-- 创建redis
local red = redis:new()
-- 设置超时(毫秒)
red:set_timeout(self.timeout)
local ok, err = red:connect(self.host, self.port)
if not ok then
ngx_log(ngx_ERR, "connect to redis error : ", err)
return close_redis(red)
end
-- 密码
if self.password ~= "" then
local res, err = red:auth(self.password)
if not res then
ngx_log("failed to authenticate: ", err)
return close_redis(red)
end
end
-- 选择DB索引
red:select(self.dbIndex)
return red
end
return _M
6. 自定义工具Lua
a. RedisUtil.lua
-- 操作redis工具类
local redis = require("redis/MyRedis")
local dkjson = require("dkjson")
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_INFO = ngx.INFO
local redisConfig = {
host = "192.168.1.55",
port = 6379,
password = "",
dbIndex = 1,
timeout = 1000,
pool_size = 100,
pool_max_idle_time = 10000
}
-- 创建redis示例
local myRedis = redis:new(redisConfig)
local _M = {}
-- 获取redis缓存
function _M.read_redis(key)
-- 获取redis
local red = myRedis:create_redis()
local resp, err = red:get(key)
if not resp then
local errorMsg = "get redis content error, key=" .. key
ngx_log(ngx_ERR, errorMsg, err)
return myRedis:close_redis(red)
end
-- 得到的数据为空处理
if resp == ngx.null then
resp = nil
end
myRedis:close_redis(red)
return resp
end
-- 获取redis缓存
function _M.write_redis(key, value, expire)
-- 获取redis
local red = myRedis:create_redis()
local resp, err = red:set(key, value)
-- 设置过期事件
red:expire(key, expire)
if not resp then
local errorMsg = "get redis content error, key=" .. key .. ", value=" .. value
ngx_log(ngx_ERR, errorMsg, err)
return myRedis:close_redis(red)
end
-- 得到的数据为空处理
if resp == ngx.null then
resp = nil
end
myRedis:close_redis(red)
return resp
end
return _M
b. CommonUtil.lua
-- 通用工具类
local dkjson = require("dkjson")
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_INFO = ngx.INFO
local _M = {}
-- 两table合并返回table1
function _M.unionTable(table1,table2)
for k,v in pairs(table2) do
if type(v) == "table" then
table1[k] = table.concat(v, ",")
else
table1[k] = v
end
end
return table1
end
-- post参数转table
function _M.postArgsToTable(postArgs)
local result = {}
-- {xxx}:true
for k,v in pairs(postArgs) do
-- k是{xxx}
local obj, pos, err = dkjson.decode(k)
if not obj then
return result
end
-- 参数转table
for k,v in pairs(obj) do
if type(v) == "table" then
result[k] = table.concat(v, ",")
else
result[k] = v
end
end
end
return result
end
-- MB5
function _M.local_md5(object)
local keys, tmp = {}, {}
--提出所有的键名并按字符顺序排序
for k, _ in pairs(object) do
keys[#keys+1] = k
end
table.sort(keys)
--根据排序好的键名依次读取值并拼接字符串成key=value&key=value
for _,k in pairs(keys) do
if type(object[k]) == "string" or type(object[k]) == "number" then
tmp[#tmp+1] = k .. "=" .. tostring(object[k])
end
end
ngx_log(ngx_INFO, "request all params sort: ", dkjson.encode(tmp, {indent = true}))
--将salt添加到最后,计算正确的签名sign值并与传入的sign签名对比,
local signchar = table.concat(tmp, "&")
local rightsign = ngx.md5(signchar)
return rightsign
end
return _M
7. 缓存goods_cache.lua
-- 商品缓存示例
local RedisUtil = require("util/RedisUtil")
local CommonUtil = require("util/CommonUtil")
local cjson = require("cjson")
local dkjson = require("dkjson")
local cjson_encode = cjson.encode
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_INFO = ngx.INFO
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_re_match = ngx.re.match
local ngx_var = ngx.var
-- 全局配置参数
local local_cache = ngx.shared.local_cache
local on_dict_cache = on_dict_cache
local on_redis_cache = on_redis_cache
-- 配置缓存时间
local dict_cache = 1 * 15
local redis_cahce = 1 * 30
local REQUEST_PREFIX = "/goodsCache"
-- 请求远程服务
local function read_http(uri)
-- 组装代理URL
local url = "/proxy2" .. string.gsub(uri,REQUEST_PREFIX,"",1)
-- 请求方式
local requestMethod = ngx.HTTP_GET
if ngx.var.request_method == 'POST' then
requestMethod = ngx.HTTP_POST
end
-- 跳转至nginx内部的location
local resp = ngx.location.capture(url, {
-- 请求方式,指定类似ngx.HTTP_GET(默认)
method = requestMethod,
-- 复制当前请求的ngx.var变量到子请求
copy_all_vars = true,
-- true时,父请求中的body转发到子请求;默认false,仅转发PUT和POST请求
-- always_forward_body = true
})
if not resp then
ngx_log(ngx_ERR, "request error :", err)
return
end
if resp.status ~= 200 then
ngx_log(ngx_ERR, "request error, status :", resp.status)
return
end
return resp.body
end
-- 获取缓存key,格式:URL + "?SIGN=" + 所有请求参数签名
local function getCacheKey(uri)
-- 去除前缀
uri = string.gsub(uri,REQUEST_PREFIX,"",1)
-- 获取请求URL,如:/xxxx?
local url = string.sub(uri,0,string.find(uri,"?"))
-- 是否含?
local match = string.match(url,"?", 1)
if not match then
url = url .. "?"
end
-- 获取所有参数
local params = {}
-- 获取请求参数
local get_args = ngx.req.get_uri_args()
-- 获取请求体
local post_args = ngx.req.get_post_args()
CommonUtil.unionTable(params,get_args)
CommonUtil.unionTable(params,CommonUtil.postArgsToTable(post_args))
-- 对请求参数MD5,格式:URL + "?SIGN=" + 所有请求参数签名
local paramKey = "NGINX-CHACHE:" .. url .. "SIGN=" .. CommonUtil.local_md5(params)
return paramKey
end
-- 多级缓存:本地共享字典、redis、回源
local function getCacheContent(uri, cacheKey)
-- 返回内容
local content = nil
-- 共享字典开启
if on_dict_cache then
if not content then
content = local_cache:get(cacheKey)
ngx_log(ngx_INFO, "back to share_dict, cacheKey: ", cacheKey)
end
end
-- redis开启
if on_redis_cache then
if not content then
content = RedisUtil.read_redis(cacheKey)
ngx_log(ngx_INFO, "back to redis, cacheKey: ", cacheKey)
end
end
-- 回源
if not content then
-- 回源请求
content = read_http(uri)
ngx_log(ngx_INFO, "back to http, cacheKey: ", cacheKey)
-- 回源是否成功
if not content then
ngx_log(ngx_ERR, "http not found content, cacheKey: ", cacheKey)
-- 回源没有,返回404
return ngx_exit(404)
else
-- 开启了共享字典
if on_dict_cache then
-- 设置共享字段缓存
local_cache:set(cacheKey, content, dict_cache)
end
-- 开启了redis缓存
if on_redis_cache then
-- 设置redis缓存
RedisUtil.write_redis(cacheKey, content, redis_cahce)
end
end
end
return content
end
-- 获取请求URI
local uri = ngx_var.request_uri
-- 获取缓存key,格式:URL + "?SIGN=" + 所有请求参数签名
local cacheKey = getCacheKey(uri)
ngx_log(ngx_INFO, "cacheKey: ", cacheKey)
-- 多级缓存:本地共享字典、redis、回源
local content = getCacheContent(uri, cacheKey)
--输出内容
ngx.say(content)
三、缓存验证
全局变量控制开启共享字典缓存、redis分布式缓存。获取顺序:共享字典,再到redis缓存。
1. 第一次请求
回源到应用端,再写入共享字典、redis缓存。日志如下:
2022/01/13 07:09:04 [info] 2165#0: *1496 [lua] CommonUtil.lua:59: local_md5(): request all params sort: ["id=85","number=2"], client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:04 [info] 2165#0: *1496 [lua] goods_cache.lua:137: cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:04 [info] 2165#0: *1496 [lua] goods_cache.lua:95: getCacheContent(): back to share_dict, cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:04 [info] 2165#0: *1496 [lua] goods_cache.lua:102: getCacheContent(): back to redis, cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:04 [info] 2165#0: *1496 [lua] goods_cache.lua:109: getCacheContent(): back to http, cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca while sending to client, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2. 第二次请求
共享字典获取,说明成功缓存。
2022/01/13 07:09:12 [info] 2165#0: *1496 [lua] CommonUtil.lua:59: local_md5(): request all params sort: ["id=85","number=2"], client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:12 [info] 2165#0: *1496 [lua] goods_cache.lua:137: cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
2022/01/13 07:09:12 [info] 2165#0: *1496 [lua] goods_cache.lua:95: getCacheContent(): back to share_dict, cacheKey: NGINX-CHACHE:/instance-test/nginxCache/queryOrder?SIGN=17b0e38a00f51114c1992f87d4f907ca, client: 172.17.0.1, server: goods, request: "GET /goodsCache/instance-test/nginxCache/queryOrder?id=85&number=2 HTTP/1.1", host: "localhost:8203"
四、参考资料
https://www.iteye.com/blog/jinnianshilongnian-2190344
openresty版本的nginx+lua实现接口签名安全认证_reblue520的专栏-CSDN博客
利用 Lua 的函数式编程简化 lua-resty-redis 的使用_weixin_34209406的博客-CSDN博客