让我直接涨薪5K的Nginx/OpenResty详解,NginxLua操作Redis有多牛

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 实战案例运行准备:本节涉及的配置文件为源码工程的nginxredis-demo.conf文件。在运行本节实例前需要修改启动脚本openresty-start.bat(或openresty-start.sh)中的PROJECT_CONF变量的值,将其改为nginx-redis-demo.conf,然后重启OpenRestry。

Nginx Lua操作Redis

本节介绍如何使用开源的lua-resty-redis模块在Lua脚本中连接和访问Redis,该模块的官方网址为

https://github.com/openresty/lua-resty-redis。

实战案例运行准备:本节涉及的配置文件为源码工程的nginxredis-demo.conf文件。在运行本节实例前需要修改启动脚本openresty-start.bat(或openresty-start.sh)中的PROJECT_CONF变量的值,将其改为nginx-redis-demo.conf,然后重启OpenRestry。

Redis的CRUD基本操作

使用Lua模块lua-resty-redis之前需要在官方网站下载resty/redis.lua库文件,然后将该库文件加入项目工程所在的Lua外部库路径。lua-resty-redis官方已经申明,大部分的Redis操作命令都实现了同名的Lua API方法。

下面是一个简单的使用Lua模块lua-resty-redis操作Redis的实

例,代码如下:

local redis = require "resty.redis"
local config = require("luaScript.module.config.redis-config");
---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--设置超时时长
local red = redis:new()
--设置超时时长,单位为ms
red:set_timeouts(config.timeout, config.timeout, config.timeout)
--连接服务器
local ok, err = red:connect(config.host_name, config.port)
if not ok then
 ngx.say("failed to connect: ", err)
 return
end
--设置值
ok, err = red:set("dog", "an animal")
if not ok then
 ngx.say("failed to set dog: ", err, "<br>")
 return
else
 ngx.say("set dog: ok", "<br>")
end
--取值
local res, err = red:get("dog")
--判空演示
if not res or res == ngx.null then
 ngx.say("failed to get dog: ", err, "<br>")
 return
else
 ngx.say("get dog: ok", "<br>", res, "<br>")
end
--批量操作,减少网络IO次数
red:init_pipeline()
red:set("cat", "cat 1")
red:set("horse", "horse 1")
red:get("cat")red:get("horse")
red:get("dog")
local results, err = red:commit_pipeline()
if not results then
 ngx.say("failed to commit the pipelined requests: ", err)
 return
end
for i, res in ipairs(results) do
 if type(res) == "table" then
 if res[1] == false then
 ngx.say("failed to run command ", i, ": ", res[2], "<br>")
 else
 --处理表容器
 ngx.say("succeed to run command ", i, ": ", res[i], "<br>")
 end
 else
 --处理变量
 ngx.say("succeed to run command ", i, ": ", res, "<br>")
 end
end
--简单的关闭连接
local ok, err = red:close()
if not ok then
 ngx.say("failed to close: ", err)
 return
else
 ngx.say("succeed to close redis")
end

以上Lua脚本处于工程目录下的
luaScript/redis/RedisDemo.lua文件中,完成了如下Redis的操作:

(1)连接Redis服务器。

(2)根据key设置缓存值。

(3)根据key获取缓存值。

(4)批量Redis操作。

(5)简单地关闭Redis连接。

在nginx-redis-demo.conf配置文件中编写一个location配置块来使用该脚本,具体代码如下:

简单操作演示 #redis CRUD简单操作演示
 location /redis_demo {
 content_by_lua_file luaScript/redis/RedisDemo.lua;
 }

修改nginx-redis-demo.conf文件后需要重启OpenRestry,然后可以使用浏览器访问其地址/redis_demo,具体的访问结果如图8-21所示。

网络异常,图片无法展示
|

图8-21 Redis CRUD简单操作演示的访问结

RedisDemo.lua通过require导入了redis-config.lua配置文件,该文件定义了整个项目都需要使用的全局Redis配置信息,其代码如下:

--定义一个统一的redis配置模块
--统一的模块对象
local _Module = {
 --redis服务器的地址
 host_name = "192.168.233.128";
 --redis服务器的端口
 port = "6379";
服务器的数据库 --redis服务器的数据库
 db = "0";
 --redis服务器的密码
 password = '123456';
 --连接超时时长
 timeout = 20000;
 --线程池的连接数量
 pool_size = 100;
 --最大的空闲时间,单位:毫秒
 pool_max_idle_time = 10000;
}
return _Module

