一、共享内存锁机制:高并发场景下的性能救星

在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)
    }
}

注释说明

  1. lua_shared_dict定义了一块共享内存。
  2. add操作是原子的,适合做简单的锁。
  3. 一定要设置锁超时,避免死锁。

优缺点

  • 优点:轻量级、无第三方依赖。
  • 缺点:锁粒度较粗,不适合复杂事务。

注意事项

  1. 锁超时时间要合理,太长会影响性能,太短会导致误判。
  2. 共享内存大小要预估好,避免溢出。

二、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)
    }
}

注释说明

  1. set_keepalive会将连接放入池中供后续复用。
  2. 池大小要根据实际负载调整。

优缺点

  • 优点:减少TCP握手开销,提升吞吐量。
  • 缺点:连接池管理不当可能导致内存泄漏。

注意事项

  1. 连接池大小要监控,避免占用过多资源。
  2. 超时设置要合理,避免僵尸连接。

三、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)

注释说明

  1. Lua的字符串是不可变的,频繁拼接会生成大量临时对象。
  2. table.concat是专门优化过的函数。

优缺点

  • 优点:无需修改架构,直接提升性能。
  • 缺点:需要熟悉LuaJIT的编译特性。

注意事项

  1. 避免使用LuaJIT不支持的特性(如某些debug函数)。
  2. 热点代码尽量用局部变量。

四、总结与最佳实践

  1. 共享内存锁:适合简单原子操作,但别滥用。
  2. cosocket池化:网络密集型应用的必备优化。
  3. LuaJIT编译优化:细节决定性能,好的习惯很重要。

最终建议:性能调优一定要结合监控(如ngx.log或Prometheus),避免盲目优化。