Redis是近几年业内非常流行的内存KV存储系统,以速度快和丰富的数据类型而闻名,可以用在缓存、消息队列、数据库等领域,许多国内外知名公司都是它的用户。OpenResty发行包内置了lua-resty-redis库,它基于cosocket实现了非阻塞的Redis客户端,支持Redis的所有命令以及管道操作。
lua-resty-redis库需要显式加载后才能使用,即:
local redis = require "resty.redis" -- 加载lua-resty-redis库
在操作Redis之前,我们应当调用new方法创建连接对象
rds, err = redis:new() -- 创建Redis连接对象
new方法执行成功后会返回一个连接对象rds,内部持有一个cosocket。
创建对象之后还需要设置超时时间,但函数名与cosocket略有不同:
rds:set_timeout(time) -- 注意名字里有一个下画线!
函数connect使用地址、端口等参数连接Redis服务器:
ok, err = rds:connect(host, port) -- 连接Redis服务器
它也会优先复用连接池里的空闲连接。如果连接成功,返回ok,否则返回错误原因。
下面的示例创建了Redis对象,并连接到本地服务器:
local redis = require "resty.redis" -- 加载resty.redis模块
local rds = redis:new() -- 新建一个redis连接对象
rds:set_timeout(1000) -- 设置超时时间为1000毫秒
ok, err = rds:connect("127.0.0.1",6379) -- 连接Redis服务器
if not ok then -- 检查是否连接成功
ngx.say("failed to connect : ", err)
rds:close() -- 连接失败需要及时关闭释放连接
return
end
Redis操作完毕或者出错,应该调用函数close关闭连接,释放cosocket资源:
ok, err = rds:close() -- 关闭连接
lua-resty-redis库复用连接功能基于cosocket,同样有两个函数用来放入连接池和获取复用次数,但需要注意名字有微小的不同(名字里有下画线):
ok, err = rds:set_keepalive(timeout, size) -- 放入连接池
count, err = rds:get_reused_times() -- 获取复用次数
如果Redis服务器开启了认证功能,那么connect后使用get_reused_times函数就可以检查连接是否已经认证过了(即count>0),避免重复发送auth命令
lua-resty-redis库使用了动态生成Redis命令的技巧,不仅支持现有的命令,也能够支持今后可能新出的命令。每个Redis命令对应一个同名的函数,只是变为了小写的形式,例如SET对应rds:set, INCR对应rds:incr,基本形式是:
res, err = rds:command(key, ...) -- 执行Redis命令
需要特别注意一点:如果命令的执行结果是NULL,那么在OpenResty里会表示为常量ngx.null而不是nil,即执行成功但返回的是空结果。
下面的代码示范了一些Redis命令的的用法(省略了错误处理):
ok, err = rds:set("metroid", "prime") -- 向Redis写入数据
res, err = rds:get("metroid") -- 从Redis读取数据
assert(res ~= ngx.null) -- 结果不是NULL
assert(res == "prime") -- 检查读取的数据
ok, err = rds:hset('zelda', 'bow', 2017) -- 设置散列数据
res, err = rds:hget('zelda', 'bow') -- 读取散列数据
ok, err = rds:del('list') -- 删除一个键
ok, err = rds:lpush('list', 1,2,3,4) -- 向列表添加多个元素
res, err = rds:lpop('list') -- 从列表弹出一个元素
学会了以上基本指令,我们需要看看在怎么封装这些命令,形成一个Redis工具类,代码如下:
-- redis工具类,对lua-resty-redis进行封装
local redis = require("resty.redis")
local config = {
host = "10.56.216.212",
port = 6377,
password = "1234567",
db_index = 0,
max_idle_time = 30000,
database = 1,
pool_size = 100,
timeout = 5000,
}
local _M = {}
function _M.new()
local instance = {
host = config.host or "127.0.0.1",
port = config.port or 6379,
password = config.password or "",
timeout = config.timeout or 5000,
database = config.database or 0,
max_idle_time = config.max_idle_time or 60000,
pool_size = config.pool_size or 100
}
setmetatable(instance, {__index = _M})
return instance
end
-- 执行reids指令
-- func 具体指令函数,在该类下方定义
-- ... 可变参数,参数含义看具体的func函数,函数不同,函数的传参的数量和值都不同
function _M:exec(func, ...)
local red = redis:new()
-- 为后续操作设置超时(以毫秒为单位)保护,包括connect方法。
red:set_timeout(self.timeout)
-- 建立连接
local ok, err = red:connect(self.host, self.port)
if not ok or err ~= nil then
ngx.log(ngx.ERR, "redis: ", "connect error, host: " .. self.host .. ", port: " .. self.port, err)
return nil, err
end
if self.password ~= "" then
-- 如果连接来自于连接池中,get_reused_times() 永远返回一个非零的值
-- 只有新的连接才会进行授权
local count = red:get_reused_times()
if count == 0 then
ok, err = red:auth(self.password)
if not ok or err ~= nil then
ngx.log(ngx.ERR, "redis: ", "auth error, host: " .. self.host .. ", port: " .. self.port, err)
red:close()
return nil, err
end
end
end
if self.database ~= 0 then
red:select(self.database)
end
-- 执行业务逻辑
local res, err = func(red, ...)
-- print(res, ', ', type(res), ', ', err, ', ', type(err))
if res == nil or err ~= nil then
-- 表示获取数据错误, 不将连接放回连接池
ngx.log(ngx.ERR, "redis: ", "exec command error" .. self.host .. ", port: " .. self.port, err)
red:close()
return nil, err
end
-- 将连接放回连接池
red:set_keepalive(self.max_idle_time, self.pool_size)
-- 转换结果
if self:is_redis_null(res) then
res = nil
end
return res, err
end
function _M:is_redis_null( res )
if type(res) == "table" then
for k,v in pairs(res) do
if v ~= ngx.null then
return false
end
end
return true
elseif res == ngx.null then
return true
elseif res == nil then
return true
end
return false
end
-- set指令
-- @param key
-- @param value
function _M.set(red, ...)
local arg={...}
return red:set(arg[1], arg[2])
end
-- get指令
-- @param key
function _M.get(red, ...)
local arg={...}
return red:get(arg[1])
end
-- smembers指令
-- @param set结构的key
function _M.smembers(red, ...)
local arg={...}
return red:smembers(arg[1])
end
-- hgetall指令
-- @param set结构的key
function _M.hgetall(red, ...)
local arg={...}
return red:hgetall(arg[1])
end
...需要加命令自己往后面加
return _M