实战:封装一个操作Redis的基础类

通过Lua操作Redis会涉及获取连接、操作数据、连接回收等基础性工作,这里建议将这些基础性工作封装到一个Redis操作的基础类,主要的代码如下:

local redis = require "resty.redis"
local basic = require("luaScript.module.common.basic");
local config = require("luaScript.module.config.redis-config");
--连接池大小
local pool_size = config.pool_size;
--最大的空闲时间,单位:毫秒
local pool_max_idle_time = config.pool_max_idle_time;
--一个统一的模块对象
local _Module = {}
_Module.__index = _Module
--类的方法new
function _Module.new(self)
 local object = { red = nil }
 setmetatable(object, self)
 return object
end
--获取redis连接
function _Module.open(self)
 local red = redis:new()
 --设置超时的时间为 2 sec,connect_timeout, send_timeout, read_timeout
 red:set_timeout(config.timeout, config.timeout, config.timeout);
 local ok, err = red:connect( config.host_name, config.port)
 if not ok then
 basic.error("连接redis服务器失败: ", err)
 return false;
 end
 if config.password then
 red:auth(config.password)
 end
 if config.db then
 red:select(config.db)
 end
 basic.log("连接redis服务器成功")
 self.red = red;
 return true;
end
--缓存值
function _Module.setValue(self, key, value)
 ok, err = self.red:set(key, value)
 if not ok then
 basic.error("redis缓存设置失败")
 return false;
 end basic.log("set result ok")
 return true;
end
--值递增
function _Module.incrValue(self, key)
 ok, err = self.red:incr(key)
 if not ok then
 basic.error("redis缓存递增失败 ")
 return false;
 end
 basic.log("incr ok")
 return true;
end
--过期
function _Module.expire(self, key, seconds)
 ok, err = self.red:expire(key, seconds)
 if not ok then
 basic.error("redis设置过期失败 ")
 return false;
 end
 return true;
end
--获取值
function _Module.getValue(self, key)
 local resp, err = self.red:get(key)
 if not resp then
 basic.error("redis缓存读取失败 ")
 return nil;
 end
 return resp;
end
--省略封装的其他Redis操作方法
--将连接还给连接池
function _Module.close(self)
 if not self.red then
 return
 end
 local ok, err = self.red:set_keepalive(pool_max_idle_time, pool_size)
 if not ok then
 basic.error("redis set_keepalive执行失败 ")
 end
 basic.log("redis连接释放成功")
end
return _Module

