常用 Lua 开发库 1-redis、mysql、http 客户端
http://wiki.jikexueyuan.com/project/nginx-lua/introduction.html 对于开发来说需要有好的生态开发库来辅助我们快速开发,而 Lua 中也有大多数我们需要的第三方开发库如 Redis、Memcached、Mysql、Http 客户端、JSON、模板引擎等。一些常见的 Lua 库可以在 github 上搜索,https://github.com/search?utf8=%E2%9C%93&q=lua+resty。 Redis 客户端lua-resty-redis 是为基于 cosocket API 的 ngx_lua 提供的 Lua redis 客户端,通过它可以完成 Redis 的操作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https://github.com/openresty/lua-resty-redis。 在测试之前请启动 Redis 实例:
基本操作编辑 test_redis_baisc.lua Java 代码 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") --创建实例 local red = redis:new() --设置超时(毫秒) red:set_timeout(1000) --建立连接 local ip = "127.0.0.1" local port = 6660 local ok,err = red:connect(ip,port) if not ok then ngx.say("connect to redis error : ",err) return close_redis(red) end --调用API进行处理 ok,err = red:set("msg","hello world") if not ok then ngx.say("set msg error : ",err) return close_redis(red) end --调用API获取数据 local resp,err = red:get("msg") if not resp then ngx.say("get msg error : ",err) return close_reedis(red) end --得到的数据为空处理 if resp == ngx.null then resp = '' --比如默认值 end ngx.say("msg : ",resp) close_redis(red) 基本逻辑很简单,要注意此处判断是否为 nil,需要跟 ngx.null 比较。 example.conf 配置文件 Java 代码 location /lua_redis_basic { default_type 'text/html'; lua_code_cache on; content_by_lua_file /usr/example/lua/test_redis_basic.lua; } 访问如 连接池建立 TCP 连接需要三次握手而释放 TCP 连接需要四次握手,而这些往返时延仅需要一次,以后应该复用 TCP 连接,此时就可以考虑使用连接池,即连接池可以复用连接。 我们只需要将之前的 close_redis 函数改造为如下即可: Java 代码 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 即设置空闲连接超时时间防止连接一直占用不释放;设置连接池大小来复用连接。 此处假设调用 red:set_keepalive(),连接池大小通过 nginx.conf 中 http 部分的如下指令定义: #默认连接池大小,默认 30lua_socket_pool_size 30;#默认超时时间,默认 60slua_socket_keepalive_timeout 60s; 注意:
pipelinepipeline 即管道,可以理解为把多个命令打包然后一起发送;MTU(Maxitum Transmission Unit 最大传输单元)为二层包大小,一般为 1500 字节;而 MSS(Maximum Segment Size 最大报文分段大小)为四层包大小,其一般是 1500-20(IP 报头)-20(TCP 报头)=1460 字节;因此假设我们执行的多个 Redis 命令能在一个报文中传输的话,可以减少网络往返来提高速度。因此可以根据实际情况来选择走 pipeline 模式将多个命令打包到一个报文发送然后接受响应,而 Redis 协议也能很简单的识别和解决粘包。 修改之前的代码片段 Java 代码 red:init_pipeline() red:set("msg1","hello1") red:set("msg2","hello2") red:get("msg1") red:get("msg2") local respTable,err = red:commit_pipeline() --得到的数据为空处理 if respTable == ngx.null then respTable = {} --比如默认值 end --结果是按照执行顺序返回的一个table for i,v in ipairs(respTable) do ngx.say("msg : ",v,"<br/>") end 通过 init_pipeline() 初始化,然后通过 commit_pipieline() 打包提交 init_pipeline() 之后的Redis命令;返回结果是一个 lua table,可以通过 ipairs 循环获取结果; 配置相应 location,测试得到的结果 msg : OK msg : OK msg : hello1 msg : hello2 Redis Lua 脚本 利用 Redis 单线程特性,可以通过在 Redis 中执行 Lua 脚本实现一些原子操作。如之前的 red:get("msg") 可以通过如下两种方式实现: 1、直接 eval: Java 代码 local resp,err = red:eval("return redis.call('get',KEYS[1])",1,"msg"); 2、script load 然后 evalsha SHA1 校验和,这样可以节省脚本本身的服务器带宽:Java 代码 local sha1,err = red:script("load","return redis.call('get',KEYS[1])"); if not sha1 then ngx.say("load script error : ",err) return close_redis(red) end ngx.say("sha1 : ",sha1,"<br/>") local resp,err = red:evalsha(sha1,"msg"); 首先通过 script load 导入脚本并得到一个 sha1 校验和(仅需第一次导入即可),然后通过evalsha 执行 sha1 校验和即可,这样如果脚本很长通过这种方式可以减少带宽的消耗。 此处仅介绍了最简单的 redis lua 脚本,更复杂的请参考官方文档学习使用。 另外 Redis 集群分片算法该客户端没有提供需要自己实现,当然可以考虑直接使用类似于Twemproxy 这种中间件实现。 Memcached 客户端使用方式和本文类似,本文就不介绍了。 Mysql 客户端lua-resty-mysql 是为基于 cosocket API 的 ngx_lua 提供的 Lua Mysql 客户端,通过它可以完成 Mysql 的操作。默认安装 OpenResty 时已经自带了该模块,使用文档可参考https://github.com/openresty/lua-resty-mysql。 编辑 test_mysql.luaJava 代码 local function close_db(db) if not db then return end db:close() end local mysql = require("resty.mysql") --创建实例 local db,err = mysql:new() if not db then ngx.say("new mysql error : ",err) return end --设置超时时间(毫秒) db:set_timeout(1000) local props = { host = "127.0.0.1",port = 3306,database = "mysql",user = "root",password = "123456" } local res,err,errno,sqlstate = db:connect(props) if not res then ngx.say("connect to mysql error : ",",errno : ",sqlstate : ",sqlstate) return close_db(db) end --删除表 local drop_table_sql = "drop table if exists test" res,sqlstate = db:query(drop_table_sql) if not res then ngx.say("drop table error : ",sqlstate) return close_db(db) end --创建表 local create_table_sql = "create table test(id int primary key auto_increment,ch varchar(100))" res,sqlstate = db:query(create_table_sql) if not res then ngx.say("create table error : ",sqlstate) return close_db(db) end --插入 local insert_sql = "insert into test (ch) values('hello')" res,sqlstate = db:query(insert_sql) if not res then ngx.say("insert error : ",sqlstate) return close_db(db) end res,sqlstate = db:query(insert_sql) ngx.say("insert rows : ",res.affected_rows,id : ",res.insert_id,"<br/>") --更新 local update_sql = "update test set ch = 'hello2' where id =" .. res.insert_id res,sqlstate = db:query(update_sql) if not res then ngx.say("update error : ",sqlstate) return close_db(db) end ngx.say("update rows : ","<br/>") --查询 local select_sql = "select id,ch from test" res,sqlstate = db:query(select_sql) if not res then ngx.say("select error : ",sqlstate) return close_db(db) end for i,row in ipairs(res) do for name,value in pairs(row) do ngx.say("select row ",i," : ",name," = ",value,"<br/>") end end ngx.say("<br/>") --防止sql注入 local ch_param = ngx.req.get_uri_args()["ch"] or '' --使用ngx.quote_sql_str防止sql注入 local query_sql = "select id,ch from test where ch = " .. ngx.quote_sql_str(ch_param) res,sqlstate = db:query(query_sql) if not res then ngx.say("select error : ","<br/>") end end --删除 local delete_sql = "delete from test" res,sqlstate = db:query(delete_sql) if not res then ngx.say("delete error : ",sqlstate) return close_db(db) end ngx.say("delete rows : ","<br/>") close_db(db) 对于新增/修改/删除会返回如下格式的响应: Java 代码 { insert_id = 0,server_status = 2,warning_count = 1,affected_rows = 32,message = nil } affected_rows 表示操作影响的行数,insert_id 是在使用自增序列时产生的 id。 对于查询会返回如下格式的响应: Java 代码 { { id= 1,ch= "hello"},{ id= 2,ch= "hello2"} } null 将返回 ngx.null。 example.conf 配置文件Java 代码 location /lua_mysql { default_type 'text/html'; lua_code_cache on; content_by_lua_file /usr/example/lua/test_mysql.lua; } 访问如
|