一、OpenResty性能问题的常见表现
我们先来看看OpenResty在什么情况下会出现性能问题。最常见的就是高并发时响应变慢,CPU占用率飙升,甚至出现请求超时的情况。比如下面这个典型的Nginx错误日志:
2023/03/15 10:23:45 [error] 12345#0: *56789 upstream timed out (110: Connection timed out) while reading response header from upstream
这种情况往往发生在Lua脚本处理时间过长,或者后端服务响应不及时的时候。我见过一个电商网站在大促时,因为商品详情页的Lua脚本里有复杂的计算逻辑,导致整个OpenResty实例的CPU直接飙到100%。
二、性能调优的核心思路
调优的核心思路其实很简单:找到瓶颈,然后解决它。但难就难在如何准确找到瓶颈点。我总结了一个"三板斧"方法:
- 先用工具测量(比如wrk、ab、或者OpenResty自用的工具)
- 分析测量结果(看是CPU瓶颈、内存瓶颈还是I/O瓶颈)
- 针对性优化(改代码、调配置或者加缓存)
举个实际的例子,我们有个API服务原来QPS只能到2000,经过下面这样的优化后提升到了8000:
-- 优化前的代码
location /api {
content_by_lua_block {
local res = ngx.location.capture("/internal-process")
ngx.say(res.body)
}
}
-- 优化后的代码
location /api {
content_by_lua_block {
local redis = require "resty.redis"
local red = redis:new()
-- 先查Redis缓存
local cached = red:get("cache_key")
if cached then
ngx.say(cached)
return
end
-- 缓存没有才走内部处理
local res = ngx.location.capture("/internal-process")
red:set("cache_key", res.body, "EX", 60) -- 缓存60秒
ngx.say(res.body)
}
}
这个例子展示了最典型的优化手段:加缓存。但要注意,缓存虽好,也要考虑缓存一致性问题。
三、关键配置参数调优
OpenResty的性能很大程度上取决于Nginx的配置参数。下面这些参数特别关键:
- worker_processes:应该设置为CPU核心数
- worker_connections:每个worker能处理的连接数
- keepalive_timeout:长连接保持时间
- lua_code_cache:开发时可以关,生产环境必须开
这里有个完整的优化配置示例:
worker_processes auto; # 自动根据CPU核心数设置
worker_rlimit_nofile 102400; # 文件描述符限制
events {
worker_connections 4096; # 每个worker的连接数
use epoll; # Linux下性能最好的事件模型
}
http {
lua_code_cache on; # 必须开启代码缓存
lua_shared_dict my_cache 128m; # 共享内存缓存
keepalive_timeout 65; # 长连接超时
keepalive_requests 10000; # 单个长连接最大请求数
# 其他优化配置...
}
特别提醒:lua_code_cache这个参数,我见过太多人在生产环境忘记打开了,结果性能差得离谱。
四、Lua代码层面的优化技巧
Lua代码的写法对性能影响巨大。这里分享几个实战经验:
- 避免在热路径上创建临时表
- 多用local变量
- 谨慎使用字符串拼接
- 合理使用ngx.timer.at做异步处理
看个实际的例子:
-- 不好的写法
function process_request()
local headers = ngx.req.get_headers() -- 每次调用都创建新表
-- 处理逻辑...
end
-- 好的写法
local header_mt = { __index = function(t, k) return ngx.req.get_headers()[k] end }
function process_request()
local headers = setmetatable({}, header_mt) -- 轻量级的代理表
-- 处理逻辑...
end
再来看个字符串处理的例子:
-- 低效的字符串拼接
local result = ""
for i = 1, 10000 do
result = result .. "data" .. i -- 每次拼接都创建新字符串
end
-- 高效的写法
local t = {}
for i = 1, 10000 do
t[#t+1] = "data" .. i
end
local result = table.concat(t) -- 一次性拼接
五、缓存策略的优化
缓存用得好,性能提升立竿见影。OpenResty提供了多种缓存机制:
- 共享字典(lua_shared_dict)
- LRU缓存(lua-resty-lrucache)
- 外部缓存(Redis/Memcached)
这里重点说说共享字典的使用技巧:
local shared_cache = ngx.shared.my_cache
-- 基础用法
shared_cache:set("key", "value", 60) -- 缓存60秒
local value = shared_cache:get("key")
-- 高级用法:防止缓存击穿
local function get_data(key)
local value = shared_cache:get(key)
if value then return value end
-- 使用锁防止缓存击穿
local lock_key = "lock:" .. key
if shared_cache:add(lock_key, true, 5) then -- 获取锁
value = fetch_data_from_db(key) -- 从数据库获取
shared_cache:set(key, value, 60)
shared_cache:delete(lock_key) -- 释放锁
else
-- 没拿到锁,短暂等待后重试
ngx.sleep(0.1)
return get_data(key)
end
return value
end
六、连接池的正确使用
数据库和Redis连接池的配置对性能影响很大。看个Redis连接池的示例:
local redis = require "resty.redis"
local red = redis:new()
-- 设置连接超时和连接池大小
red:set_timeouts(100, 600, 200) -- 连接/发送/读取超时(毫秒)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("failed to connect: ", err)
return
end
-- 使用完毕后放回连接池
local ok, err = red:set_keepalive(10000, 100) -- 连接池大小100,超时10秒
if not ok then
ngx.say("failed to set keepalive: ", err)
return
end
常见错误是连接池设置过小,导致频繁创建新连接;或者设置过大,浪费内存。
七、实战案例分析
最后分享一个真实案例。某金融公司风控系统,原QPS只有500左右,经过以下优化提升到3000+:
- 用ngx.balancer实现动态负载均衡
- 对频繁访问的风控规则加了二级缓存(内存+Redis)
- 优化了Lua代码中的JSON处理(改用cjson.safe)
- 调整了worker_processes和worker_connections
关键优化代码片段:
-- 优化后的JSON处理
local cjson = require "cjson.safe"
local rule_cache = ngx.shared.rule_cache
local function get_rule(rule_id)
-- 先从内存缓存查
local rule = rule_cache:get(rule_id)
if rule then
return cjson.decode(rule)
end
-- 内存没有查Redis
local redis = require "resty.redis"
local red = redis:new()
rule = red:get("rule:" .. rule_id)
if rule then
rule_cache:set(rule_id, rule, 60) -- 缓存到内存
return cjson.decode(rule)
end
-- 都没有才查数据库
rule = query_rule_from_db(rule_id)
local rule_json = cjson.encode(rule)
red:set("rule:" .. rule_id, rule_json, "EX", 3600) -- Redis缓存1小时
rule_cache:set(rule_id, rule_json, 60) -- 内存缓存1分钟
return rule
end
八、总结与建议
OpenResty性能调优是个系统工程,我的建议是:
- 先测量,再优化,不要凭感觉
- 从大到小:先调架构,再调配置,最后调代码
- 缓存是银弹,但要处理好一致性问题
- 连接池大小要适中,太大太小都不好
- Lua代码要避免常见性能陷阱
记住,没有放之四海而皆准的最优配置,一定要根据实际业务场景和负载特点来调整。
评论