Openresty+Lua+Redis实现高性能缓存

一、背景

当我们的程序需要提供较高的并发访问时,往往需要在程序中引入缓存技术,通常都是使用Redis作为缓存,但是要再更进一步提升性能的话,就需要尽可能的减少请求的链路长度,比如可以将访问Redis缓存从Tomcat服务器提前Nginx

原本访问缓存逻辑

User---> Nginx -> Tomcat -> Redis 

User---> Nginx -> Redis 

二、介绍

1 OpenResty 介绍

OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

官网: OpenResty® - 开源官方站

2 Lua 介绍

Lua 是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

推荐教程: Lua 教程 | 菜鸟教程

三、软件安装

1 OpenResty 安装

下载最新版本

上传到虚拟机的/usr/local 目录下,之后解压

这里前提是需要安装c语言编译器和Nginx依赖包(如已经安装过了跳过下面3个命令),否则下面的安装会报错的

yum install -y gcc

yum install -y pcre pcre-devel

yum install -y zlib zlib-devel

yum install -y openssl openssl-devel

进入到解压后的文件夹  openresty-1.25.3.1 中执行

./configure --prefix=/usr/local/openresty

正常的话,出现下面的画面说明执行成功了

然后执行make && make install 

make 

make install 

执行完成后,可以看到在/usr/local 目录下多了一个openresty 目录

2 目录介绍

  1. bin目录:执行文件目录。
  2. lualib目录:这个目录存放的是OpenResty中使用的Lua库,主要分为ngx和resty两个子目录。
  3. nginx目录:这个目录存放的是OpenResty的nginx配置和可执行文件。
  4. luajit目录:luajit目录是LuaJIT的安装根目录,用于提供LuaJIT的运行环境和相关资源。

3 启动Nginx

 nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf

4 访问Nginx

在浏览器中输入虚拟机的地址http://192.168.31.115/

四、Openresty中初试Lua

1 编辑nginx.conf

在server{}中插入下面代码

location /lua {default_type text/html;content_by_lua 'ngx.say("<p>hello,world</p>")';}

2 重启一下Nginx

nginx/sbin/nginx -s stop

nginx/sbin/nginx -c /usr/local/openresty/nginx/conf/nginx.conf

3 访问浏览器

在浏览器中输入虚拟机的IP地址+lua

http://192.168.31.115/lua

正常的话应该可以看到下面的画面

 4 通过lua文件的方式

进入到Nginx目录,创建lua文件夹,并新建一个hello.lua文件

cd nginx

mkdir lua

vim lua/hello.lua

ngx.say("<p>hello,hello,hello</p>")

修改nginx.conf 文件

location /lua {default_type text/html;content_by_lua_file lua/hello.lua;}

重启Nginx,再次刷新网站

5 Openresty连接Redis

参考官网文档:GitHub - openresty/lua-resty-redis: Lua redis client driver for the ngx_lua based on the cosocket API

在/usr/local/openresty/nginx/lua目录下,编辑一个redis.lua 文件,内容如下:

local redis = require "resty.redis"
local red = redis:new()red:set_timeouts(1000, 1000, 1000) -- 1 seclocal ok, err = red:connect("192.168.31.114", 6579)
if not ok thenngx.say("failed to connect: ", err)return
endlocal res, err = red:auth("123456")
if not res thenngx.say("failed to authenticate: ", err)return
endok, err = red:set("dog", "an animal")
if not ok thenngx.say("failed to set dog: ", err)return
endngx.say("set result: ", ok)local res, err = red:get("dog")
if not res thenngx.say("failed to get dog: ", err)return
endif res == ngx.null thenngx.say("dog not found.")return
endngx.say("dog: ", res)

再修改nginx.conf 

location /lua {default_type text/html;content_by_lua_file lua/redis.lua;}

访问浏览器

到这里,我们已经成功使用Nginx通过lua脚本访问到了Redis,但是这种写法仍然有一个巨大的问题,就是每次请求都会重新连接Redis,性能非常低下,我们测试一下这样写的接口性能

6 解决Redis重复连接问题

再修改nginx.conf 

require("my/cache").go()

五、真实案例

1 案例背景

应用程序中有一个接口/goods-center/getGoodsDetails 希望通过nginx先查询Redis缓存,缓存中没有就去应用服务中查询,然后把查询到结果缓存到Redis中

