320 lines
8.4 KiB
Lua
320 lines
8.4 KiB
Lua
|
|
---
|
|||
|
|
--- Desc: 对OpenResty中使用Lua对Redis操作的封装库,支持订阅、管道等功能
|
|||
|
|
--- Note:本Lua脚本借鉴了网络,未经测试,仅供参考,也不提供任何技术支持
|
|||
|
|
---
|
|||
|
|
local redis_c = require "resty.redis"
|
|||
|
|
|
|||
|
|
local ok, new_tab = pcall(require, "table.new")
|
|||
|
|
if not ok or type(new_tab) ~= "function" then
|
|||
|
|
new_tab = function (narr, nrec) return {} end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local _M = new_tab(0, 155)
|
|||
|
|
_M._VERSION = '0.01'
|
|||
|
|
|
|||
|
|
local commands = {
|
|||
|
|
"append", "auth", "bgrewriteaof",
|
|||
|
|
"bgsave", "bitcount", "bitop",
|
|||
|
|
"blpop", "brpop",
|
|||
|
|
"brpoplpush", "client", "config",
|
|||
|
|
"dbsize",
|
|||
|
|
"debug", "decr", "decrby",
|
|||
|
|
"del", "discard", "dump",
|
|||
|
|
"echo",
|
|||
|
|
"eval", "exec", "exists",
|
|||
|
|
"expire", "expireat", "flushall",
|
|||
|
|
"flushdb", "get", "getbit",
|
|||
|
|
"getrange", "getset", "hdel",
|
|||
|
|
"hexists", "hget", "hgetall",
|
|||
|
|
"hincrby", "hincrbyfloat", "hkeys",
|
|||
|
|
"hlen",
|
|||
|
|
"hmget", "hmset", "hscan",
|
|||
|
|
"hset",
|
|||
|
|
"hsetnx", "hvals", "incr",
|
|||
|
|
"incrby", "incrbyfloat", "info",
|
|||
|
|
"keys",
|
|||
|
|
"lastsave", "lindex", "linsert",
|
|||
|
|
"llen", "lpop", "lpush",
|
|||
|
|
"lpushx", "lrange", "lrem",
|
|||
|
|
"lset", "ltrim", "mget",
|
|||
|
|
"migrate",
|
|||
|
|
"monitor", "move", "mset",
|
|||
|
|
"msetnx", "multi", "object",
|
|||
|
|
"persist", "pexpire", "pexpireat",
|
|||
|
|
"ping", "psetex", "psubscribe",
|
|||
|
|
"pttl",
|
|||
|
|
"publish", --[[ "punsubscribe", ]] "pubsub",
|
|||
|
|
"quit",
|
|||
|
|
"randomkey", "rename", "renamenx",
|
|||
|
|
"restore",
|
|||
|
|
"rpop", "rpoplpush", "rpush",
|
|||
|
|
"rpushx", "sadd", "save",
|
|||
|
|
"scan", "scard", "script",
|
|||
|
|
"sdiff", "sdiffstore",
|
|||
|
|
"select", "set", "setbit",
|
|||
|
|
"setex", "setnx", "setrange",
|
|||
|
|
"shutdown", "sinter", "sinterstore",
|
|||
|
|
"sismember", "slaveof", "slowlog",
|
|||
|
|
"smembers", "smove", "sort",
|
|||
|
|
"spop", "srandmember", "srem",
|
|||
|
|
"sscan",
|
|||
|
|
"strlen", --[[ "subscribe", ]] "sunion",
|
|||
|
|
"sunionstore", "sync", "time",
|
|||
|
|
"ttl",
|
|||
|
|
"type", --[[ "unsubscribe", ]] "unwatch",
|
|||
|
|
"watch", "zadd", "zcard",
|
|||
|
|
"zcount", "zincrby", "zinterstore",
|
|||
|
|
"zrange", "zrangebyscore", "zrank",
|
|||
|
|
"zrem", "zremrangebyrank", "zremrangebyscore",
|
|||
|
|
"zrevrange", "zrevrangebyscore", "zrevrank",
|
|||
|
|
"zscan",
|
|||
|
|
"zscore", "zunionstore", "evalsha"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
local mt = { __index = _M }
|
|||
|
|
|
|||
|
|
local function 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
|
|||
|
|
|
|||
|
|
function _M.close_redis(self, redis)
|
|||
|
|
if not redis then
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
--释放连接(连接池实现)
|
|||
|
|
local pool_max_idle_time = self.pool_max_idle_time --最大空闲时间 毫秒
|
|||
|
|
local pool_size = self.pool_size --连接池大小
|
|||
|
|
|
|||
|
|
local ok, err = redis:set_keepalive(pool_max_idle_time, pool_size)
|
|||
|
|
if not ok then
|
|||
|
|
ngx.say("set keepalive error : ", err)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- change connect address as you need
|
|||
|
|
function _M.connect_mod( self, redis )
|
|||
|
|
redis:set_timeout(self.timeout)
|
|||
|
|
|
|||
|
|
local ok, err = redis:connect(self.ip, self.port)
|
|||
|
|
if not ok then
|
|||
|
|
ngx.say("connect to redis error : ", err)
|
|||
|
|
return self:close_redis(redis)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if self.password then ----密码认证
|
|||
|
|
local count, err = redis:get_reused_times()
|
|||
|
|
if 0 == count then ----新建连接,需要认证密码
|
|||
|
|
ok, err = redis:auth(self.password)
|
|||
|
|
if not ok then
|
|||
|
|
ngx.say("failed to auth: ", err)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
elseif err then ----从连接池中获取连接,无需再次认证密码
|
|||
|
|
ngx.say("failed to get reused times: ", err)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return ok,err;
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function _M.init_pipeline( self )
|
|||
|
|
self._reqs = {}
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function _M.commit_pipeline( self )
|
|||
|
|
local reqs = self._reqs
|
|||
|
|
|
|||
|
|
if nil == reqs or 0 == #reqs then
|
|||
|
|
return {}, "no pipeline"
|
|||
|
|
else
|
|||
|
|
self._reqs = nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local redis, err = redis_c:new()
|
|||
|
|
if not redis then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = self:connect_mod(redis)
|
|||
|
|
if not ok then
|
|||
|
|
return {}, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
redis:init_pipeline()
|
|||
|
|
for _, vals in ipairs(reqs) do
|
|||
|
|
local fun = redis[vals[1]]
|
|||
|
|
table.remove(vals , 1)
|
|||
|
|
|
|||
|
|
fun(redis, unpack(vals))
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local results, err = redis:commit_pipeline()
|
|||
|
|
if not results or err then
|
|||
|
|
return {}, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if is_redis_null(results) then
|
|||
|
|
results = {}
|
|||
|
|
ngx.log(ngx.WARN, "is null")
|
|||
|
|
end
|
|||
|
|
-- table.remove (results , 1)
|
|||
|
|
|
|||
|
|
--self.set_keepalive_mod(redis)
|
|||
|
|
self:close_redis(redis)
|
|||
|
|
|
|||
|
|
for i,value in ipairs(results) do
|
|||
|
|
if is_redis_null(value) then
|
|||
|
|
results[i] = nil
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return results, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
|
|||
|
|
local function do_command(self, cmd, ... )
|
|||
|
|
if self._reqs then
|
|||
|
|
table.insert(self._reqs, {cmd, ...})
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local redis, err = redis_c:new()
|
|||
|
|
if not redis then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = self:connect_mod(redis)
|
|||
|
|
if not ok or err then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
redis:select(self.db_index)
|
|||
|
|
|
|||
|
|
local fun = redis[cmd]
|
|||
|
|
local result, err = fun(redis, ...)
|
|||
|
|
if not result or err then
|
|||
|
|
-- ngx.log(ngx.ERR, "pipeline result:", result, " err:", err)
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
if is_redis_null(result) then
|
|||
|
|
result = nil
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
--self.set_keepalive_mod(redis)
|
|||
|
|
self:close_redis(redis)
|
|||
|
|
|
|||
|
|
return result, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
for i = 1, #commands do
|
|||
|
|
local cmd = commands[i]
|
|||
|
|
_M[cmd] =
|
|||
|
|
function (self, ...)
|
|||
|
|
return do_command(self, cmd, ...)
|
|||
|
|
end
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function _M.new(self, opts)
|
|||
|
|
opts = opts or {}
|
|||
|
|
local timeout = (opts.timeout and opts.timeout * 1000) or 1000
|
|||
|
|
local db_index= opts.db_index or 0
|
|||
|
|
local ip = opts.ip or '127.0.0.1'
|
|||
|
|
local port = opts.port or 6379
|
|||
|
|
local password = opts.password
|
|||
|
|
local pool_max_idle_time = opts.pool_max_idle_time or 60000
|
|||
|
|
local pool_size = opts.pool_size or 100
|
|||
|
|
|
|||
|
|
return setmetatable({
|
|||
|
|
timeout = timeout,
|
|||
|
|
db_index = db_index,
|
|||
|
|
ip = ip,
|
|||
|
|
port = port,
|
|||
|
|
password = password,
|
|||
|
|
pool_max_idle_time = pool_max_idle_time,
|
|||
|
|
pool_size = pool_size,
|
|||
|
|
_reqs = nil }, mt)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
function _M.subscribe( self, channel )
|
|||
|
|
local redis, err = redis_c:new()
|
|||
|
|
if not redis then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local ok, err = self:connect_mod(redis)
|
|||
|
|
if not ok or err then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local res, err = redis:subscribe(channel)
|
|||
|
|
if not res then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local function do_read_func ( do_read )
|
|||
|
|
if do_read == nil or do_read == true then
|
|||
|
|
res, err = redis:read_reply()
|
|||
|
|
if not res then
|
|||
|
|
return nil, err
|
|||
|
|
end
|
|||
|
|
return res
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
redis:unsubscribe(channel)
|
|||
|
|
self.set_keepalive_mod(redis)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return do_read_func
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
return _M
|
|||
|
|
|
|||
|
|
---------------------------------------
|
|||
|
|
-- 调用案例
|
|||
|
|
|
|||
|
|
local redis = require "RedisExtOps"
|
|||
|
|
local opts = {
|
|||
|
|
ip = "10.11.0.215",
|
|||
|
|
port = "6379",
|
|||
|
|
password = "redis123",
|
|||
|
|
db_index = 1
|
|||
|
|
}
|
|||
|
|
local red = redis:new(opts)
|
|||
|
|
local ok, err = red:set("dog", "an animal")
|
|||
|
|
if not ok then
|
|||
|
|
ngx.say("failed to set dog: ", err)
|
|||
|
|
return
|
|||
|
|
end
|
|||
|
|
ngx.say("set result: ", ok)
|
|||
|
|
|
|||
|
|
---------------------------------------
|
|||
|
|
-- 管道
|
|||
|
|
red:init_pipeline()
|
|||
|
|
red:set("cat", "Marry")
|
|||
|
|
red:set("horse", "Bob")
|
|||
|
|
red:get("cat")
|
|||
|
|
red:get("horse")
|
|||
|
|
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
|
|||
|
|
ngx.say(res,"<br/>");
|
|||
|
|
end
|