了解 OpenResty XRay 是如何做到帮助企业定位应用程序存在的问题以及优化其效率的。

了解更多 LIVE DEMO

本 OpenResty 教程系列将带你从一台全新的 Linux 服务器出发,通过七个动手步骤搭建出一个可运行的 Nginx + Lua 应用——无需任何 Lua 基础。

OpenResty 是一个功能完整的 Web 平台,它在 Nginx 之上内置了 LuaJIT 运行时。你不再需要编写 C 模块来定制 Nginx 的行为,而是编写 Lua——让 Nginx 变快的那套非阻塞事件循环,同样也让你的 Lua 代码变快。单个 OpenResty 工作进程无需创建线程,就能处理数万个并发连接。

下面的每一节都涵盖学习路径上的一个里程碑,附带你将构建内容的概述以及指向完整实战文章的链接。如果你刚接触 OpenResty,请按顺序完成这些步骤;如果你在寻找某个特定主题,也可以直接跳到相应的章节。

什么是 OpenResty?

OpenResty 将 Nginx、LuaJIT 以及一组 Lua 库打包成一个统一的发行版。当一个请求到达时,Nginx 会通过其事件驱动的流水线对其进行路由,并在可配置的各个阶段将控制权交给一个 Lua 协程。这个协程可以执行非阻塞 I/O——TCP 连接、DNS 查询、定时器——而不会阻塞工作线程,因为 OpenResty 的 cosocket API 会挂起该协程,并在数据就绪时将其恢复。

其结果是这样一种架构:

  • 一个操作系统线程多路复用数千个并发请求。
  • Lua 代码取代 C 来实现自定义逻辑——限流、认证、动态路由、响应转换。
  • LuaJIT 将热点 Lua 路径编译为原生机器码,在 CPU 密集型工作上缩小与手写 C 之间的差距。

OpenResty 已部署在全球超过 4000 万个域名上,并且是 Kong、Apache APISIX 以及 OpenResty Edge 等产品的基础。

本教程适合谁

本系列面向已经理解 HTTP、并且使用过 Nginx 作为反向代理或静态文件服务器的工程师。不需要任何 Lua 基础——每篇文章都会恰好引入该步骤所需的 Lua。如果你有 Python、Go 或 Node.js 的背景,你会发现这种非阻塞模型很熟悉;主要的适应点在于,一切都运行在 Nginx 的事件循环内部,而不是一个独立的进程中。

OpenResty 教程系列

本系列遵循一条循序渐进的学习路径。每篇文章都建立在前一篇的基础之上。

第 1 步 —— 安装 OpenResty

在编写任何 Lua 之前,你需要一个可用的 OpenResty 安装。OpenResty 为所有主流 Linux 发行版提供了预编译的二进制包——使用官方的 APT 或 YUM 仓库安装耗时不到五分钟,且无需编译。

如何安装 OpenResty

如需覆盖所有受支持 Linux 发行版的官方软件包仓库说明,请参阅 OpenResty 官方 Linux 软件包页面

安装后,通过以下命令验证你运行的是 OpenResty 的 Nginx 二进制文件(而非系统自带的 Nginx):

export PATH=/usr/local/openresty/nginx/sbin:$PATH
which nginx
nginx -v

第 2 步 —— Hello World:你的第一个 OpenResty 应用

OpenResty 中的 Hello World HTTP 示例

第一篇文章将带你了解从零运行一个 OpenResty 应用所需的最小项目结构。你会创建 conf/logs/ 目录,编写一个最小化的 nginx.conf,用 content_by_lua_block 嵌入 Lua,并用 curl 验证结果。

location / {
    default_type text/plain;
    content_by_lua_block {
        ngx.say("Hello World")
    }
}

读完之后,你将理解 nginx -p 前缀约定,知道如何启动和停止服务器,并拥有一个可在本系列后续每篇文章中复用的项目骨架。

