目录
一、整体架构描述
1.1 方案v1.0
1.2 方案v1.0优化
1.3 方案对比
二、方案2.0部署
2.1 OpenResty 安装
2.2 Lua脚本编写
2.3 配置 OpenResty
三、测试
一、整体架构描述
1.1 方案v1.0
在优化旧的设计框架前,先看一下常见的查询请求处理
步骤如下:
- ① 请求到 Nginx 或 restApi 服务后, 向 Redis 请求数据
- ②、③ Redis 查看缓存中是否有数据,若有数据则直接进入流程⑥,没有则进入流程④
- ④ 、⑤ 查询数据库数据,并更新数据到 Redis ,以免下次还需要访问数据库
- ⑥ 返回数据给用户
1.2 方案v1.0优化
在方案v1.0,存在了大量简单的请求,如在IM软件中查询用户/群组信息、在商城项目中查询商品信息等,这些简单的查询却频繁的查询无疑会对服务造成一定压力。那么是否有办法进行优化呢。
对方案v1.0进行优化后,方案v2.0结合了 OpenResty、Lua、Redis 实现了二级缓存。利用 Nginx 高并发的特性,使得上述这些请求无需再通过 restAPI 服务,而是通过Lua脚本直接查询和操作 redis 和 mysql,降低服务压力。
步骤如下:
- ①、② 请求到 Nginx 后,Nginx 先查询 Nginx-Cache,若缓存中没有数据,则调用对应的 Lua 脚本
- ③、④ Lua 脚本查询 Redis,若Redis 缓存中有数据则直接返回并更新Nginx缓存, 没有数据则调用 Lua脚本,查询数据库
- ⑤ 查询数据库数据并更新Redis缓存
注意:
方案v2.0 采用的是逐级缓存的方式:
- 第一次访问:nginx 和 redis 中均没有缓存,数据从数据库中查出,并存入 redis 缓存
- 第二次访问:nginx 中没有缓存,数据从 redis 中查出,并存入 nginx 缓存
- 第三次访问:数据从 nginx 中查出
这样做有以下原因:
- nginx 缓存过期时间小于 redis 缓存过期时间,有利于降低 redis 雪崩的危险
- 若同时对 nginx 和 redis 设置缓存,则数据访问到的都是 nginx。当数据库变化时,无法在不访问数据库的情况下实现快速响应。解决方案为使用 Canal 实现数据库和 redis 的数据同步,将 nginx 的有效时间设置得更短,做到防止大量并发请求到数据库的同时,又能拿到最新数据
1.3 方案对比
根据不同的项目需求和实际方案,选择使用哪一种方案。
方案v1.0 | 方案v2.0 | |
灵活性 | 访问 redis 和数据库由 restApi 控制,可处理复杂请求。但如果有修改必须重启服务 | 利用 Nginx 高并发特性处理大量简单请求,且修改脚本无需重启后端服务,只要重新加载 Nginx 即可。 但只能处理简单的请求,且需要有特殊的识别方式,如加特定的 API |
并发性 | 高 | 高,相对来说肯定比方案v1.0高,但要根据实际情况使用,而不是为了炫技而部署 |
部署难度 | 简单 | 中等。需要学习 OpenResty框架和Lua语言 |
适用场景 | 通用场景 | 更适用于存在大量简单的查询请求的项目,如仓库管理系统、商城系统等。 同时这个方案还可以处理权限控制,如直接在 Nginx 拒绝不携带 token 的请求,或进行 token 验证等。 |
二、方案2.0部署
2.1 OpenResty 安装
安装流程:https://blog.csdn.net/qq_34416331/article/details/106421783
2.2 Lua脚本编写
Lua 的基本用法:https://blog.csdn.net/qq_34416331/article/details/106419100
Lua 脚本编写:
# 创建存放 lua 脚本的文件夹,名字自定义
mkdir /usr/local/lua_conf
# 进入文件夹
cd /usr/local/lua_conf
# 创建 lua 脚本
vim read_conf.lua
ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
-- 加载nginx缓存模块
local cache_ngx = ngx.shared.dis_cache;
-- 根据ID获取本地缓存数据
local contentCache = cache_ngx:get('content_cache_'..id);
-- 获取IP信息,可删除
-- [[
local headers=ngx.header;
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
ngx.say(ip)
ngx.say(contentCache)
]]--
-- 若nginx中没有相应的缓存信息
if contentCache == "" or contentCache == nil then
-- 获取redis模块
local redis = require("resty.redis");
local red = redis:new()
red:set_timeout(2000)
red:connect("192.168.47.142", 6379)
local rescontent=red:get("content_"..id);
-- 若redis模块也没有这个信息
if ngx.null == rescontent then
-- 从数据库中获取数据
local cjson = require("cjson");
local mysql = require("resty.mysql");
local db = mysql:new();
db:set_timeout(2000)
local props = {
host = "192.168.47.142",
port = 3306,
database = "changgou_content",
user = "root",
password = "123456"
}
local res = db:connect(props);
local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
res = db:query(select_sql);
local responsejson = cjson.encode(res);
-- 存储到redis中
red:set("content_"..id,responsejson);
-- 返回数据
ngx.say(responsejson);
-- 关闭数据库连接
db:close()
else
-- 若redis中有缓存,则设置的到nginx缓存中,并返回
-- 2*60 表示设置 nginx 缓存时间为2分钟,应根据实际情况修改
cache_ngx:set('content_cache_'..id, rescontent, 2*60);
ngx.say(rescontent)
end
-- 关闭redis连接
red:close()
else
-- 若nginx中有对应的信息,则返回
ngx.say(contentCache)
end
2.3 配置 OpenResty
要让 OpenResty 来使用 Lua 脚本,只需要配置 Nginx 的配置文件即可。
### 修改 Nginx 配置文件 ###
# 进入 OpenResty 自带的 Nginx 目录
cd /usr/local/openresty/nginx/conf
# 编辑配置文件
vim nginx.conf
lua_shared_dict 的作用是声明一个共享内存区域 name,以充当基于 Lua 字典的共享存储。简单来说就是当nginx运行时的 lua 脚本缓存空间大小
在http中配置要访问的接口:
重启 nginx
# 重新加载 nginx
/usr/local/openresty/nginx/sbin/nginx -s reload
三、测试
修改 read_content.lua
ngx.header.content_type="application/json;charset=utf8"
local uri_args = ngx.req.get_uri_args();
local id = uri_args["id"];
-- 加载nginx缓存模块
local cache_ngx = ngx.shared.dis_cache;
-- 根据ID获取本地缓存数据
local contentCache = cache_ngx:get('content_cache_'..id);
--[[
local headers=ngx.header;
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
ngx.say(ip)
ngx.say(contentCache)
]]--
if contentCache==nil then
ngx.say("nginx缓存为空")
else
ngx.say("从nginx缓存中查到了数据")
end
-- 若nginx中没有相应的缓存信息
if contentCache == "" or contentCache == nil then
-- 获取redis模块
local redis = require("resty.redis");
local red = redis:new()
red:set_timeout(2000)
red:connect("192.168.47.142", 6379)
local rescontent=red:get("content_"..id);
if ngx.null == rescontent then
ngx.say("redis为空")
else
ngx.say("nginx为空,从redis中查到了数据")
end
-- 若redis模块也没有这个信息
if ngx.null == rescontent then
-- 从数据库中获取数据
local cjson = require("cjson");
local mysql = require("resty.mysql");
local db = mysql:new();
db:set_timeout(2000)
local props = {
host = "192.168.47.142",
port = 3306,
database = "changgou_content",
user = "root",
password = "123456"
}
local res = db:connect(props);
local select_sql = "select url,pic from tb_content where status ='1' and category_id="..id.." order by sort_order";
res = db:query(select_sql);
local responsejson = cjson.encode(res);
-- 存储到redis中
red:set("content_"..id,responsejson);
-- 返回数据
ngx.say(responsejson);
-- 关闭数据库连接
db:close()
else
-- 若redis中有缓存,则设置的到nginx缓存中,并返回
cache_ngx:set('content_cache_'..id, rescontent, 30);
end
-- 关闭redis连接
red:close()
else
-- 若nginx中有对应的信息,则返回
ngx.say(contentCache)
end
清理redis
访问:
第一次访问
第二次访问:
第三次访问: