OpenResty® 通过 Lua 扩展 NGINX 的可扩展 Web 平台

OpenResty XRay

为 OpenResty 及更多平台打造的先进可观测性

×

限时优惠

立即申请 试用 并获取诊断 报告
Learn more

常见问题解答

章亦春 , 2015 年 7 月 31 日 (创建于 2015 年 6 月 13 日)

问题和答案

OpenResty 是否遵循 NGINX 的主线版本?

是的,当然。OpenResty 目前采用的是“滴答”发布模式。每次“滴答”版本(通常)会将捆绑的 NGINX 内核升级到官方 NGINX 团队的最新主线版本,然后会紧跟着发布一个或多个“滴答”版本,专注于 OpenResty 自身的特性和增强,而不会升级 NGINX 内核。

OpenResty 发布版本号的第一部分是捆绑的 NGINX 内核的版本。例如,OpenResty 1.7.10.2 捆绑的是 NGINX 1.7.10,而 OpenResty 1.9.1.1 捆绑的是 NGINX 1.9.1。

为什么 OpenResty 不遵循 NGINX 的稳定版本?

NGINX 的稳定版本只意味着 NGINX ABI 是稳定的。主线版本和稳定版本的代码都非常可靠,已准备好投入生产。认为 Nginx 稳定版本比主线版本更稳定是一种常见的误解。

NGINX 的稳定版本更新缓慢。为了尽早获得主线版本的新特性,我们需要遵循主线版本。

此外,许多重要的错误修复也是首先提交到主线版本,然后在稍后合并到稳定版本中。

OpenResty 多久发布一次新版本?

我们正努力每隔几个月发布一次 OpenResty 新版本。有时可能需要更长的时间,因为这个项目主要依靠志愿者。

我应该如何报告问题?

遇到问题时,建议您报告到 openresty-en(英文)邮件列表或 openresty(中文)邮件列表(取决于您的语言)。有关更多详细信息,请参阅 社区 页面。但请不要交叉发布。

强烈建议您在报告问题时提供尽可能多的细节,例如,

  • 您 Nginx 的错误日志文件(logs/error.log)中的任何有趣内容(如果有),
  • 您使用的相关软件的确切版本,包括但不限于您的操作系统类型/版本、您的 OpenResty 版本(或者如果您没有使用 OpenResty 捆绑包,那么您使用的 Nginx、ngx_lua、Lua/LuaJIT 和其他模块的版本),
  • 一个可以在我们这边可靠地重现问题的最小且独立的示例,以及
  • 启用 Nginx/OpenResty 的 调试日志 并提供您执行的有问题的请求的完整日志(相同的 ./configure --with-debug 命令行也适用于 OpenResty 捆绑包)。

您提供的细节越多,我们就越有可能更快地帮助您解决问题。大多数情况下,在最小化有问题的示例或查看 Nginx 错误日志文件时,您会发现自己的错误。

如果您绝对确定您遇到了真正的错误,那么建议您在相应项目的 GitHub 问题页面中提交问题。例如,ngx_lua 组件的 GitHub 问题页面是 https://github.com/openresty/lua-nginx-module/issues。OpenResty 组件的 GitHub 问题页面都被视为仅限英文。不要在那里使用其他语言,如中文。但是,您可以发送邮件到上述任一邮件列表;但请不要交叉发布。

为什么我不能使用 Lua 5.2 或更高版本?

Lua 5.2+ 与 Lua 5.1 在 C API 层和 Lua 层(包括各种语言语义)都兼容。如您所说,已经有很多用户在使用 ngx_lua + Lua 5.1,因此链接到 Lua 5.2+ 可能会破坏这些用户现有的 Lua 代码。Lua 5.2+ 本质上是不兼容的不同的语言。

支持 Lua 5.2+ 需要在 ngx_lua 的基本基础架构中进行非平凡的架构更改。最麻烦的事情是 Lua 5.2+ 中截然不同的“环境”模型。目前,我们想阻止在 ngx_lua 中添加对 Lua 5.2+ 的支持。此外,我们不想在 Lua 层为在 ngx_lua 之上运行的应用程序以及所有用 Lua 5.1 语言编写的现有 lua-resty-* 库创建混乱和不兼容性。

