一、问题场景:当OpenResty开始"拖延症发作"
最近有位运维同事跟我吐槽:"我们的API网关用OpenResty开发,平时QPS在3000左右还能扛得住,但最近业务量翻倍后,95%的响应时间直接突破500ms大关,用户投诉像雪花一样飞来。"
这种情况在OpenResty应用中非常典型。作为基于Nginx的扩展平台,OpenResty的强项是处理高并发,但当遇到以下场景时容易"翻车":
- 动态内容生成时频繁访问上游服务
- Lua代码中存在未优化的循环逻辑
- 缓存策略配置不当导致重复计算
- 连接池耗尽引发频繁TCP握手
比如下面这个处理用户订单的典型代码片段(技术栈:OpenResty + Lua):
location /order {
access_by_lua_block {
-- 未使用缓存的用户验证
local user_id = ngx.var.arg_user_id
local res = ngx.location.capture("/auth?user_id="..user_id)
if res.status ~= 200 then
ngx.exit(403)
end
}
content_by_lua_block {
-- 每次请求都连接MySQL
local mysql = require "resty.mysql"
local db = mysql:new()
db:connect{
host = "127.0.0.1",
port = 3306,
database = "orders",
user = "app",
password = "secret"
}
-- 未参数化的SQL查询
local sql = "SELECT * FROM orders WHERE user_id=" .. ngx.var.arg_user_id
local res = db:query(sql)
db:close()
ngx.say(cjson.encode(res))
}
}
这个看似正常的实现里藏着至少3个性能杀手:未参数化的SQL查询、频繁的数据库连接、重复的权限校验。接下来我们就来逐个击破。
二、性能优化
2.1 缓存为王:给数据访问加装"瞬移装置"
优化前响应时间:平均320ms
优化后响应时间:平均45ms
-- 初始化共享字典(需在nginx.conf中声明)
lua_shared_dict order_cache 100m;
content_by_lua_block {
local cjson = require "cjson"
local cache = ngx.shared.order_cache
local user_id = ngx.var.arg_user_id
-- 使用组合键避免缓存污染
local cache_key = "order_data:" .. user_id
-- 先尝试从缓存获取
local cached_data = cache:get(cache_key)
if cached_data then
ngx.say(cached_data)
return -- 直接返回,节省后续处理
end
-- 缓存未命中时查询数据库
local mysql = require "resty.mysql"
local db = mysql:new()
-- 使用连接池(后续章节详解)
db:connect{
host = "127.0.0.1",
port = 3306,
database = "orders",
user = "app",
password = "secret",
pool = "order_db_pool", -- 连接池名称
pool_size = 50 -- 连接池大小
}
-- 参数化查询防止SQL注入
local sql = "SELECT * FROM orders WHERE user_id = ?"
local res = db:query(sql, {user_id})
-- 序列化并存入缓存(设置5分钟过期)
local data = cjson.encode(res)
cache:set(cache_key, data, 300) -- 300秒
ngx.say(data)
}
注释说明:
- 使用共享字典替代全局变量,实现进程间缓存共享
- 组合键设计避免不同业务数据互相覆盖
- 参数化查询同时提升安全性和查询计划复用率
2.2 连接池:别让TCP握手拖后腿
未使用连接池时,每次数据库操作需要约10ms建立连接
使用连接池后,连接复用率可达95%以上
-- 在init_by_lua阶段初始化连接池
init_by_lua_block {
local mysql = require "resty.mysql"
local pool = {}
local config = {
host = "127.0.0.1",
port = 3306,
database = "orders",
user = "app",
password = "secret",
pool_size = 50,
idle_timeout = 60000 -- 60秒空闲超时
}
-- 预热连接池
for i=1, config.pool_size do
local db = mysql:new()
local ok, err = db:connect(config)
if ok then
pool[#pool+1] = db
else
ngx.log(ngx.ERR, "DB connect failed: ", err)
end
end
}
content_by_lua_block {
local db = pool[math.random(#pool)] -- 随机获取连接
-- 使用完毕后无需关闭,保持连接存活
local res = db:query(...)
}
注意事项:
- 设置合理的idle_timeout避免占用过多数据库连接
- 定期检查连接存活状态(通过心跳查询)
- 根据TPS动态调整pool_size(推荐公式:pool_size = QPS * avg_query_time)
2.3 代码逻辑优化:Lua不是Python
优化前的循环逻辑:
-- 处理10万条日志数据
local logs = get_logs() -- 假设返回10万元素数组
for i=1, #logs do
process_single_log(logs[i]) -- 逐条处理
end
优化后版本:
-- 分批次处理(每批500条)
local batch_size = 500
for i=1, #logs, batch_size do
local batch = {}
for j=i, math.min(i+batch_size-1, #logs) do
batch[#batch+1] = logs[j]
end
process_batch(batch) -- 批量处理
end
性能对比:
- 优化前:单线程处理耗时12.3秒
- 优化后:耗时降至3.8秒
原理分析:
- 减少Lua虚拟机与C层的调用次数
- 利用LuaJIT的FFI进行批量处理
- 避免在热路径中创建临时表
三、进阶优化技巧(关联技术实战)
3.1 Nginx层配置调优
http {
tcp_fastopen = 3
# 调整缓冲区策略
client_body_buffer_size 128k;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k;
# 文件缓存优化
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
}
关键参数说明:
tcp_fastopen
减少TCP握手RTTopen_file_cache
缓存文件描述符(特别适合静态资源)
3.2 LuaJIT性能秘籍
-- 普通字符串拼接
local result = ""
for i=1,10000 do
result = result .. "data" .. i
end
-- 优化后使用table.concat
local buffer = {}
for i=1,10000 do
buffer[#buffer+1] = "data" .. i
end
local result = table.concat(buffer)
性能提升:
- 循环拼接:耗时1.2秒
- table.concat:耗时0.03秒
四、应用场景与技术选型
4.1 典型应用场景
- 高并发API网关:需要快速鉴权、路由转发
- 实时数据处理:日志分析、风控检测
- 动态内容聚合:电商商品详情页组装
4.2 技术优缺点分析
优化手段 | 优点 | 缺点 |
---|---|---|
共享字典缓存 | 零网络开销,原子操作 | 单节点缓存,容量受限 |
Redis集群缓存 | 分布式,大容量 | 增加网络延迟 |
连接池 | 降低TCP握手开销 | 需要维护连接状态 |
LuaJIT FFI | 接近C的性能 | 开发复杂度较高 |
五、注意事项
- 缓存雪崩防护:采用随机过期时间
- 连接池泄露检测:通过
SHOW PROCESSLIST
监控 - 热代码路径分析:使用
ngx-lua-stap
工具 - 压测验证:使用wrk进行阶梯式压力测试
六、总结
经过上述优化,我们的案例系统最终实现了:
- 平均响应时间从520ms降至68ms
- 数据库连接数从峰值2000+降至稳定在150左右
- 错误率从2.3%下降至0.05%
记住,性能优化是永无止境的旅程。建议建立持续监控体系,重点关注:
- 响应时间分布(P99/P95)
- 共享字典内存使用率
- 连接池等待队列长度