OpenResty在init_worker_by_lua中初始化MySQL报错如何解决
问题根因
你遇到的报错是因为init_worker_by_lua*上下文默认禁用了cosocket(即ngx.socket.tcp相关API),而lua-resty-mysql、lua-resty-redis的底层都依赖cosocket实现网络通信,所以直接在这个阶段调用new/connect方法就会触发API禁用的错误。
可行解决方案
方案1:基于ngx.timer的延迟初始化(预初始化场景推荐)
ngx.timer的回调上下文支持cosocket调用,你可以在init_worker阶段启动一个0延迟的定时器,把数据库连接初始化逻辑放到定时器回调中执行。
改后的db.lua示例代码:
local mysql = require("resty.mysql") local redis = require("resty.redis") -- 数据库初始化函数 local function init_db() local db, err = mysql:new() if not db then ngx.log(ngx.ERR, "failed to instantiate mysql: ", err) return end db:set_timeout(3000) -- 设置连接超时时间 local ok, err, errcode, sqlstate = db:connect{ host = "127.0.0.1", port = 3306, database = "employee", user = "root", password = "password", charset = "utf8", max_packet_size = 1024 * 1024, } if not ok then ngx.log(ngx.ERR, "failed to connect mysql: ", err, " errcode: ", errcode) return end -- 连接成功后放入连接池复用 local ok, err = db:set_keepalive(10000, 100) if not ok then ngx.log(ngx.ERR, "failed to set keepalive: ", err) return end -- Redis初始化逻辑和MySQL一致,此处省略 end -- 启动0延迟定时器,worker进程启动后立刻执行初始化 local ok, err = ngx.timer.at(0, init_db) if not ok then ngx.log(ngx.ERR, "failed to create init timer: ", err) end
注意:
init_worker阶段不能调用ngx.say这类请求输出API,所有日志输出必须用ngx.log写入错误日志。
方案2:请求侧懒加载(大多数场景推荐,稳定性更高)
不在服务启动阶段做数据库初始化,而是在第一次请求触发的时候初始化连接,之后将连接状态缓存起来,后续请求直接复用。
你可以把连接逻辑封装为独立工具模块db_utils.lua:
local mysql = require("resty.mysql") local _M = {} function _M.get_db_conn() -- 用模块级变量缓存连接,也可以用ngx.shared.DICT做跨请求缓存 if _M.db_conn then return _M.db_conn end local db, err = mysql:new() -- 连接逻辑和方案1一致,连接成功后赋值给_M.db_conn即可 -- 省略重复代码 return db end return _M
之后在access_by_lua或者content_by_lua阶段调用require("db_utils").get_db_conn()获取连接即可。
注意事项
- 多worker进程下,每个worker都会独立执行
init_worker的逻辑,每个worker的连接都是独立的,符合OpenResty的进程模型 - 数据库连接用完后要记得调用
set_keepalive放到连接池复用,避免每次请求都新建连接造成性能损耗
内容的提问来源于stack exchange,提问作者rahul garg
相关产品推荐
相关产品推荐