我们认为在 ngx_lua 中坚持使用一种 Lua 语言更好。追逐 Lua 语言的版本号并没有多少实际的技术优势(如果有的话就是政治上的)。

我可以使用 LuaJIT 2.0.x 吗?

当然可以。尽管强烈建议使用 LuaJIT 2.1+,但在 OpenResty 中始终支持 LuaJIT 2.0.x。

为什么 OpenResty 默认使用 LuaJIT 2.1?

截至撰写本文时,LuaJIT 2.1 仍然是官方的“测试版”,但 OpenResty 默认使用 LuaJIT 2.1 并鼓励在生产环境中使用它,因为

  1. 我们始终在各种大规模服务器端业务软件基础设施中运行最新的 LuaJIT v2.1。
  2. Cloudflare Inc. 赞助的所有最新性能改进都只在 v2.1 中实现。
  3. 对于我们典型的 Lua 应用程序(代码量为 10K+ LOC),与 LuaJIT 2.0.x 相比,LuaJIT v2.1 提供了 100% 以上的总体加速(当使用 lua-resty-core 时)。
  4. v2.1 分支中的许多重要错误修复已移植回 2.0.x 系列,因为这些错误也存在于 2.0.x 中(只是隐藏了很长时间)。2.0.3 版本就是一个证明。

我们强烈推荐 LuaJIT 2.1,因为我们确实需要 OpenResty 的速度,尽管您始终可以选择在 OpenResty 中使用 LuaJIT 2.0.x 甚至标准 Lua 5.1 解释器。

为什么我不能使用重复的配置指令?

ngx_lua 模块的大多数配置指令不允许在同一上下文中重复。例如,以下 nginx.conf 代码段

    location / {
        content_by_lua_file conf/a.lua;
        content_by_lua_file conf/b.lua;
    }

在启动 nginx 时会产生以下错误

nginx: [emerg] "content_by_lua_file" directive is duplicate in

人们可能希望使用重复指令将复杂的 Lua 代码库拆分为多个独立的 .lua 文件。这是不允许的,即使它在 ngx_lua 中实现,它也必然会效率低下。

组织 Lua 代码库的推荐方法是使用 Lua 自身的模块机制

https://www.lua.ac.cn/manual/5.1/manual.html#5.3

您可以将不相关的 Lua 代码放入独立的 Lua 模块文件中,例如,

-- foo.lua
local _M = {}
function _M.go() ... end
return _M
-- bar.lua
local _M = {}
function _M.go() ... end
return _M

最后,只需在入口点中 require 它们即可,如下所示

location / {
    content_by_lua '
        require("foo").go()
        require("bar").go()
    ';
}

您需要将 Lua 模块的路径添加到 Lua 的模块搜索路径中,例如:

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

在您的 nginx.conf 中的 http {} 块中。

您可以查看 OpenResty 的标准 Lua 库,以获取实际示例,例如 openresty/lua-resty-redis。

使用 Lua 的原生模块机制也非常高效。得益于 Lua 内置函数 require() 中的内置缓存机制(通过锚定在 Lua 注册表中的全局 package.loaded 表,因此由整个 Lua VM 共享)。

我可以在 Lua 中使用自定义日志记录器吗?

是的,当然。您有多种选择

  1. 编写一个自定义的 Lua 日志记录器库,使用 LuaJIT FFI 直接操作文件。您需要访问低级文件 I/O 系统调用,例如 openwriteclose,而无需经过 libc 的缓冲 I/O 层。这很重要,因为这可以确保多个(工作进程)进程追加数据到同一个文件(在追加模式下)时的原子性。您可以通过 此技术 在 NGINX 工作进程级别共享从 open 系统调用返回的结果文件描述符 (fd)。也许有一天 OpenResty 会提供一个标准的 lua-resty-logger-file 库;欢迎您贡献这样的库 :)
  2. 通过使用 lua-resty-logger-socket 库,完全避免在 nginx 服务器中进行文件 I/O。此库可以通过套接字和 100% 非阻塞 I/O 将您的日志数据发送到远程端点(例如 syslog-ng 服务器)。例如,Cloudflare 在其全球网络中广泛使用此库。与 NGINX 核心本机 syslog 日志记录器不同,此日志记录器会尽力避免在错误条件下丢失数据。

