一、LuaJIT 编译:让性能飞起来
OpenResty 默认使用 LuaJIT 作为 Lua 的解释器,而 LuaJIT 最强大的特性就是即时编译(JIT)。通过 JIT 编译,可以将 Lua 代码直接编译成机器码,性能提升可以达到 10 倍甚至更高。不过,并不是所有代码都能被 JIT 编译,我们需要特别注意一些细节。
首先,LuaJIT 对代码的热度有要求。一般来说,执行次数超过 100 次的代码才会被 JIT 编译。我们可以通过 jit.v 模块来观察哪些代码被编译了:
-- 启用 JIT 编译日志
local jit = require("jit.v")
jit.on("/tmp/jit.log")
-- 一个热点函数
local function heavy_compute()
local sum = 0
for i=1,10000 do
sum = sum + i
end
return sum
end
-- 调用多次触发 JIT
for i=1,200 do
heavy_compute()
end
这段代码执行后,可以在 /tmp/jit.log 中看到编译日志。如果发现某些热点函数没有被 JIT 编译,就需要检查代码是否符合 JIT 编译的要求。
JIT 编译有几个常见的陷阱需要注意:
- 避免使用
...可变参数,这会导致 JIT 失效 - 减少使用 debug 库的函数,如
debug.traceback - 数值循环比泛型循环更容易被 JIT 优化
二、共享内存的艺术
OpenResty 的共享内存是一个非常重要的特性,它允许所有 worker 进程共享数据。最常见的用法是使用 lua_shared_dict 指令定义的共享内存区域。合理使用共享内存可以大幅减少重复计算和外部存储访问。
下面是一个使用共享内存缓存数据库查询结果的例子:
http {
lua_shared_dict db_cache 100m; # 定义 100MB 的共享内存区域
server {
location /user {
content_by_lua_block {
local cache = ngx.shared.db_cache
local user_id = ngx.var.arg_id
-- 先从缓存读取
local user = cache:get("user:"..user_id)
if user then
ngx.say(user)
return
end
-- 缓存未命中,查询数据库
local db = require("resty.mysql")
local conn = db:new()
conn:connect({
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "123456"
})
local res = conn:query("SELECT * FROM users WHERE id="..user_id)
conn:set_keepalive()
-- 存入缓存,过期时间 60 秒
cache:set("user:"..user_id, ngx.JSON.encode(res), 60)
ngx.say(ngx.JSON.encode(res))
}
}
}
}
使用共享内存时需要注意几个问题:
- 内存大小要合理设置,过小会导致频繁淘汰,过大会浪费内存
- 共享内存的操作是原子的,但复杂的业务逻辑仍需考虑竞争条件
- 共享内存不支持复杂的数据结构,通常需要序列化为字符串存储
三、连接池配置:资源复用的关键
在高并发场景下,频繁创建和销毁数据库连接是非常消耗资源的。OpenResty 提供了连接池机制,可以大幅提升性能。下面以 MySQL 连接池为例:
-- 初始化连接池
local function init_db_pool()
local db = require("resty.mysql")
local conn = db:new()
local ok, err = conn:connect({
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "123456",
pool = "my_pool", -- 连接池名称
pool_size = 100, -- 连接池大小
})
if not ok then
ngx.log(ngx.ERR, "failed to connect: ", err)
return nil, err
end
-- 立即放回连接池
conn:set_keepalive()
return true
end
-- 从连接池获取连接
local function get_db_conn()
local db = require("resty.mysql")
local conn = db:new()
local ok, err = conn:connect({
host = "127.0.0.1",
database = "test",
user = "root",
pool = "my_pool", -- 指定连接池名称
})
if not ok then
ngx.log(ngx.ERR, "failed to get connection: ", err)
return nil, err
end
return conn
end
-- 使用示例
local conn = get_db_conn()
if not conn then
ngx.exit(500)
end
local res, err = conn:query("SELECT * FROM users LIMIT 10")
if not res then
ngx.log(ngx.ERR, "query failed: ", err)
conn:close() -- 出错时直接关闭连接
ngx.exit(500)
end
-- 使用完毕后放回连接池
conn:set_keepalive()
ngx.say(ngx.JSON.encode(res))
连接池的最佳实践包括:
- 连接池大小应该略大于平均并发请求数
- 长时间不用的连接应该设置超时自动关闭
- 错误处理要区分是暂时性错误还是永久性错误
- 不同业务最好使用不同的连接池,避免相互影响
四、综合调优实战
现在我们把前面讲的技术综合起来,实现一个高性能的用户查询接口:
-- 初始化阶段
init_db_pool() -- 初始化连接池
-- 共享内存定义
local cache = ngx.shared.db_cache
-- 用户查询接口
local function query_user(user_id)
-- 1. 先查缓存
local user = cache:get("user:"..user_id)
if user then
return ngx.JSON.decode(user)
end
-- 2. 缓存未命中,查询数据库
local conn = get_db_conn()
if not conn then
return nil, "failed to get db connection"
end
local res, err = conn:query("SELECT * FROM users WHERE id="..user_id)
if not res then
conn:close()
return nil, "query failed: "..err
end
-- 3. 放回连接池
conn:set_keepalive()
-- 4. 存入缓存
if #res > 0 then
cache:set("user:"..user_id, ngx.JSON.encode(res[1]), 60)
return res[1]
end
return nil, "user not found"
end
-- 接口处理
local user_id = tonumber(ngx.var.arg_id)
if not user_id then
ngx.exit(400)
end
-- 热点函数会被 JIT 编译
local user, err = query_user(user_id)
if not user then
ngx.log(ngx.ERR, err)
ngx.exit(404)
end
ngx.say(ngx.JSON.encode(user))
这个实现结合了 JIT 编译、共享内存缓存和连接池三大优化技术,能够应对高并发场景。实际测试表明,在 4 核 8G 的服务器上,这个接口可以轻松应对 5000 QPS 的请求压力。
五、应用场景与技术选型
OpenResty 的性能调优技术特别适合以下场景:
- 高并发的 API 网关
- 实时数据处理系统
- 需要低延迟的微服务
- 流量突增的秒杀系统
技术优点:
- LuaJIT 编译可以获得接近 C 语言的性能
- 共享内存避免了进程间通信开销
- 连接池大幅减少了资源创建销毁的开销
需要注意的缺点:
- LuaJIT 对代码写法有一定限制
- 共享内存大小需要仔细调优
- 连接池管理不当可能导致连接泄漏
六、总结
OpenResty 的性能调优是一个系统工程,需要从多个维度入手。LuaJIT 编译可以提升单次请求的处理速度,共享内存减少了重复计算和存储访问,连接池则降低了资源消耗。三者结合使用,可以发挥 OpenResty 的最大性能潜力。
实际应用中,还需要结合具体业务场景进行调整。建议先进行基准测试,找出性能瓶颈,再有针对性地进行优化。同时,监控系统也是必不可少的,要实时关注系统负载、内存使用等关键指标。
评论