ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

openresty中使用redis模块

2022-07-12 22:03:31  阅读:186  来源: 互联网

标签:end openresty err redis 模块 return local red


在一些高并发的场景中,我们常常会用到缓存技术,现在我们常用的分布式缓存redis是最知名的,

我们这里介绍一下如何操作redis。

操作redis,我们需要引入redis模块 require "resty.redis";

我们现在做个可以操作redis进行赋值,读值的案例

一)连接redis服务器

---定义 redis关闭连接的方法
local function close_redis(red)
if not red then
return
end
local ok, err = red:close()
if not ok then
ngx.say("close redis error : ", err)
end
end

local redis = require "resty.redis" --引入redis模块
local red = redis:new() --创建一个对象,注意是用冒号调用的
--设置超时(毫秒)
red:set_timeout(1000)
--建立连接
local ip = "192.168.31.247"
local port = 6379
local ok, err = red:connect(ip, port)
if not ok then
ngx.say("connect to redis error : ", err)
return close_redis(red)
end
--调用API设置key
ok, err = red:set("msg", "hello world")
if not ok then
ngx.say("set msg error : ", err)
return close_redis(red)
end
--调用API获取key值
local resp, err = red:get("msg")
if not resp then
ngx.say("get msg error : ", err)
return close_redis(red)
end

ngx.say("msg : ", resp)
close_redis(red)

请求结果 msg : hello world

--------------------------------
注意:得到的数据为空处理 ,redis返回的空 为null,所以不能用nil判断,而要用ngx.null判断
if resp == ngx.null then
resp = '' --比如默认值
end


--------------连接授权的redis-----------------
在redis.conf配置文件 配置认证密码
requirepass 123456

注意:windows 启动redis时,配置redis.windows.conf;并且不能直接 双击redis-server.exe,
如果双击启动,默认不会找此目录下的配置文件;需要指定配置文件
解决方案:
1)cmd窗口中 运行 redis-server.exe redis.windows.conf
2)新建一个bat批处理文件 文件内容 redis-server.exe redis.windows.conf


连接报错set msg error : NOAUTH Authentication required.因为认证出错
在red:connect成功后,调用red:auth认证密码

ok, err = red:auth("123456")
if not ok then
ngx.say("failed to auth: ", err)
return close_redis(red)
end

=======================================================

二)redis连接池

redis的连接是tcp连接,建立TCP连接需要三次握手,而释放TCP连接需要四次握手,而这些往返时延仅需要一次,
以后应该复用TCP连接,此时就可以考虑使用连接池,即连接池可以复用连接。
我们需要把close_redis函数改造一下

local function close_redis(red)
if not red then
return
end
--释放连接(连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end
即设置空闲连接超时时间防止连接一直占用不释放;设置连接池大小来复用连接。
注意:
1、连接池是每Worker进程的,而不是每Server的;
2、当连接超过最大连接池大小时,会按照LRU算法回收空闲连接为新连接使用;
3、连接池中的空闲连接出现异常时会自动被移除;
4、连接池是通过ip和port标识的,即相同的ip和port会使用同一个连接池(即使是不同类型的客户端);
5、连接池第一次set_keepalive时连接池大小就确定下了,不会再变更;

---------------------------------------------------

注意:我们如何知道,redis连接对象是从连接池中获取的,还是新创建的连接呢??

使用 red:get_reused_times --->得到此连接被使用的次数

如果当前连接不是从内建连接池中获取的,该方法总是返回 0 ,也就是说,该连接还没有被使用过。

如果连接来自连接池,那么返回值永远都是非零。所以这个方法可以用来确认当前连接是否来自池子。

=============================================
连接优化

采用连接池,连接带认证的redis

---定义 redis关闭连接的方法
local function close_redis(red)
if not red then
return
end
--释放连接(连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx.say("set keepalive error : ", err)
end
end

local redis = require "resty.redis" --引入redis模块
local red = redis:new() --创建一个对象,注意是用冒号调用的
--设置超时(毫秒)
red:set_timeout(1000)
--建立连接
local ip = "192.168.31.247"
local port = 6379
local ok, err = red:connect(ip, port)
if not ok then
ngx.say("connect to redis error : ", err)
return close_redis(red)
end

local count, err = red:get_reused_times()
if 0 == count then ----新建连接,需要认证密码
ok, err = red:auth("123456")
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

--调用API设置key
ok, err = red:set("msg", "hello world333333333")
if not ok then
ngx.say("set msg error : ", err)
return close_redis(red)
end
--调用API获取key值
local resp, err = red:get("msg")
if not resp then
ngx.say("get msg error : ", err)
return close_redis(red)
end

ngx.say("msg : ", resp)
close_redis(red)

=======================================

非常注意:连接池使用过程中,业务代码有select方法,会导致数据错乱

ok, err = red:select(1) --->选择db
if not ok then
ngx.say("failed to select db: ", err)
return
end

如:
A业务使用了db1,所以使用了 select(1);

B业务使用默认的db0,select(0)遗漏

但A,B业务共用了连接池,很有可能 B业务拿到的 A业务使用的连接,而此连接操作的数据库db1;
而B业务中代码没有指定select数据库,所以B业务操作数据到了db1中;导致数据错乱

切记!!!

 

在关于web+lua+openresty开发中,项目中会大量操作redis,

重复创建连接-->数据操作-->关闭连接(或放到连接池)这个完整的链路调用完毕,
甚至还要考虑不同的 return 情况做不同处理,就很快发现代码中有大量的重复

推荐一个二次封装的类库
---------------------------------

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

return _M

---------------------------------------
调用案例

local redis = require "resty.redis_iresty"

local opts = {
ip = "192.168.31.247",
port = "6379",
password = "123456",
db_index = 1
}

local red = redis:new(opts)

local ok, err = red:set("dog", "an animal11111")
if not ok then
ngx.say("failed to set dog: ", err)
return
end

ngx.say("set result: ", ok)


---------------------------------------
管道

redis.set
redis.get


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


不需要重复造轮子,只要这个轮子 稳定,高效

标签:end,openresty,err,redis,模块,return,local,red
来源: https://www.cnblogs.com/sunnyyangwang/p/16471946.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有