如何應對 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 裏這也是標準的「使用」全局變量的方式。雖然我我的不推薦這麼作。用全局變量,早晚都要還的。

相關文章
相關標籤/搜索