2 Nginx获取请求的参数

编辑conf/nginx.conf 中的server,添加下面配置

        location /goods-center/getGoodsDetails {default_type application/json;content_by_lua_file lua/item.lua;}

然后在/usr/local/openresty/nginx/lua 目录中创建item.lua,添加下面lua代码

local args = ngx.req.get_uri_args()ngx.say(args["goodsId"])

重新加载nginx配置

sbin/nginx -s reload

浏览器演示

3 转发请求到后端服务

定义一些工具类,方便后续写代码时调用,在/usr/local/openresty/lualib/mylua/common.lua

在common.lua中添加http get工具方法

local function http_get(path,params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params})if not resp thenngx.log(ngx.ERR)ngx.exit(404)endreturn resp.body
endlocal _M = {http_get = http_get
}return _M

编辑/usr/local/openresty/nginx/lua/item.lua 文件

-- 导入common包
local common = require('mylua.common')
local http_get = common.http_getlocal args = ngx.req.get_uri_args()-- 查询商品信息
local itemJson = http_get("/goods-center/getGoodsDetails",args)ngx.say(itemJson)

修改/usr/local/openresty/nginx/conf/nginx.conf 添加下面代码

    	location /nginx-cache/goods-center/getGoodsDetails {default_type application/json;content_by_lua_file lua/item.lua;}location ~ ^/goods-center/ {proxy_pass http://192.168.31.112:9527;}

解释一下上面代码,将/nginx-cache/goods-center/getGoodsDetails 请求通过lua脚本处理,转发到/goods-center/getGoodsDetails ,再通过~ ^/goods-center/  反向代理到应用服务器上http://192.168.31.112:9527;

演示:

4 优先查询Redis (官方并发会报错)

官网文档:GitHub - openresty/lua-resty-redis: Lua redis client driver for the ngx_lua based on the cosocket API

这应该是我到目前为止最想吐槽的开源软件了,按照官网的文档操作简直就是个玩具,无法商用

只要超过1个线程去压测就会报bad request 错误,这个在官网的局限性一栏中有提到,但是不明白为什么不解决,这个问题不解决,就无法商用,而且每次请求都会创建连接,性能巨差,关键这个问题在网上都很少有人提出过这个问题,包括一些教学视频,都是点到为止,根本没有测试过并发场景能不能用,我只要一并发测试就GG,怎么改都不行,翻了很多文档,都没有解决方案,如果有人有方案可以在评论区分享一下,互相学习

GitHub - openresty/lua-resty-redis: Lua redis client driver for the ngx_lua based on the cosocket API

先看代码

/usr/local/openresty/lualib/mylua/common.lua

local redis = require('resty.redis')
local red = redis:new()
red:set_timeouts(1000,1000,1000)local function get_from_redis(key)ngx.log(ngx.INFO,"redis init start .............")local ok,err = red:connect("192.168.31.114",6579)-- 连接失败if not ok thenngx.log(ngx.ERR,"connect redis error",err)return nilend-- 认证失败local res, err = red:auth("123456")if not res thenngx.say(ngx.ERR,"failed to authenticate: ", err)return nilendlocal resp,err = red:get(key)if not resp thenngx.log(ngx.ERR,"get from redis error ",err," key: ",key)return nilend-- 数据为空if resp == ngx.null thenngx.log(ngx.ERR,"this key is nil, key: ",key)return nilend-- 设置连接超时时间和连接池大小red:set_keepalive(600000, 100)return respendlocal function http_get(path,params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params})if not resp thenngx.log(ngx.ERR)ngx.exit(404)endreturn resp.body
endlocal _M = {http_get = http_get,get_from_redis = get_from_redis
}return _M

/usr/local/openresty/nginx/lua/item.lua

-- 导入common包
local _M = {}common = require('mylua.common')
http_get = common.http_get
get_from_redis = common.get_from_redisfunction _M.get_data()local args = ngx.req.get_uri_args()-- 先查询Redislocal itemJson = get_from_redis("goods-center:goodsInfo:" .. args["goodsId"])ngx.log(ngx.INFO,"get from redis itemJson,  ",itemJson)if itemJson == nil then-- redis 没有,则查询服务器信息itemJson = http_get("/goods-center/getGoodsDetails",args)endngx.say(itemJson)
endreturn _M

