├── .github └── workflows │ └── release.yml ├── README.md ├── dist.ini ├── lib └── resty │ └── redis-util.lua └── rockspec └── lua-resty-redis-util-0.07-1.rockspec /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | name: Build and Publish 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v3 17 | 18 | - name: Build and push opm 19 | run: | 20 | docker run --rm -t -v $(pwd):/tmp openresty/openresty:alpine-fat /bin/sh -c "echo -e 'github_account=${{ github.actor }}\ngithub_token=${{ secrets.OPM_GITHUB_TOKEN }}\nupload_server=https://opm.openresty.org\ndownload_server=https://opm.openresty.org' > ~/.opmrc && cd /tmp && opm build && opm upload" 21 | shell: bash 22 | 23 | - name: Build and push luarocks 24 | run: | 25 | rockspec=$(ls rockspec/ | grep rockspec | sort -k5,5 -r | head -n1) 26 | docker run --rm -t -v $(pwd):/tmp akorn/luarocks:lua5.1-alpine /bin/sh -c "apk --no-cache add alpine-sdk perl zip unzip && cd /tmp && luarocks install dkjson && luarocks upload rockspec/${rockspec} --api-key ${{ secrets.LUAROCKS_API_KEY }}" 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 介绍 3 | 4 | 本项目是基于[openresty/lua-resty-redis][] 是[章亦春(agentzh)][agentzh]开发的openresty中的操作redis的库。进行二次封装的工具库。核心功能还是由[openresty/lua-resty-redis][]完成的。 5 | 6 | 本文假设你已经了解nginx+lua或者openresty如何使用lua脚本(e.g. `lua_package_path`配置,`*_by_lua_file`),基础的redis相关知识,以及[openresty/lua-resty-redis][]的基本使用方法([openresty/lua-resty-redis#README.md][README.md])。 7 | 8 | ## 安装(Install) 9 | 10 | ```bash 11 | # opm 安装 12 | opm get anjia0532/lua-resty-redis-util 13 | 14 | # luarocks 安装 15 | luarocks install lua-resty-redis-util 16 | ``` 17 | 18 | ## 对比 19 | 20 | 截取官方部分代码,进行说明 21 | 22 | ```lua 23 | local redis = require "resty.redis" 24 | local red = redis:new() 25 | 26 | red:set_timeout(1000) -- 1 sec --设置超时时间 27 | 28 | local ok, err = red:connect("127.0.0.1", 6379) --设置redis的host和port 29 | if not ok then --判断生成连接是否失败 30 | ngx.say("failed to connect: ", err) 31 | return 32 | end 33 | 34 | ok, err = red:set("dog", "an animal") --插入键值(类似 mysql insert) 35 | if not ok then --判断操作是否成功 36 | ngx.say("failed to set dog: ", err) 37 | return 38 | end 39 | 40 | ngx.say("set result: ", ok) -- 页面输出结果 41 | -- put it into the connection pool of size 100, 42 | -- with 10 seconds max idle time 43 | local ok, err = red:set_keepalive(10000, 100) --将连接放入连接池,100个连接,最长10秒的闲置时间 44 | if not ok then --判断放池结果 45 | ngx.say("failed to set keepalive: ", err) 46 | return 47 | end 48 | -- 如果不放池,用完就关闭的话,用下面的写法 49 | -- or just close the connection right away: 50 | -- local ok, err = red:close() 51 | -- if not ok then 52 | -- ngx.say("failed to close: ", err) 53 | -- return 54 | -- end 55 | ``` 56 | 57 | 如果用过java,c#等面向对象的语言,就会觉得这么写太。。。。了,必须重构啊,暴露太多无关细节了,导致代码中有大量重复代码了。 58 | 59 | 同样的内容,使用我封装后的代码。隐藏了设置连接池,取连接,用完后放回连接池等操作。 60 | 61 | ```lua 62 | -- 依赖库 63 | local redis = require "resty.redis-util" 64 | -- 初始化 65 | local red = redis:new(); 66 | -- 插入键值 67 | local ok,err = red:set("dog","an animal") 68 | -- 判断结果 69 | if not ok then 70 | ngx.say("failed to set dog:",err) 71 | return 72 | end 73 | -- 页面打印结果 74 | ngx.say("set result: ", ok) -- 页面输出结果 75 | ``` 76 | 77 | ## 注意事项(Note) 78 | 79 | ### 默认值(Default Value) 80 | 81 | ```lua 82 | local red = redis:new(); 83 | --使用了默认值,等同于 84 | local red2 = redis:new({ 85 | host='127.0.0.1', 86 | port=6379, 87 | db_index=0, 88 | password=nil, 89 | timeout=1000, 90 | keepalive=60000, 91 | pool_size=100 92 | }); 93 | ``` 94 | 95 | - host: redis host,default: 127.0.0.1 96 | - port: redis port,default:6379 97 | - db_index: redis库索引(默认0-15 共16个库),默认的就是0库(建议用不同端口开不同实例或者用不同前缀,因为换库需要用select命令),default:0 98 | - password: redis auth 认证的密码 99 | - timeout: redis连接超时时间,default: 1000 (1s) 100 | - keepalive: redis连接池的最大闲置时间, default: 60000 (1m) 101 | - pool_size: redis连接池大小, default: 100 102 | 103 | ### subscribe 104 | 105 | 因为没有用到pub/sub,所以只是简单的实现了(un)subscribe,没有继续实现(un)psubscribe(模式订阅), 参考 [Redis 接口的二次封装(发布订阅)][linkRedis接口的二次封装(发布订阅)] 106 | 107 | ```lua 108 | 109 | local cjson = require "cjson" 110 | local red = redis:new(); 111 | 112 | -- 订阅dog频道 113 | local func = red:subscribe( "dog" ) 114 | 115 | -- 判断是否成功订阅 116 | if not func then 117 | return nil 118 | end 119 | 120 | -- 获取值 121 | local res, err = func() --func()=func(true) 122 | -- 如果失败,取消订阅 123 | if err then 124 | func(false) 125 | end 126 | 127 | -- 如果取到结果,进行页面输出 128 | if res then 129 | ngx.say("1: receive: ", cjson.encode(res)) 130 | end 131 | 132 | -- 再次获取 133 | res, err = func() 134 | 135 | -- 获取成功后,取消订阅 func(false) 136 | if res then 137 | ngx.say("2: receive: ", cjson.encode(res)) 138 | func(false) 139 | end 140 | 141 | ``` 142 | 143 | ### pipeline 144 | 145 | 参考 [openresty/lua-resty-redis#Synopsis][] 146 | 147 | ```lua 148 | local cjson = require "cjson" 149 | local red = redis:new(); 150 | 151 | red:init_pipeline() 152 | 153 | red:set("cat", "Marry") 154 | red:set("horse", "Bob") 155 | red:get("cat") 156 | red:get("horse") 157 | 158 | local results, err = red:commit_pipeline() 159 | 160 | if not results then 161 | ngx.say("failed to commit the pipelined requests: ", err) 162 | return 163 | else 164 | ngx.say("pipeline",cjson.encode(results)) 165 | end 166 | -- output pipeline["OK","OK","Marry","Bob"] 167 | ``` 168 | 169 | 170 | ### script 171 | 172 | 参考 [script 压缩复杂请求][linkScript压缩复杂请求] 173 | 174 | ```lua 175 | local red = redis:new(); 176 | 177 | local id = 1 178 | local res, err = red:eval([[ 179 | -- 注意在 Redis 执行脚本的时候,从 KEYS/ARGV 取出来的值类型为 string 180 | local info = redis.call('get', KEYS[1]) 181 | info = cjson.decode(info) 182 | local g_id = info.gid 183 | 184 | local g_info = redis.call('get', g_id) 185 | return g_info 186 | ]], 1, id) 187 | 188 | if not res then 189 | ngx.say("failed to get the group info: ", err) 190 | return 191 | end 192 | 193 | ngx.say("script",res) 194 | ``` 195 | 196 | ## 鸣谢(Thanks) 197 | 198 | 本工具借鉴了 [lua-resty-redis/lib/resty/redis.lua][] 和 [Redis 接口的二次封装][linkRedis接口的二次封装] 的代码 199 | 200 | ## 反馈(Feedback) 201 | 202 | 如果有问题,欢迎提 [issues][] 203 | 204 | Copyright and License 205 | ===================== 206 | 207 | This module is licensed under the BSD license. 208 | 209 | Copyright (C) 2017-, by AnJia . 210 | 211 | All rights reserved. 212 | 213 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 214 | 215 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 216 | 217 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 218 | 219 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 220 | 221 | 222 | 223 | [openresty/lua-resty-redis]: https://github.com/openresty/lua-resty-redis 224 | [agentzh]: https://github.com/agentzh 225 | [README.md]: https://github.com/openresty/lua-resty-redis/blob/master/README.markdown 226 | [linkScript压缩复杂请求]: https://moonbingbing.gitbooks.io/openresty-best-practices/content/redis/script.html 227 | [openresty/lua-resty-redis#Synopsis]: https://github.com/openresty/lua-resty-redis/blob/master/README.markdown#synopsis 228 | [linkRedis接口的二次封装(发布订阅)]: https://moonbingbing.gitbooks.io/openresty-best-practices/content/redis/pub_sub_package.html 229 | [linkRedis接口的二次封装]: https://moonbingbing.gitbooks.io/openresty-best-practices/content/redis/out_package.html 230 | [lua-resty-redis/lib/resty/redis.lua]: https://github.com/openresty/lua-resty-redis/blob/master/lib/resty/redis.lua 231 | [issues]: https://github.com/anjia0532/lua-resty-redis-util/issues/new 232 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-redis-util 2 | abstract = base on openresty/lua-resty-redis,Making it easier to operate the redis 3 | author = AnJia(anjia0532@gmail.com) 4 | is_original = yes 5 | license = 2bsd 6 | lib_dir = lib 7 | doc_dir = lib 8 | repo_link = https://github.com/anjia0532/lua-resty-redis-util 9 | main_module = lib/resty/redis-util.lua 10 | -------------------------------------------------------------------------------- /lib/resty/redis-util.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Anjia (anjia0532) 2 | 3 | local redis_c = require("resty.redis") 4 | 5 | local ok, new_tab = pcall(require, "table.new") 6 | if not ok or type(new_tab) ~= "function" then 7 | new_tab = function (narr, nrec) return {} end 8 | end 9 | 10 | local _M = new_tab(0, 54) 11 | 12 | _M._VERSION = '0.07' 13 | 14 | local mt = {__index = _M} 15 | 16 | 17 | local ngx_log = ngx.log 18 | local debug = ngx.config.debug 19 | 20 | local DEBUG = ngx.DEBUG 21 | local CRIT = ngx.CRIT 22 | 23 | local MAX_PORT = 65535 24 | 25 | 26 | local host = '127.0.0.1' 27 | local port = 6379 28 | local db_index = 0 29 | local password = nil 30 | local keepalive = 60000 --60s 31 | local pool_size = 100 32 | local timeout = 3000 --3s --modify by hirryli 33 | 34 | 35 | -- if res is ngx.null or nil or type(res) is table and all value is ngx.null return true else false 36 | local function _is_null(res) 37 | if res == ngx.null or res ==nil then 38 | return true 39 | elseif type(res) == "table" then 40 | for _, v in pairs(res) do 41 | if v ~= ngx.null then 42 | return false 43 | end 44 | end 45 | -- thanks for https://github.com/anjia0532/lua-resty-redis-util/issues/3 46 | return true 47 | end 48 | return false 49 | end 50 | 51 | 52 | local function _debug_err(msg,err) 53 | if debug then 54 | ngx_log(DEBUG, msg ,err) 55 | end 56 | end 57 | 58 | -- encapsulation redis connect 59 | local function _connect_mod(self,redis) 60 | -- set timeout -- add by hirryli 61 | -- ngx.say("timeout:", timeout) 62 | if timeout then 63 | redis:set_timeout(timeout) 64 | else 65 | redis:set_timeout(3000) 66 | end 67 | 68 | local ok, err 69 | -- set redis unix socket 70 | if host:find("unix:/", 1, true) == 1 then 71 | ok, err = redis:connect(host) 72 | -- set redis host,port 73 | else 74 | ok, err = redis:connect(host, port) 75 | end 76 | if not ok or err then 77 | 78 | _debug_err("previous connection not finished,reason::",err) 79 | 80 | return nil, err 81 | end 82 | 83 | -- set auth 84 | if password then 85 | local times, err = redis:get_reused_times() 86 | 87 | if times == 0 then 88 | 89 | local ok, err = redis:auth(password) 90 | if not ok or err then 91 | _debug_err("failed to set redis password,reason::",err) 92 | return nil, err 93 | end 94 | elseif err then 95 | _debug_err( "failed to get this connect reused times,reason::",err) 96 | return nil, err 97 | end 98 | end 99 | 100 | if db_index >0 then 101 | local ok, err = redis:select(db_index) 102 | if not ok or err then 103 | _debug_err( "failed to select redis databse index to" , db_index , ",reason::",err) 104 | return nil, err 105 | end 106 | end 107 | 108 | return redis, nil 109 | end 110 | 111 | 112 | local function _init_connect() 113 | -- init redis 114 | local redis, err = redis_c:new() 115 | if not redis then 116 | _debug_err( "failed to init redis,reason::",err) 117 | return nil, err 118 | end 119 | 120 | -- get connect 121 | local ok, err = _connect_mod(self,redis) 122 | if not ok or err then 123 | _debug_err( "failed to create redis connection,reason::",err) 124 | return nil, err 125 | end 126 | return redis,nil 127 | end 128 | 129 | -- put it into the connection pool of size (default 100), with max idle time (default 60s) 130 | local function _set_keepalive_mod(self,redis ) 131 | return redis:set_keepalive(keepalive, pool_size) 132 | end 133 | 134 | -- encapsulation subscribe 135 | function _M.subscribe( self, channel ) 136 | 137 | -- init redis 138 | local redis, err = _init_connect() 139 | if not redis then 140 | _debug_err( "failed to init redis,reason::",err) 141 | return nil, err 142 | end 143 | 144 | -- sub channel 145 | local res, err = redis:subscribe(channel) 146 | if not res then 147 | _debug_err("failed to subscribe channel,reason:",err) 148 | return nil, err 149 | end 150 | 151 | local function do_read_func ( do_read ) 152 | if do_read == nil or do_read == true then 153 | res, err = redis:read_reply() 154 | if not res then 155 | _debug_err("failed to read subscribe channel reply,reason:",err) 156 | return nil, err 157 | end 158 | return res 159 | end 160 | 161 | -- if do_read is false 162 | redis:unsubscribe(channel) 163 | _set_keepalive_mod(self,redis) 164 | return 165 | end 166 | 167 | return do_read_func 168 | end 169 | 170 | -- init pipeline,default cmds num is 4 171 | function _M.init_pipeline(self, n) 172 | self._reqs = new_tab(n or 4, 0) 173 | end 174 | 175 | -- cancel pipeline 176 | function _M.cancel_pipeline(self) 177 | self._reqs = nil 178 | end 179 | 180 | -- commit pipeline 181 | function _M.commit_pipeline(self) 182 | -- get cache cmds 183 | local _reqs = rawget(self, "_reqs") 184 | if not _reqs then 185 | _debug_err("failed to commit pipeline,reason:no pipeline") 186 | return nil, "no pipeline" 187 | end 188 | 189 | self._reqs = nil 190 | 191 | -- init redis 192 | local redis, err = _init_connect() 193 | if not redis then 194 | _debug_err( "failed to init redis,reason::",err) 195 | return nil, err 196 | end 197 | 198 | redis:init_pipeline() 199 | 200 | --redis command like set/get ... 201 | for _, vals in ipairs(_reqs) do 202 | -- vals[1] is redis cmd 203 | local fun = redis[vals[1]] 204 | -- get params without cmd 205 | table.remove(vals , 1) 206 | -- invoke redis cmd 207 | fun(redis, unpack(vals)) 208 | end 209 | 210 | -- commit pipeline 211 | local results, err = redis:commit_pipeline() 212 | if not results or err then 213 | _debug_err( "failed to commit pipeline,reason:",err) 214 | return {}, err 215 | end 216 | 217 | -- check null 218 | if _is_null(results) then 219 | results = {} 220 | ngx.log(ngx.WARN, "redis result is null") 221 | end 222 | 223 | -- put it into the connection pool 224 | _set_keepalive_mod(self,redis) 225 | 226 | -- if null set default value nil 227 | for i,value in ipairs(results) do 228 | if _is_null(value) then 229 | results[i] = nil 230 | end 231 | end 232 | 233 | return results, err 234 | end 235 | 236 | -- common method 237 | local function do_command(self, cmd, ...) 238 | 239 | -- pipeline reqs 240 | local _reqs = rawget(self, "_reqs") 241 | if _reqs then 242 | -- append reqs 243 | _reqs[#_reqs + 1] = {cmd,...} 244 | return 245 | end 246 | 247 | -- init redis 248 | local redis, err = _init_connect() 249 | if not redis then 250 | _debug_err( "failed to init redis,reason::",err) 251 | return nil, err 252 | end 253 | 254 | -- exec redis cmd 255 | local method = redis[cmd] 256 | local result, err = method(redis, ...) 257 | if not result or err then 258 | return nil, err 259 | end 260 | 261 | -- check null 262 | if _is_null(result) then 263 | result = nil 264 | end 265 | 266 | -- put it into the connection pool 267 | local ok, err = _set_keepalive_mod(self,redis) 268 | if not ok or err then 269 | return nil, err 270 | end 271 | 272 | return result, nil 273 | end 274 | 275 | -- init options 276 | function _M.new(self, opts) 277 | opts = opts or {} -- fixed https://github.com/anjia0532/lua-resty-redis-util/issues/4 278 | if (type(opts) ~= "table") then 279 | return nil, "user_config must be a table" 280 | end 281 | 282 | for k, v in pairs(opts) do 283 | if k == "host" then 284 | if type(v) ~= "string" then 285 | return nil, '"host" must be a string' 286 | end 287 | host = v 288 | elseif k == "port" then 289 | if type(v) ~= "number" then 290 | return nil, '"port" must be a number' 291 | end 292 | if v < 0 or v > MAX_PORT then 293 | return nil, ('"port" out of range 0~%s'):format(MAX_PORT) 294 | end 295 | port = v 296 | elseif k == "password" then 297 | if type(v) ~= "string" then 298 | return nil, '"password" must be a string' 299 | end 300 | password = v 301 | elseif k == "db_index" then 302 | if type(v) ~= "number" then 303 | return nil, '"db_index" must be a number' 304 | end 305 | if v < 0 then 306 | return nil, '"db_index" must be >= 0' 307 | end 308 | db_index = v 309 | elseif k == "timeout" then 310 | if type(v) ~= "number" or v < 0 then 311 | return nil, 'invalid "timeout"' 312 | end 313 | timeout = v 314 | elseif k == "keepalive" then 315 | if type(v) ~= "number" or v < 0 then 316 | return nil, 'invalid "keepalive"' 317 | end 318 | keepalive = v 319 | elseif k == "pool_size" then 320 | if type(v) ~= "number" or v < 0 then 321 | return nil, 'invalid "pool_size"' 322 | end 323 | pool_size = v 324 | end 325 | end 326 | 327 | if not (host and port) then 328 | return nil, "no redis server configured. \"host\"/\"port\" is required." 329 | end 330 | 331 | return setmetatable({},mt) 332 | end 333 | 334 | -- dynamic cmd 335 | setmetatable(_M, {__index = function(self, cmd) 336 | local method = 337 | function (self, ...) 338 | return do_command(self, cmd, ...) 339 | end 340 | 341 | -- cache the lazily generated method in our 342 | -- module table 343 | _M[cmd] = method 344 | return method 345 | end}) 346 | 347 | return _M 348 | -------------------------------------------------------------------------------- /rockspec/lua-resty-redis-util-0.07-1.rockspec: -------------------------------------------------------------------------------- 1 | rockspec_format = "3.0" 2 | package = "lua-resty-redis-util" 3 | version = "0.07-1" 4 | supported_platforms = {"linux", "macosx"} 5 | 6 | source = { 7 | url = "git://github.com/anjia0532/lua-resty-redis-util", 8 | tag = "v0.07" 9 | } 10 | 11 | description = { 12 | summary = "openresty/lua-resty-redis 封装工具类", 13 | detailed = [[ 14 | 本项目是基于openresty/lua-resty-redis 是章亦春(agentzh)开发的openresty中的操作redis的库。进行二次封装的工具库。核心功能还是由openresty/lua-resty-redis完成的。 15 | ]], 16 | homepage = "https://github.com/anjia0532/lua-resty-redis-util", 17 | license = "BSD", 18 | labels = { "openresty" , "redis"} 19 | } 20 | dependencies = { 21 | } 22 | 23 | build = { 24 | type = "builtin", 25 | modules = { 26 | ["resty.redis-util"] = "lib/resty/redis-util.lua", 27 | } 28 | } 29 | --------------------------------------------------------------------------------