OpenResty封装Redis客户端

Reids 客户端

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')                  -- 从列表弹出一个元素

OpenResty封装Redis客户端

学会了以上基本指令,我们需要看看在怎么封装这些命令,形成一个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

你可能感兴趣的:(OpenResty,redis,lua,openresty)