全书目录

第五十四篇:OpenResty 完全指南 —— 可编程网关锻造厂宇宙

二、Nginx + LuaJIT 的正确心智

3 分钟 953 字 第 937 / 962 个阅读单元

#1. Nginx 不是应用线程池,它是请求流水线

很多 Web 框架的脑回路是:

text
一个请求 -> 一个线程/协程 -> 业务代码跑到底

Nginx 的脑回路不是这样。它更像:

text
一个请求 -> 进入一条分阶段流水线 -> 在不同工位被处理

每个请求会依次穿过若干阶段。你不是拿到一个“完整独占”的执行环境,而是在这些阶段里把逻辑挂进去。

所以 OpenResty 编程不是“写一个 controller”,而是“决定哪段逻辑放在哪个工位”。

#2. LuaJIT 不是附件,而是性能发动机

Lua 本来就轻,LuaJIT 更进一步:它会把热点 Lua 代码 JIT 编译,执行速度远高于很多人对“脚本语言”的直觉。

对 OpenResty 来说,LuaJIT 的价值主要有三层:

  • 让路由、鉴权、限流、头处理这类热路径逻辑足够快。
  • 让大量控制逻辑可以留在用户态 Lua,而不是一上来就写 Nginx C 模块。
  • 通过协程模型,把“等待 I/O”变成“让出执行权”,继续喂饱 Nginx 事件循环。

一句话:LuaJIT 让 Lua 在网关场景里不只是“能用”,而是“能上生产核心路径”。

#3. Worker 是隔离的,内存不是天然共享的

这是 OpenResty 最容易踩坑的地方。

每个 Nginx worker 进程都有自己的 Lua VM。也就是说:

  • 普通 Lua 模块缓存,是每个 worker 一份
  • 普通 Lua 全局变量,是每个 worker 内各自一份
  • 想跨 worker 共享,通常要用 lua_shared_dict、外部 Redis、数据库,或者其他显式共享手段。

这就像锻造厂里有多条并行生产线。你在 1 号线的白板上写字,2 号线不会自动看到。
只有写进“中央公告牌”里,大家才共享。这个中央公告牌,就是 lua_shared_dict 这类共享内存结构。

#4. 真正高并发的前提:非阻塞 I/O

OpenResty 的核心承诺建立在一个前提上:你的 Lua 代码不能偷偷把 worker 卡死

所以要分清两类库:

  • 该用:基于 cosocket 的 lua-resty-* 非阻塞库
  • 慎用:传统阻塞式 Lua/C 库、直接文件阻塞 I/O、阻塞 DNS、阻塞数据库驱动

错误姿势:

text
Lua 代码里顺手调了一个阻塞 HTTP/Redis/MySQL 客户端
-> 当前 worker 被卡住
-> 这条生产线停摆
-> QPS 和尾延迟一起塌

正确姿势:

text
Lua 代码发起 cosocket I/O
-> 当前 Lua 协程挂起
-> worker 继续处理别的连接
-> I/O ready 后再恢复

这就是 OpenResty 最值钱的地方:把 Lua 协程挂进 Nginx 事件循环