此基础操作类的名称为RedisOperator,需要使用时通过require("
luaScript.redis.RedisOperator")导入即可。

在Lua中使用Redis连接池

在示例代码RedisDemo.lua脚本中,每一次客户端请求,luaresty-redis模块都会创建一个新的Redis连接。在生产环境中,每一次请求都开启一个服务器新连接会导致以下问题:

(1)连接资源被快速消耗。

(2)网络一旦抖动,会有大量TIME_WAIT连接产生,需要定期重启服务程序或机器。

(3)服务器工作不稳定,QPS忽高忽低。

(4)性能普遍上不去。

为什么会出现这些性能问题呢?因为每一次传输数据,我们需要完成创建连接、收发数据、拆除连接3个基本步骤,在低并发场景下每次请求完整走完这3步基本上不会有什么问题。然而,一旦挪到高并发应用场景,性能问题就出现了。

性能优化的第一件事情就是把短连接改成长连接,可以减少大量创建连接、拆除连接的时间。从性能上来说肯定要比短连接好很多,但还是有比较大的浪费。

性能优化的第二件事情就是使用连接池。通过一个连接池pool将所有长连接缓存和管理起来,谁需要使用,就从这里取走,干完活立马放回来。

实际上,大家在开发过程中用到的连接池是非常多的,比如HTTP连接池、数据库连接池、消息推送连接池等。实际上,几乎所有点到点之间的连接资源复用都需要通过连接池完成。在OpenResty中,lua-resty-redis模块管理了一个连接池,并且定义了set_keepalive方法完成连接的回收和复用。set_keepalive方法的语法如下:

ok, err = red:set_keepalive(max_idle_timeout, pool_size)

该方法将当前的Redis连接立即放入连接池。其中,max_idle_timeout参数指定连接在池中的最大空闲超时时长(以毫秒为单位);pool_size参数指定每个Nginx工作进程的连接池的最大连接数。如果入池成功,就返回1;如果入池出现错误,就返回nil,并返回错误描述字符串。

下面看一个连接回收的示例,具体的代码如下:

location /pool_demo {
 content_by_lua_block {
 local redis = require "resty.redis"
 local config = require("luaScript.module.config.redis-config");
 --连接池大小
 local pool_size = config.pool_size;
 --最大的空闲时间,单位:毫秒
 local pool_max_idle_time = config.pool_max_idle_time;
 local red = redis:new()
 local ok, err = red:connect(config.host_name, config.port)
 if not ok then
 ngx.say("failed to connect: ", err)
 return
 else
 ngx.say("succeed to connect redis", "<br>")
 end
 red: auth(config.password)
 --red: set_keepalive(pool_max_idle_time, pool_size) --① 坑
 ok, err = red:set("dog", "an animal")
 if not ok then
 --red: set_keepalive(pool_max_idle_time, pool_size) --② 坑
 return
 end
 --③ 正确回收
 red: set_keepalive(pool_max_idle_time, pool_size)
 ngx.say("succeed to collect redis connection", "<br>")
 }
 }

以上代码中,有3个需要注意的地方,具体介绍如下:

①坑:只有数据传输完毕、Redis连接使用完成之后,才能调用set_keepalive方法将连接放到池子里,set_keepalive方法会立即将red连接对象转换到closed状态,后面的Redis调用将出错。

②坑:如果设置错误,那么red连接对象不一定可用,不能把可用性存疑的连接放回池子里,如果另一个请求从连接池获取到一个不能用的连接,就会直接报错。

③正确回收:此处的set_keepalive方法调用是正确的。

以上代码处于nginx-redis-demo.conf文件中,修改后需要重启OpenRestry,然后可以使用浏览器访问其地址/pool_demo,具体的访问结果如图8-22所示。

网络异常,图片无法展示
|

图8-22 Redis连接池演示实例的执行结果

set_keepalive方法完成连接回收之后,下一次通过red:

connect(...)获取连接时,connect方法在创建新连接前会在连接池中查找空闲连接,只有查找失败才会真正创建新连接。

总之,作为一个专业的服务端开发工程师,大家必须要对连接池有较深理解,其实不论是Redis连接池、HTTP连接池,还是数据库连接池,甚至是线程池,其原理都是差不多的。

本文给大家讲解的内容是Nginx/OpenResty详解,Nginx Lua编程,Nginx Lua操作Redis

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
2月前
|
NoSQL 网络协议 应用服务中间件
redis,memcached,nginx网络组件
redis,memcached,nginx网络组件
21 0
|
4月前
|
应用服务中间件 nginx
[nginx]编译安装openresty
[nginx]编译安装openresty
|
5月前
|
NoSQL Linux Redis
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
Redis性能优化问题之想确认Redis延迟变大是否因为fork耗时导致的,如何解决
|
4月前
|
NoSQL Java 应用服务中间件
使用Redis和Nginx分别实现限制接口请求频率
这篇文章介绍了如何使用Redis和Nginx分别实现限制接口请求频率的方法,包括具体的命令使用、代码实现和配置步骤。
79 0
|
5月前
|
Ubuntu 应用服务中间件 Linux
Linux学习之Ubuntu 20中OpenResty的nginx目录里内容和配置文件
总的来说,OpenResty的Nginx配置文件是一个强大的工具,它允许你以非常灵活的方式定义你的Web服务的行为。
110 2
|
6月前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
134 1
|
6月前
|
缓存 NoSQL Redis
redis管道操作(节省网络IO开销)
pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
58 1
|
6月前
|
NoSQL Java Redis
如何在 Java 中操作这些 Redis 数据结构的基本方法
如何在 Java 中操作这些 Redis 数据结构的基本方法
42 2
|
6月前
|
存储 NoSQL Redis
Python—操作redis的一些心得
Python—操作redis的一些心得
28 0
下一篇
DataWorks