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

本文不是关于新版 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 值不同。ui

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

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

https://lwn.net/Articles/717293/
https://www.kernel.org/doc/Do...指针

getfenv(0).__ngx_cycle

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

你可能会问,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 里这也是标准的“使用”全局变量的方式。虽然我我的不推荐这么作。用全局变量,早晚都要还的。

相关文章
相关标签/搜索