如何应对 OpenResty 为支持 ARM64 引入的 break change

栏目: 服务器 · Nginx · 发布时间: 4年前

内容简介:本文不是关于新版 OpenResty 如何支持 ARM64 的,而是关于如何应对这一过程引入的 break change。另外,如果你没用 OpenResty 自己的 LuaJIT 分支,那么可以直接关掉这个页面了,因为这些 break change 只有在使用了 OpenResty 自己的 LuaJIT 分支才会出现。一切的根源在于,新版本的 OpenResty 把当前请求的

本文不是关于新版 OpenResty 如何支持 ARM64 的,而是关于如何应对这一过程引入的 break change。

另外,如果你没用 OpenResty 自己的 LuaJIT 分支,那么可以直接关掉这个页面了,因为这些 break change 只有在使用了 OpenResty 自己的 LuaJIT 分支才会出现。

一切的根源在于,新版本的 OpenResty 把当前请求的 ngx_http_request_t 放到了 luaStateexdata 属性里面,不再使用 getfenv(0).__ngx_req 这种方式了。 exdata 是 OpenResty 自己的 LuaJIT 分支加的属性,所以如果不用 OpenResty 自己的 LuaJIT 分支,依旧还是得走 getfenv(0).__ngx_req 这种方式。同样被移除的还有 getfenv(0).__ngx_cycle 和给每个 main thread 准备的全局环境表。接下来我们谈谈如何应对这几个变化。

getfenv(0).__ngx_req

新版 OpenResty 使用 resty.core.base 里面的 get_request() 代替了 getfenv(0).__ngx_req 。在你的代码里,可以这么写:

local base = require "resty.core.base"
local get_request = base.get_request
if not get_request then
    get_request = function()
        return getfenv(0).__ngx_req
    end
end

...
local r = get_request()

但是! get_request() 并非 100% 兼容 getfenv(0).__ngx_req 。前者返回的是一个 cdata,而后者返回的是一个 lightuserdata。cdata 和 lightuserdata 在语义上有些微妙的不同。当你用 lightuserdata 作为 table 的 hash key 时,如果 lightuserdata 指向的地址相同,那么 hash 值会相同。但如果是 cdata,即使是指向同一地址的指针类型的 cdata,由于计算 hash 时用的是 cdata 的地址,而非其内部的值,所以不同的 cdata 的 hash 值会不一样。举个例子:

local r = get_request()
local h = {}
h[r] = 1
ngx.say(h[get_request()])

在之前的版本里,两次 get_request() 会返回同一个地址(都是同一个请求嘛),所以会输出 1。而新的 OpenResty 里,你会发现输出结果是 nil。这是因为两次 get_request() 会创造两个 cdata 对象,这两个对象虽然值一样,但是内存地址不一样,所以 hash 值不一样。

那怎么解决呢?我们可以实现一个转换函数,把 cdata 的值变成某种可用作 hash key 的类型。一个简单的解决方法是加上 tostring。 tostring(cdata) 的输出中会包含 cdata 指向的地址,这样同一个请求对应的 key 就会相同。

考虑到 LuaJIT 创建字符串的开销比较大,作为一种优化手段,在某些架构下我们可以用 tonumber(ffi_cast("intptr_t", cdata)) 代替。之所以限定在某些架构,是因为 LuaJIT 的 Number 其实是 double,而不是 int64,所以对于某些 64 位的架构,不一定能得到正确的输出。好在 x64 的用户态空间地址不会超过 48 位,所以我们可以在主流的 x64 服务器上采用该优化。当然前提是你没有启用 5 级页表。考虑到只有数百 TB 内存的机器才会有开启 5 级页表的需要,大体上你可以放心地认为你的 x64 环境不会遇到这样的问题。即使开启了 5 级页表,现阶段 48 位以上的内存地址也不是默认可用的。关于 5 级页表的更多上下文,可以看下这两个链接:

getfenv(0).__ngx_cycle

有些 Lua 代码会通过 getfenv(0).__ngx_cycle 获取 ngx_cycle , 然后通过 FFI 调用传给 C 函数。其实直接在 C 函数里面访问 ngx_cycle 就可以了,不需要经过 Lua 这一层。

你可能会问,reload 的时候, init 阶段下 ngx_cylce 应该会指向旧的 ngx_cycle 吧?这里 OpenResty 做了点手脚。它会把旧的 ngx_cycle 放到 saved_ngx_cycle 里面来,让 ngx_cycle 指向新构建的 ngx_cycle_t *cycle 。所以并不需要特殊的对待。

每个 main thread 准备的全局环境表

为了放下 getfenv(0).__ngx_req ,过去的 OpenResty 需要给每个 main thread 准备独立的全局环境表,这样每个请求的 getfenv(0) 才会返回不同的 table。既然新的 OpenResty 已经不需要 getfenv(0).__ngx_req ,这些全局环境表就能干掉了。

不过让它们下岗,还有点副作用。过去在 rewrite / access / content 等阶段里定义了全局变量(通常是手误引入的),不会污染到其他的请求。那是因为 OpenResty 设置了全局环境表,这些全局变量只会影响到它们所在的全局环境表。但是移除了全局环境表的保护后,这些全局变量就能肆无忌惮地跑来跑去。为此新版 OpenResty 加了个 guard,如果在这些阶段里遇到全局变量的定义,会打印这样的错误信息:

2019/04/30 11:01:18 [warn] 26843#26843: *240 [lua] _G write guard:12: __newindex(): writing a global lua variable ('xxx') which may lead to race conditions between concurrent requests, so prefer the use of 'local' variables
stack traceback:
...

这种错误信息对程序的流程没有影响,但对性能有影响。解决办法?把全局变量一个个都揪出来解决掉。

当然如果你是在 initinit_worker 阶段定义全局变量,并不会触发这个 guard。毕竟这么做的人一般是故意的,在过去的 OpenResty 里这也是标准的“使用”全局变量的方式。虽然我个人不推荐这么做。用全局变量,迟早都要还的。


以上所述就是小编给大家介绍的《如何应对 OpenResty 为支持 ARM64 引入的 break change》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入浅出Node.js

深入浅出Node.js

朴灵 / 人民邮电出版社 / 2013-12-1 / CNY 69.00

本书从不同的视角介绍了 Node 内在的特点和结构。由首章Node 介绍为索引,涉及Node 的各个方面,主要内容包含模块机制的揭示、异步I/O 实现原理的展现、异步编程的探讨、内存控制的介绍、二进制数据Buffer 的细节、Node 中的网络编程基础、Node 中的Web 开发、进程间的消息传递、Node 测试以及通过Node 构建产品需要的注意事项。最后的附录介绍了Node 的安装、调试、编码......一起来看看 《深入浅出Node.js》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换