一、共享内存锁机制:高并发场景下的性能救星
在OpenResty中,共享内存是多Worker进程间通信的关键。但多个Worker同时读写共享内存时,就会遇到经典的并发问题。这时候,lua_shared_dict和锁机制就派上用场了。
应用场景
比如秒杀系统中库存的扣减,或者全局计数器的维护。
技术实现示例(技术栈:OpenResty + Lua)
-- 定义一个共享内存区域,大小10MB
lua_shared_dict shared_data 10m;
location /counter {
content_by_lua_block {
local shared_data = ngx.shared.shared_data
-- 获取锁(通过递增操作模拟原子锁)
local key = "counter_lock"
local timeout = 5 -- 锁超时时间5秒
local elapsed, err = shared_data:add(key, 1, timeout)
if not elapsed then
if err == "exists" then
ngx.say("操作太频繁,请稍后再试")
return
end
ngx.say("获取锁失败: ", err)
return
end
-- 临界区操作:安全的计数器递增
local counter = shared_data:get("global_counter") or 0
counter = counter + 1
shared_data:set("global_counter", counter)
-- 释放锁(通过删除key)
shared_data:delete(key)
ngx.say("当前计数器值: ", counter)
}
}
注释说明:
lua_shared_dict定义了一块共享内存。add操作是原子的,适合做简单的锁。- 一定要设置锁超时,避免死锁。
优缺点
- 优点:轻量级、无第三方依赖。
- 缺点:锁粒度较粗,不适合复杂事务。
注意事项
- 锁超时时间要合理,太长会影响性能,太短会导致误判。
- 共享内存大小要预估好,避免溢出。
二、cosocket池化:网络请求的性能加速器
OpenResty的cosocket(如ngx.socket.tcp)是非阻塞的,但频繁创建和销毁连接会带来开销。池化技术可以显著提升性能。
应用场景
适用于高频调用的外部API(如支付网关、数据库查询)。
技术实现示例(技术栈:OpenResty + Lua)
location /api {
content_by_lua_block {
-- 从连接池获取cosocket对象
local sock, err = ngx.socket.tcp()
if not sock then
ngx.say("创建socket失败: ", err)
return
end
-- 设置连接超时
sock:settimeout(1000) -- 1秒
-- 尝试从池中复用连接
local ok, err = sock:connect("backend.example.com", 80)
if not ok then
ngx.say("连接失败: ", err)
return
end
-- 发送请求
local bytes, err = sock:send("GET /data HTTP/1.1\r\nHost: backend.example.com\r\n\r\n")
if not bytes then
ngx.say("发送失败: ", err)
return
end
-- 读取响应
local data, err = sock:receive("*a")
if not data then
ngx.say("读取失败: ", err)
return
end
-- 将连接放回池中(通过set_keepalive)
local ok, err = sock:set_keepalive(5000, 10) -- 空闲5秒,池大小10
if not ok then
ngx.say("池化失败: ", err)
end
ngx.say("响应数据: ", data)
}
}
注释说明:
set_keepalive会将连接放入池中供后续复用。- 池大小要根据实际负载调整。
优缺点
- 优点:减少TCP握手开销,提升吞吐量。
- 缺点:连接池管理不当可能导致内存泄漏。
注意事项
- 连接池大小要监控,避免占用过多资源。
- 超时设置要合理,避免僵尸连接。
三、LuaJIT编译优化:让Lua代码飞起来
LuaJIT是OpenResty的性能核心,但它的JIT编译器并非万能。合理的代码写法能显著提升性能。
应用场景
高频调用的Lua代码(如JSON解析、加密算法)。
技术实现示例(技术栈:LuaJIT)
-- 糟糕的写法:频繁拼接字符串
local function bad_concat()
local result = ""
for i = 1, 10000 do
result = result .. "data" .. i -- 每次拼接都生成新字符串
end
return result
end
-- 优化写法:使用table.concat
local function good_concat()
local parts = {}
for i = 1, 10000 do
parts[i] = "data" .. i
end
return table.concat(parts) -- 一次性拼接
end
-- 测试性能差异
local start = os.clock()
bad_concat()
ngx.say("糟糕写法耗时: ", os.clock() - start)
start = os.clock()
good_concat()
ngx.say("优化写法耗时: ", os.clock() - start)
注释说明:
- Lua的字符串是不可变的,频繁拼接会生成大量临时对象。
table.concat是专门优化过的函数。
优缺点
- 优点:无需修改架构,直接提升性能。
- 缺点:需要熟悉LuaJIT的编译特性。
注意事项
- 避免使用LuaJIT不支持的特性(如某些
debug函数)。 - 热点代码尽量用局部变量。
四、总结与最佳实践
- 共享内存锁:适合简单原子操作,但别滥用。
- cosocket池化:网络密集型应用的必备优化。
- LuaJIT编译优化:细节决定性能,好的习惯很重要。
最终建议:性能调优一定要结合监控(如ngx.log或Prometheus),避免盲目优化。
评论