第 3 步 —— Lua 模块:组织你的应用代码

如何在 OpenResty 中编写 Lua 模块

内联的 content_by_lua_block 代码用于单行内容没问题,但任何真实应用都需要把可复用的函数放进独立的文件中。本文介绍 _M 表模式——在 OpenResty 中声明 Lua 模块的标准方式——并展示如何配置 lua_package_path,让 require 能在运行时找到你的模块。

local _M = {}

function _M.greet(name)
    ngx.say("Greetings from ", name)
end

return _M

本文还涵盖 init_by_lua_block,它在服务器启动时预加载模块,这样第一个真实请求就不必再付出加载成本。这是后续每篇文章的基础。

第 4 步 —— 在请求间共享数据

如何在 OpenResty 中跨请求共享数据

OpenResty 的工作进程是相互隔离的操作系统进程,默认情况下不共享内存。本文介绍 OpenResty 提供的两种跨请求共享状态的机制:

Lua 模块变量lua_shared_dict
作用域单个工作进程所有工作进程
值类型任意 Lua 类型字符串、数字、布尔值
典型用途单进程 LRU 缓存、配置计数器、限流、共享标志位
原子操作是(incraddset

在构建任何需要跟踪状态的东西——计数器、限流器或连接池——之前,理解单进程共享与跨进程共享之间的区别至关重要。

第 5 步 —— 流式 HTTP 响应

在 OpenResty 中用 ngx.flush 流式输出 HTTP 响应

OpenResty 的 ngx.flush(true) 调用是完全非阻塞的:它把缓冲的输出推送给客户端,并挂起当前协程直到写入完成,在此期间释放工作线程去服务其他请求。这使得流式输出大响应——或实现服务器发送事件(SSE)——变得直截了当,而不会造成阻塞。

本文演示了:

  • 为分块流式输出配置一个 location
  • 在循环中使用 ngx.flush(true)ngx.sleep()
  • 用单个工作进程对 500 个并发的慢速流式连接进行压测(结果:120+ 请求/秒,单个操作系统线程)
  • 通过 XMLHttpRequest 从浏览器消费该流

第 6 步 —— 命令行工具:resty 与 restydoc

OpenResty 附带了两个能加速日常开发的命令行工具。它们用途不同,因此分成了两篇文章介绍。

resty 命令行运行器

OpenResty resty 命令行教程

resty 会在幕后启动一个无头的 Nginx,直接从终端运行你的 Lua 代码——无需 nginx.conf。它是测试 cosocket 调用、快速原型化模块函数或查看 LuaJIT 编译输出的最快方式。

resty -e 'ngx.say("Hello World")'
resty -I lua/ -e 'require "mymodule".run()'
resty --shdict 'cache 10m' -e 'print(ngx.shared.cache:set("k", 1))'
resty -jv bench.lua   # 查看 JIT trace

用 restydoc 查阅文档

如何用 restydoc 查阅 OpenResty 文档

restydoc 把完整的 OpenResty 参考文档——Nginx 指令、ngx.* Lua API、LuaJIT 扩展以及内置 Lua 模块文档——带进你的终端,无需打开浏览器。它仿照 Perl 的 perldoc 设计,并使用 less 分页器。

restydoc -s ngx.flush          # OpenResty Lua API
restydoc -s lua_shared_dict    # Nginx 指令
restydoc -s string.find        # 标准 Lua
restydoc resty.redis           # 完整的 Lua 模块文档

第 7 步 —— 性能:压测与 LuaJIT 字节码

正确地对 Lua 代码进行压测

如何在 OpenResty 中对 Lua 代码进行压测

time resty 来测量 Lua 性能会产生误导——resty 进程的启动会增加约 11 毫秒的开销,足以淹没纳秒级的函数成本。正确的做法是使用 ngx.now() 测量耗时,用一个预热循环确保函数在开始记录之前已被 JIT 编译,并用 collectgarbage() 清除待处理的 GC 对象。

local function target()
    ngx.re.find("hello, world.", [[\w+\.]], "jo")
end

for i = 1, 100 do target() end  -- JIT 预热

ngx.update_time()
local begin = ngx.now()
local N = 1e7
for i = 1, N do target() end
ngx.update_time()
ngx.say("elapsed: ", (ngx.now() - begin) / N)

将 Lua 模块预编译为 LuaJIT 字节码

OpenResty 中的 LuaJIT 字节码:如何预编译 Lua 模块

对于较大的 Lua 模块,预编译为 .ljbc 字节码文件能显著缩短启动时间。一个 1.6 MB 的 Lua 源文件原本需要 12 毫秒加载,预编译后降到了 2 毫秒——提升了 6 倍。字节码文件也比源文件小 50% 以上。

/usr/local/openresty/luajit/bin/luajit -bg mymodule.lua mymodule.ljbc

nginx.conf 中,让 OpenResty 优先使用 .ljbc 文件:

lua_package_path "$prefix/lua/?.ljbc;$prefix/lua/?.lua;;";

注意:.ljbc 文件在不同的 OpenResty 构建模式(x64 与 GC64)之间不可移植。升级 OpenResty 后请始终从源文件重新编译字节码。

核心概念速查表

概念作用引入于
content_by_lua_block在 location 的 content 阶段嵌入一个 Lua 处理器Hello World
ngx.say()向响应缓冲区写入一行Hello World
lua_package_path告诉 require 到哪里找 .lua.ljbc 文件Lua 模块
init_by_lua_block在服务器启动时运行一次 Lua;用于预加载模块Lua 模块
lua_shared_dict声明一块所有工作进程都能访问的具名共享内存区数据共享
ngx.shared.DICT用于读写共享内存字典的 Lua API数据共享
ngx.flush(true)非阻塞地将写缓冲区刷新给客户端流式输出
ngx.sleep()让出当前协程而不阻塞工作线程流式输出
resty -e从终端运行内联 Lua,无需配置文件resty 命令行
restydoc -s在终端分页器中查阅某个指令或 API 章节restydoc
ngx.now()返回以秒为单位、毫秒精度的墙上时钟时间压测
.ljbcLuaJIT 预编译字节码文件;加载速度快于 .lua 源文件字节码

常见问题

OpenResty 可以免费使用吗?

可以。OpenResty 是免费的开源软件。它在 BSD 许可证下发布,与 Nginx 使用的许可证相同。完整的源代码、面向 Linux 的预编译二进制包以及内置的 Lua 库,都可以从 openresty.org 免费获取。在生产环境中运行 OpenResty 无需任何付费许可证。

OpenResty 和 Nginx 有什么区别?

OpenResty 是 Nginx 的超集。根据 OpenResty 官方网站,OpenResty 是"一个基于 NGINX 与 LuaJIT 的动态 Web 平台"。每一份有效的 nginx.conf 都能在 OpenResty 上原样运行,而且你可以逐步添加 Lua——一次一个 location。区别在于,OpenResty 内嵌了 LuaJIT,并暴露了一套非阻塞的 Lua API——即 ngx.* 命名空间——让你无需编译 C 模块就能编写自定义的请求处理逻辑。如果你需要可编程的请求逻辑,就用 OpenResty;如果你只需要一个不含自定义代码的普通反向代理或静态文件服务器,单独的 Nginx 就足够了。

OpenResty 用来做什么?

OpenResty 最常见的用途是作为可编程的 API 网关、Web 应用防火墙(WAF)、带自定义认证或路由逻辑的反向代理,以及 CDN 边缘节点。由于 Lua 在 Nginx 的事件循环内部以纳秒级延迟运行,它非常适合任何原本需要一个 sidecar 进程或一次到外部服务往返的任务——令牌校验、限流、请求改写和 A/B 路由都是典型的例子。

OpenResty 使用哪个版本的 Lua?

OpenResty 使用 LuaJIT 2.x,它实现了 Lua 5.1 语言,并带有一小组 Lua 5.2 兼容扩展。OpenResty 维护着自己的 LuaJIT 分支,包含用于正确性和性能的额外补丁。标准的 Lua 5.1 代码无需修改即可运行;LuaJIT 特有的特性,如 FFI 和 bit 库,同样可用。

如果我已经会 Nginx,OpenResty 难学吗?

Nginx 配置这一侧是完全相同的。新的部分是 Lua 层:ngx.* API、基于协程的并发模型,以及 Nginx 请求阶段(access_by_lua_blockcontent_by_lua_blocklog_by_lua_block 等)的概念。本教程系列会在每个概念第一次被需要的地方引入它,因此不需要 Lua 基础也能跟上。

OpenResty 有企业版吗?

有。OpenResty Edge 是构建在 OpenResty 之上的商业产品。它将私有 CDN、通用网关、WAF、DDoS 防护和 Kubernetes 集成打包进一个可本地部署的平台——通过 Web UI 管理,而不再手动编辑 nginx.conf 文件。OpenResty Edge 由 OpenResty 的创始人所创立的 OpenResty Inc. 公司开发和支持。

如何调试 OpenResty 中的 Lua 错误?

Lua 运行时错误会被写入 Nginx 的错误日志。首先应该查看的是:

tail -f logs/error.log

如需更结构化的调试,restydoc -s ngx.log 记录了日志 API。resty 命令行工具在把一个 bug 从运行中的服务器里隔离出来并复现时也很有用。

本系列之后可以继续探索什么

完成上面的七个步骤后,你就具备了构建生产级 OpenResty 应用的基础。接下来可以探索的主题包括:

  • resty.lrucache —— 一个单进程 LRU 缓存模块,适用于无需跨进程共享的热点数据
  • Cosocket 连接 —— 用 ngx.socket.tcp() 非阻塞地连接 Redis、MySQL、PostgreSQL 及其他后端
  • ngx.timer.atngx.timer.every —— 用于在请求周期之外执行周期性任务的后台定时器
  • OpenResty XRay —— 一款非侵入式的动态追踪工具,无需修改代码即可对运行中的 OpenResty 应用进行剖析和排障

如果你喜欢这个教程系列,请订阅本博客和我们的 YouTube 频道

关于作者

章亦春是开源 OpenResty® 项目创始人兼 OpenResty Inc. 公司 CEO 和创始人。

章亦春(Github ID: agentzh),生于中国江苏,现定居美国湾区。他是中国早期开源技术和文化的倡导者和领军人物,曾供职于多家国际知名的高科技企业,如 Cloudflare、雅虎、阿里巴巴, 是 “边缘计算“、”动态追踪 “和 “机器编程 “的先驱,拥有超过 22 年的编程及 16 年的开源经验。作为拥有超过 4000 万全球域名用户的开源项目的领导者。他基于其 OpenResty® 开源项目打造的高科技企业 OpenResty Inc. 位于美国硅谷中心。其主打的两个产品 OpenResty XRay(利用动态追踪技术的非侵入式的故障剖析和排除工具)和 OpenResty Edge(最适合微服务和分布式流量的全能型网关软件),广受全球众多上市及大型企业青睐。在 OpenResty 以外,章亦春为多个开源项目贡献了累计超过百万行代码,其中包括,Linux 内核、Nginx、LuaJITGDBSystemTapLLVM、Perl 等,并编写过 60 多个开源软件库。

关注我们

如果您喜欢本文,欢迎关注我们 OpenResty Inc. 公司的博客网站 。也欢迎扫码关注我们的微信公众号:

我们的微信公众号

翻译

我们提供了英文版原文和中译版(本文) 。我们也欢迎读者提供其他语言的翻译版本,只要是全文翻译不带省略,我们都将会考虑采用,非常感谢!