http://lua.ren/topic/135/%E5%85%B3%E4%BA%8E-openresty-%E7%9A%84%E4%B8%A4%E4%B8%89%E4%BA%8B
基础原理 Nginx 采用的是 master-worker 模型,一个 master 进程管理多个 worker 进程,基本的事件处理都是放在 woker 中,master 负责一些全局初始化,以及对 worker 的管理。
每个 woker 使用一个 LuaVM,当请求被分配到 woker 时,将在这个 LuaVM 里创建一个 coroutine。协程之间数据隔离,每个协程具有独立的全局变量 _G。
关于 LUACODECACHE 关闭 luacodecache 时,require 的处理方式是每次都强制重新加载和解析,也就是说,你对代码的任何修改的效果,都将在上传后立即体现。
开启 luacodecache 时,在同一个 LuaVM 中,模块将在首次加载并解析后被缓存,之后再次 require 将直接返回缓存的内容。换句话说,同一 worker 上的所有请求将共享已加载的模块,任意一个请求对于模块属性的修改,都将影响到同一 worker 上的其他请求。
不应使用模块级的局部变量以及模块属性,存放任何请求级的数据。否则在 luacodecache 开启时,会造成请求间相互影响和数据竞争,产生不可预知的异常状况。
关闭 luacodecache 会极大的降低性能,在生产环境中应开启 luacodecache 。
虽然开发环境中关闭 luacodecache 会有一些便利性,但我强烈建议开启 luacodecache ,与线上保持一致,以减少不必要的差异性问题和额外测试需求。
开启 luacodecache 时,可用 nginx -s reload 或 kill -HUP masterPID 方式热重载代码,无需重启 Nginx。
关于 PATH 和 CPATH OpenResty 会将它的 lib 目录加入 package.path 和 package.cpath,但你的项目目录需要自己处理。
在入口文件中,将项目目录加入 package.path 和 package.cpath 是不可取的。因为 luacodecache 开启时,package 模块是同一 worker 上所有请求共享的,如果无条件追加,package.path 和 package.cpath 将不断变长,并最终导致内存溢出。
以下是我采用的解决方案:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
local ok, app = pcall(require, "core.app")
if ok then app:run() else local rootPath = ngx.var.document_root
if not (package.path:find(rootPath)) then package.path = package.path .. ";" .. rootPath .. "/?.lua;;" end
if not (package.cpath:find(rootPath)) then package.cpath = package.cpath .. ";" .. rootPath .. "/?.so;;" end
require("core.app"):run() end
|
关于 LUA-RESTY-MYSQL 和 LUA-RESTY-REDIS 不应使用模块级的局部变量以及模块属性,存放 resty.mysql 和 resty.redis 实例。否则,在 luacodecache 开启时,同一 worker 的所有请求将共享该实例,造成数据竞争问题。建议将 resty.mysql 和 resty.redis 实例存放到 ngx.ctx 中。
不能在 require 过程中实例化 resty.mysql 和 resty.redis 实例,否则会报错。例如,模块返回一个 function,此 function 直接或间接调用实例化 resty.mysql 和 resty.redis 的代码,将会导致报错。
在首次查询时实例化是一个比较好的解决方案:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
local mysql = require("resty.mysql") local exception = require("core.exception") local dbConf = require("config.mysql") local sysConf = require("config.system")
local MySQL = {}
--- 获取连接 -- -- @return resty.mysql MySQL连接 -- @error mysql.socketFailed socket建立失败 -- @error mysql.cantConnect 无法连接数据库 -- @error mysql.queryFailed 数据查询失败 function MySQL:getClient() if ngx.ctx[MySQL] then return ngx.ctx[MySQL] end
local client, errmsg = mysql:new()
if not client then exception:raise("mysql.socketFailed", { message = errmsg }) end
client:set_timeout(3000)
local options = { user = dbConf.USER, password = dbConf.PASSWORD, database = dbConf.DATABASE }
if dbConf.SOCK then options.path = dbConf.SOCK else options.host = dbConf.HOST options.port = dbConf.PORT end
local result, errmsg, errno, sqlstate = client:connect(options)
if not result then exception:raise("mysql.cantConnect", { message = errmsg, code = errno, state = sqlstate }) end
local query = "SET NAMES " .. sysConf.DEFAULT_CHARSET local result, errmsg, errno, sqlstate = client:query(query)
if not result then exception:raise("mysql.queryFailed", { query = query, message = errmsg, code = errno, state = sqlstate }) end
ngx.ctx[MySQL] = client return ngx.ctx[MySQL] end
--- 关闭连接 function MySQL:close() if ngx.ctx[MySQL] then ngx.ctx[MySQL]:set_keepalive(0, 100) ngx.ctx[MySQL] = nil end end
--- 执行查询 -- -- 有结果数据集时返回结果数据集 -- 无数据数据集时返回查询影响,如: -- { insert_id = 0, server_status = 2, warning_count = 1, affected_rows = 32, message = nil} -- -- @param string query 查询语句 -- @return table 查询结果 -- @error mysql.queryFailed 查询失败 function MySQL:query(query) local result, errmsg, errno, sqlstate = self:getClient():query(query)
if not result then exception:raise("mysql.queryFailed", { query = query, message = errmsg, code = errno, state = sqlstate }) end
return result end
return MySQL
|
使用 setkeepalive(maxidletimeout, poolsize) 替代 close() 将启用连接池特性。set_keepalive 的意思可以理解为,保持连接,并将连接归还到连接池内。这样在下次连接时,会首先会尝试从连接池获取连接,获取不成功才会创建新的连接。在高并发下,连接池能大大的减少连接 MySQL 和 Redis 的次数,明显的提升性能。
使用模块缓存静态数据 利用 luacodecache 开启时模块会被缓存的特性,我们可以使用模块来缓存静态数据,其效率接近于将数据缓存在内存中。
存储方法:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
local exception = require("core.exception") local mysql = require("core.driver.mysql")
--- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function makeCityCache() local citys = mysql:query("SELECT * FROM `data_city` WHERE 1") local cityData = {}
for _, city in ipairs(citys) do cityData[city.id] = city end
package.loaded["cache.city"] = cityData end
|
读取方法:
None
1 2 3 4 5 6 7 8 9 10
|
--- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function getCityCache(id) local ok, cacheData = pcall(require, "cache.city")
if ok then return cacheData[id] end
return nil end
|
清理方法:
None
1 2 3 4
|
--- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function clearCityCache() package.loaded["cache.city"] = nil end
|
关于 OPENRESTY 的两三事 火星梅梅 | 5 八月, 2013 | OpenResty, 爱 Coding | 2条评论 基础原理 Nginx 采用的是 master-worker 模型,一个 master 进程管理多个 worker 进程,基本的事件处理都是放在 woker 中,master 负责一些全局初始化,以及对 worker 的管理。
每个 woker 使用一个 LuaVM,当请求被分配到 woker 时,将在这个 LuaVM 里创建一个 coroutine。协程之间数据隔离,每个协程具有独立的全局变量 _G。
关于 LUACODECACHE 关闭 luacodecache 时,require 的处理方式是每次都强制重新加载和解析,也就是说,你对代码的任何修改的效果,都将在上传后立即体现。
开启 luacodecache 时,在同一个 LuaVM 中,模块将在首次加载并解析后被缓存,之后再次 require 将直接返回缓存的内容。换句话说,同一 worker 上的所有请求将共享已加载的模块,任意一个请求对于模块属性的修改,都将影响到同一 worker 上的其他请求。
不应使用模块级的局部变量以及模块属性,存放任何请求级的数据。否则在 luacodecache 开启时,会造成请求间相互影响和数据竞争,产生不可预知的异常状况。
关闭 luacodecache 会极大的降低性能,在生产环境中应开启 luacodecache 。
虽然开发环境中关闭 luacodecache 会有一些便利性,但我强烈建议开启 luacodecache ,与线上保持一致,以减少不必要的差异性问题和额外测试需求。
开启 luacodecache 时,可用 nginx -s reload 或 kill -HUP masterPID 方式热重载代码,无需重启 Nginx。
关于 PATH 和 CPATH OpenResty 会将它的 lib 目录加入 package.path 和 package.cpath,但你的项目目录需要自己处理。
在入口文件中,将项目目录加入 package.path 和 package.cpath 是不可取的。因为 luacodecache 开启时,package 模块是同一 worker 上所有请求共享的,如果无条件追加,package.path 和 package.cpath 将不断变长,并最终导致内存溢出。
以下是我采用的解决方案:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
local ok, app = pcall(require, "core.app") if ok then app:run() else local rootPath = ngx.var.document_root if not (package.path:find(rootPath)) then package.path = package.path .. ";" .. rootPath .. "/?.lua;;" end if not (package.cpath:find(rootPath)) then package.cpath = package.cpath .. ";" .. rootPath .. "/?.so;;" end require("core.app"):run() end
|
关于 LUA-RESTY-MYSQL 和 LUA-RESTY-REDIS 不应使用模块级的局部变量以及模块属性,存放 resty.mysql 和 resty.redis 实例。否则,在 luacodecache 开启时,同一 worker 的所有请求将共享该实例,造成数据竞争问题。建议将 resty.mysql 和 resty.redis 实例存放到 ngx.ctx 中。
不能在 require 过程中实例化 resty.mysql 和 resty.redis 实例,否则会报错。例如,模块返回一个 function,此 function 直接或间接调用实例化 resty.mysql 和 resty.redis 的代码,将会导致报错。
在首次查询时实例化是一个比较好的解决方案:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
local mysql = require("resty.mysql") local exception = require("core.exception") local dbConf = require("config.mysql") local sysConf = require("config.system") local MySQL = {} --- 获取连接 -- -- @return resty.mysql MySQL连接 -- @error mysql.socketFailed socket建立失败 -- @error mysql.cantConnect 无法连接数据库 -- @error mysql.queryFailed 数据查询失败 function MySQL:getClient() if ngx.ctx[MySQL] then return ngx.ctx[MySQL] end local client, errmsg = mysql:new() if not client then exception:raise("mysql.socketFailed", { message = errmsg }) end client:set_timeout(3000) local options = { user = dbConf.USER, password = dbConf.PASSWORD, database = dbConf.DATABASE } if dbConf.SOCK then options.path = dbConf.SOCK else options.host = dbConf.HOST options.port = dbConf.PORT end local result, errmsg, errno, sqlstate = client:connect(options) if not result then exception:raise("mysql.cantConnect", { message = errmsg, code = errno, state = sqlstate }) end local query = "SET NAMES " .. sysConf.DEFAULT_CHARSET local result, errmsg, errno, sqlstate = client:query(query) if not result then exception:raise("mysql.queryFailed", { query = query, message = errmsg, code = errno, state = sqlstate }) end ngx.ctx[MySQL] = client return ngx.ctx[MySQL] end --- 关闭连接 function MySQL:close() if ngx.ctx[MySQL] then ngx.ctx[MySQL]:set_keepalive(0, 100) ngx.ctx[MySQL] = nil end end --- 执行查询 -- -- 有结果数据集时返回结果数据集 -- 无数据数据集时返回查询影响,如: -- { insert_id = 0, server_status = 2, warning_count = 1, affected_rows = 32, message = nil} -- -- @param string query 查询语句 -- @return table 查询结果 -- @error mysql.queryFailed 查询失败 function MySQL:query(query) local result, errmsg, errno, sqlstate = self:getClient():query(query) if not result then exception:raise("mysql.queryFailed", { query = query, message = errmsg, code = errno, state = sqlstate }) end return result end return MySQL
|
使用 setkeepalive(maxidletimeout, poolsize) 替代 close() 将启用连接池特性。set_keepalive 的意思可以理解为,保持连接,并将连接归还到连接池内。这样在下次连接时,会首先会尝试从连接池获取连接,获取不成功才会创建新的连接。在高并发下,连接池能大大的减少连接 MySQL 和 Redis 的次数,明显的提升性能。
使用模块缓存静态数据 利用 luacodecache 开启时模块会被缓存的特性,我们可以使用模块来缓存静态数据,其效率接近于将数据缓存在内存中。
存储方法:
None
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
local exception = require("core.exception") local mysql = require("core.driver.mysql") --- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function makeCityCache() local citys = mysql:query("SELECT * FROM `data_city` WHERE 1") local cityData = {} for _, city in ipairs(citys) do cityData[city.id] = city end package.loaded["cache.city"] = cityData end
|
读取方法:
None
1 2 3 4 5 6 7 8 9 10
|
--- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function getCityCache(id) local ok, cacheData = pcall(require, "cache.city") if ok then return cacheData[id] end return nil end
|
清理方法:
None
1 2 3 4
|
--- 实现示例,可以根据项目情况,完善后封装在数据查询层 local function clearCityCache() package.loaded["cache.city"] = nil end
|
数据存储 _G
请求级 table 变量,生命周期为本次请求,可存储请求级任意 Lua 数据。
NGX.CTX
请求级 table 变量,生命周期为本次请求,可存储请求级任意 Lua 数据。
NGX.SHARED.DICT
全局级 key-value 字典,使用共享内存实现,实现了读写锁,所有请求均可安全读写。 value 只能为布尔值、数字和字符串。Reload Nginx 时不会受影响,只有当 Nginx 被关闭时才会丢失。
模块属性和模块级局部变量
worker 级变量,同一 worker 的所有请求共享,没有读写锁,多个请求同时写入时不安全。
多谢原作者的分享: http://zivn.me/?p=157