修改/usr/local/openresty/nginx/conf/nginx.conf 添加下面代码

    server {location /nginx-cache/goods-center/getGoodsDetails {default_type application/json;content_by_lua_block {require("lua/item").get_data()}}location ~ ^/goods-center/ {proxy_pass http://192.168.31.112:9527;}

单线程测试(没有报错,且有500多的吞吐量)

2个线程测试,有40%+ 的错误率,报错详情截图给了,线程越多报错越多

2024/02/04 21:56:06 [error] 21662#0: *390686 lua entry thread aborted: runtime error: /usr/local/openresty/lualib/resty/redis.lua:166: bad request
stack traceback:
coroutine 0:[C]: in function 'connect'/usr/local/openresty/lualib/resty/redis.lua:166: in function 'connect'/usr/local/openresty/lualib/mylua/common.lua:9: in function 'get_from_redis'./lua/item.lua:12: in function 'get_data'content_by_lua(nginx.conf:49):2: in main chunk, client: 192.168.31.32, server: localhost, request: "GET /nginx-cache/goods-center/getGoodsDetails?goodsId=10000 HTTP/1.1", host: "192.168.31.115"

5 使用ngx.shared.redis_pool连接池(并发不会报错)

/usr/local/openresty/lualib/mylua/common.lua

-- 引入 lua-resty-redis 模块
local redis = require "resty.redis"-- 获取 OpenResty 全局字典对象(连接池)
local redis_pool = ngx.shared.redis_pool-- Redis 连接池的最大连接数
local max_connections = 100-- Redis 服务器地址和端口
local redis_host = "192.168.31.114"
local redis_port = 6579-- 获取 Redis 连接
local function get_redis_connection()local red = redis_pool:get(redis_host)if not red thenngx.log(ngx.ERR, "create new : ", err)-- 创建一个新的 Redis 连接red = redis:new()-- 设置连接超时时间red:set_timeout(1000,1000,1000)-- 连接 Redis 服务器local ok, err = red:connect(redis_host, redis_port)if not ok thenngx.log(ngx.ERR, "Failed to connect to Redis: ", err)return nil, errendlocal res, err = red:auth("123456")if not res thenngx.say(ngx.ERR,"failed to authenticate: ", err)return nilend-- 将连接放入连接池redis_pool:set(redis_host, red, 600)endreturn red
endlocal function get_from_redis(key)local redis_conn, err = get_redis_connection()if not redis_conn thenngx.log(ngx.ERR, "Failed to get Redis connection: ", err)ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)end-- 获取失败local resp,err = redis_conn:get(key)if not resp thenngx.log(ngx.ERR,"get from redis error ",err," key: ",key)return nilend-- 数据为空if resp == ngx.null thenngx.log(ngx.ERR,"this key is nil, key: ",key)return nilend-- 设置连接超时时间redis_conn:set_keepalive(600000, max_connections)return resp
endlocal function http_get(path,params)local resp = ngx.location.capture(path,{method = ngx.HTTP_GET,args = params})if not resp thenngx.log(ngx.ERR)ngx.exit(404)endreturn resp.body
endlocal _M = {http_get = http_get,get_from_redis = get_from_redis
}return _M

/usr/local/openresty/nginx/lua/item.lua

-- 导入common包
local _M = {}common = require('mylua.common')
http_get = common.http_get
get_from_redis = common.get_from_redisfunction _M.get_data()local args = ngx.req.get_uri_args()-- 先查询Redislocal itemJson = get_from_redis("goods-center:goodsInfo:" .. args["goodsId"])ngx.log(ngx.INFO,"get from redis itemJson,  ",itemJson)if itemJson == nil then-- redis 没有,则查询服务器信息itemJson = http_get("/goods-center/getGoodsDetails",args)endngx.say(itemJson)
endreturn _M

修改/usr/local/openresty/nginx/conf/nginx.conf 添加下面代码

    lua_shared_dict redis_pool 100m;server {location /nginx-cache/goods-center/getGoodsDetails {default_type application/json;content_by_lua_block {require("lua/item").get_data()}}location ~ ^/goods-center/ {proxy_pass http://192.168.31.112:9527;}

6 压测详情

1 命中缓存压测

命中缓存的情况吞吐量1400多,并且采用本章第5小结这种线程池的方式连接不会报错,基本上可以商用了,唯一缺点是并发量没有达到预期,通过排查原因发现,大量的连接池并未真正生效,仍然有大量的创建连接,可能这是影响性能的主要因素,如果有同学解决了这个问题可以在评论区分享一下

2 未命中缓存压测

未命中缓存,会请求后端Tomcat服务器,Tomcat服务器会查询MySQL,这边的吞吐量测试数据为313,也不怎么高,排查了一下原因,仍然是Redis一直在不停的创建连接,这个问题目前还没有找到解决方案

六、总结

通过Nginx+lua 的方式,在Nginx这层就去查询Redis缓存,看起来的确是个非常棒的方案,但是缺点是操作起来特别麻烦,需要开发人员了解Nginx + Lua  还要了解Openresty 如何集成Nginx + Lua  + Redis,还要掌握在这种方式下,能够使用好Redis的连接池。最关键的是目前这种技术的文档并不完善,代码在某些地方不是特别的成熟,网上能找到的资料都很少而且都比较皮毛,不够深入,然而开发人员却需要深入地了解他们,才能比较好的驾驭这种方式,这次探究仍然有一个遗留问题就是Openresty + Lua + Redis 连接池的方式,连接池看起来有时候会不生效,也不是每次都不生效,有一定的概率,从而导致性能并不高,这个需要后面再研究解决它

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/668155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MQ面试题整理(持续更新)

1. MQ的优缺点 优点&#xff1a;解耦&#xff0c;异步&#xff0c;削峰 缺点&#xff1a; 系统可用性降低 系统引入的外部依赖越多&#xff0c;越容易挂掉。万一 MQ 挂了&#xff0c;MQ 一挂&#xff0c;整套系统崩 溃&#xff0c;你不就完了&#xff1f;系统复杂度提高 硬生…

ES高可用架构涉及常用功能整理

ES高可用架构涉及常用功能整理 1. es的高可用系统架构和相关组件2. es的核心参数2.1 常规配置2.2 特殊优化配置2.2.1 数据分片按ip打散2.2.2 数据分片机架感知2.2.3 强制要求数据分片机架感知2.2.4 写入线程池优化2.2.5 分片balance优化2.2.6 限流控制器优化 3. es常用命令3.1 …

前缀和 acwing

思路&#xff1a;两个数组&#xff0c;一个数组用来保存数据&#xff0c;一个数组来求对应项的和 前缀和S[r]-s[r-1] 空出来下标0 从1开始 方便表示&#xff0c;防止越界 c代码实现: #include<iostream> using namespace std; const int N1000000; int a[N],s[N]; …

344. Reverse String(反转字符串)

题目描述 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 问题分析 以中间字符为轴&#xff0c;将两边的字符对换…

CSS-IN-JS

CSS-IN-JS 为什么会有CSS-IN-JS CSS-IN-JS是web项目中将CSS代码捆绑在JavaScript代码中的解决方案。 这种方案旨在解决CSS的局限性&#xff0c;例如缺乏动态功能&#xff0c;作用域和可移植性。 CSS-IN-JS介绍 1&#xff1a;CSS-IN-JS方案的优点&#xff1a; 让css代码拥…

Java与SpringBoot:实现高效车险理赔信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

ArcGIS学习(三)数据可视化

ArcGIS学习(三)数据可视化 1.矢量数据可视化 需要提前说明的是,在ArcGIS中,所有的可视化选项设置都是在“图层属性”对话框里面的“符号系统”中实现的。 对于矢量数据的可视化,主要有四种可视化方式: 按“要素”可视化按“类别”可视化按“数量”可视化按“图表”可视…

【Elasticsearch】从入门到精通

目前java常见的针对大数据存储的方案并不多&#xff0c;常见的就是mysql的分库分表、es存储 这里偏向es存储方案&#xff0c;es不同的版本之间其实差异还挺大的&#xff0c;本篇博文版本Elasticsearch 7.14.0 Springboot整合Easy-Es Easy-Es官方文档 Elasticsearch的初步认识 …

机器翻译后的美赛论文怎么润色

美赛论文的语言表达一直是组委会看重的点&#xff0c;清晰的思路和地道的语言在评审中是重要的加分项。 今天我们就来讲讲美赛论文的语言问题。 我相信有相当一部分队伍在打美赛的时候&#xff0c;出于效率的考量&#xff0c;都会选择先写中文论文&#xff0c;再机翻成英文。 …

【蓝桥杯冲冲冲】[NOIP2003 普及组] 栈

蓝桥杯备赛 | 洛谷做题打卡day27 文章目录 蓝桥杯备赛 | 洛谷做题打卡day27题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示题解代码我的一些话 [NOIP2003 普及组] 栈 题目背景 栈是计算机中经典的数据结构&#xff0c;简单的说&#xff0c;栈就是限制在一…

Linux校准时间 Centos

Linux校准时间 Centos 首先&#xff0c;确保系统中已经安装了tzdata包。如果没有安装&#xff0c;可以使用以下命令安装&#xff1a; sudo yum install tzdata设置系统时区为上海&#xff1a; sudo timedatectl set-timezone Asia/Shanghai验证时区设置是否生效&#xff1a;…

图解支付-金融级密钥管理系统:构建支付系统的安全基石

经常在网上看到某某公司几千万的个人敏感信息被泄露&#xff0c;这要是放在持牌的支付公司&#xff0c;可能就是一个非常大的麻烦&#xff0c;不但会失去用户的信任&#xff0c;而且可能会被吊销牌照。而现实情况是很多公司的技术研发人员并没有足够深的安全架构经验来设计一套…

RK3568平台 设备模型基本框架-kobject 和kset

一.什么是设备模型 字符设备驱动通常适用于相对简单的设备&#xff0c;对于一些更复杂的功能&#xff0c;比如说电源管理和热插拔事件管理&#xff0c;使用字符设备框架可能不够灵活和高效。为了应对更复杂的设备和功能&#xff0c;Linux内核提供了设备模型。设备模型允许开发…

Centos7安装图形界面并使用Win10远程桌面连接

Centos7安装图形界面并使用Win10远程桌面连接 一、关闭防火墙和selinux二、安装Server with GUI三、设置系统默认启动进入GUI界面四、安装epel库和xrdp五、启动xrdp并设置成开机自启六、win10电脑打开连接 一、关闭防火墙和selinux #临时关闭防火墙 [rootse1215 ~]# systemctl…

WordPress从入门到精通【安装部署】

初识WordPress WordPress&#xff0c;简称WP&#xff0c;其简称的由来是取英文单词“word”与“press”的首字母 WP中文官网 1WP主站&#xff08;英文&#xff09; 官方标称&#xff0c;已有43%的网站在使用WordPress WordPress亮点 WP使用PHP语言开发&#xff0c;兼容性极…

python推荐算法在汽车用品商城营销系统 django+flask

本论文拟采用计算机技术设计并开发的汽车营销中的设计与实践 &#xff0c;主要是为用户提供服务。使得会员可以在系统上查看汽车商品、汽车快讯、还可以咨询客服&#xff0c;管理员对信息进行统一管理&#xff0c;与此同时可以筛选出符合的信息&#xff0c;给笔者提供更符合实际…

FFMPEG推流到B站直播

0、参考 ffmpeg安装参考小弟另外的一个博客&#xff1a;FFmpeg和rtsp服务器搭建视频直播流服务-CSDN博客推流参考&#xff1a;用ffmpeg 做24小时推流直播_哔哩哔哩_bilibili 一、获取b站直播码 点击开始直播后&#xff0c;会出现以下的画面 二、ffmpeg进行直播推流 ffmpeg -r…

网络规划与部署实训

一 实训目的及意义 本周实训主要是了解网络规划与部署&#xff0c;熟悉三大厂商华为、思科、锐捷交换机路由器以及相关协议的原理和配置&#xff0c;提高学生的动手能力和分析规划部署能力。 实训主要针对计算机网络系统集成的设计与实现的实际训练&#xff0c;着重锻炼学生熟练…

Flutter开发iOS问题记录

一、版本适配问题 warning: The iOS deployment target ‘IPHONEOS_DEPLOYMENT_TARGET’ is set to 10.0, but the range of supported deployment target versions is 12.0 to 17.2.99. (in target ‘Protobuf’ from project ‘Pods’) 可以通过在podfile中配置解决。 pos…

docker数据管理

docker数据管理 1.数据卷2.启动一个挂载数据卷的容器3.查看数据卷的具体信息/删除数据卷4.挂载主机目录 1.数据卷 数据卷 是一个可供一个或多个容器使用的特殊目录&#xff0c;它绕过 UnionFS&#xff0c;可以提供很多有用的特性&#xff1a; 数据卷 可以在容器之间共享和重用…