当前位置: 代码迷 >> 综合 >> 【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略
  详细解决方案

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

热度:30   发布时间:2023-09-21 00:32:03.0

目录

一、整体架构描述

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

在优化旧的设计框架前,先看一下常见的查询请求处理

  【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

步骤如下:

  • ① 请求到 Nginx 或 restApi 服务后, 向 Redis 请求数据
  • ②、③ Redis 查看缓存中是否有数据,若有数据则直接进入流程⑥,没有则进入流程④
  • ④ 、⑤ 查询数据库数据,并更新数据到 Redis ,以免下次还需要访问数据库
  • ⑥ 返回数据给用户

 

1.2 方案v1.0优化

在方案v1.0,存在了大量简单的请求,如在IM软件中查询用户/群组信息、在商城项目中查询商品信息等,这些简单的查询却频繁的查询无疑会对服务造成一定压力。那么是否有办法进行优化呢。

对方案v1.0进行优化后,方案v2.0结合了 OpenResty、Lua、Redis 实现了二级缓存。利用 Nginx 高并发的特性,使得上述这些请求无需再通过 restAPI 服务,而是通过Lua脚本直接查询和操作 redis 和 mysql,降低服务压力。

  【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

步骤如下:

  • ①、② 请求到 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

  【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

 lua_shared_dict 的作用是声明一个共享内存区域 name,以充当基于 Lua 字典的共享存储。简单来说就是当nginx运行时的 lua 脚本缓存空间大小

  【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

在http中配置要访问的接口:

  【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

重启 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 thenngx.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 thenngx.say("redis为空")elsengx.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

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

访问:

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

第一次访问

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

第二次访问:

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略

第三次访问:

【并发优化】一、OpenResty 结合 Lua、Redis 实现请求高并发策略