基于OpenRestry(Nginx+Lua)的缓存前置

目录 

一、缓存项目目录

二、配置

1. 配置nginx.conf

2. 缓存服务配置goods.conf

3. 全局配置init.lua

4. 下载dkjson

5. 自定义MyRedis.lua

6. 自定义工具Lua

7. 缓存goods_cache.lua

三、缓存验证

1. 第一次请求

2. 第二次请求


一、缓存项目目录

二、配置

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

Lua | NGINX

openresty版本的nginx+lua实现接口签名安全认证_reblue520的专栏-CSDN博客

Nginx Lua API 学习 - 简书

openResty中的ngx.location.capture和ngx.location.capture_multi的使用_u010074988的博客-CSDN博客_ngx.location.capture

利用 Lua 的函数式编程简化 lua-resty-redis 的使用_weixin_34209406的博客-CSDN博客

如何用Lua脚本基于OpenResty、Redis实现数据的缓存?_SunRains-CSDN博客


版权声明:本文为m0_37543627原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。