出于性能原因,通常建议使用基于套接字的日志记录,因为文件 I/O 几乎总是会阻塞 NGINX 事件循环(或者如果为文件 I/O 启用新的 NGINX 线程池支持,则会阻塞某些操作系统线程,这会增加额外的开销)。

为什么我看到 "lua tcp socket connect timed out" 错误?

该错误发生在连接到后端的操作超时时。后端可以是 Redis、Memcached、MySQL 或您当前使用的任何其他东西。此错误日志消息由 ngx_lua 模块的 cosocket 自动错误日志记录器生成。如果您已经在自己的 Lua 代码中正确处理了所有 cosocket 错误,那么您可以通过在您的 ngnx.conf 中关闭 lua_socket_log_errors 来抑制自动日志记录器。

lua_socket_log_errors off;

连接超时错误的原因可能很复杂,但通常属于以下类别

  1. 您的 redis 服务器太忙而无法接受新连接,并且 redis 服务器的 TCP 堆栈在 accept() 队列已满时会丢弃数据包,或者
  2. 您的 nginx 服务器太忙而无法响应新的 I/O 事件(例如执行 CPU 密集型计算或阻塞在系统调用上),
  3. 您在客户端的超时阈值太小,无法考虑网络延迟和网络堆栈。

您可以尝试以下方法来解决此问题

  1. 将您的后端扩展到多个节点,以便利用更多(CPU)资源(例如,redis 服务器是单线程的,并且很容易耗尽单个 CPU 内核)。
  2. 增加您在后端服务器上的 backlog 设置(例如,在 redis.conf 配置文件中有一个 tcp-backlog 参数来调整);backlog 设置决定了您在后端服务器上的 accept() 队列的长度限制。
  3. 使用 火焰图工具 检查您的 nginx 服务器是否太忙于执行 CPU 密集型工作或阻塞系统调用(例如磁盘 I/O 系统调用),
  4. 如果合理,请增加您在 Lua 代码中的超时阈值,
  5. 当您的 connect() 调用失败时,在您的 Lua 代码中自动重试 connect() 一两次,并可以选择延迟。

为什么我从 getreusedtimes()get_reused_times() 调用中始终获取 0?

这通常意味着您无法将后端连接放入连接池中,并且连接池始终为空,因此 connect() 调用始终建立新连接而不是重用现有连接。

解决方案是始终检查 setkeepalive()set_keepalive() 调用的返回值,并在出现任何错误时正确处理错误。当您从 setkeepalive()set_keepalive() 调用中获得错误时,我们可以着手解决问题(例如,避免在错误的时间调用 setkeepalive()set_keepalive())。

我可以在子请求上设置超时阈值吗?

是的,您可以,但不能通过子请求调用的选项或参数直接设置,例如 ngx.location.captureecho_subrequest。相反,您应该在子请求目标的 location 中指定超时配置。例如:

location = /api {
    content_by_lua '
        local res = ngx.location.capture("/sub")
    ';
}

location = /sub {
    internal;
    proxy_connect_timeout 100ms;
    proxy_read_timeout 100ms;
    proxy_send_timeout 100ms;
    proxy_pass http://backend;
}

在这里,您指定了 ngx_proxy 模块在子请求访问的(内部)location(= /sub)中提供的所有超时阈值。

我可以通过 ngx.location.capture 或类似方法访问远程 URL 吗?

是的,但不能直接访问。子请求 API 目标是 nginx 的“location”。因此,您需要一个专用 location(无论是“内部”还是其他),并在其中配置标准的 ngx_proxy 模块。

或者,您可以考虑使用社区贡献的 lua-resty-http* 库中的一个。举几个例子

贡献此常见问题解答

此常见问题解答文档托管在 GitHub 上,并定期更新到 openresty.org 网站

https://github.com/openresty/openresty.org

您可以编辑上述存储库中的 faq.md 文件并创建拉取请求,以便我可以合并您的补丁。