OpenResty:worker間通信


在 Lua 中, table 是唯一的數據結構。共享內存字典shared dict, 是在 OpenResty 編程中最為重要的數據結構。它不僅支持數據的存放和讀取,還支持原子計數和隊列操作。

基於 shared dict,可以實現多個 worker 之間的緩存和通信,以及限流限速、流量統計等功能。可以把 shared dict 當作簡單的 Redis 來使用,只不過 shared dict 中的數據不能持久化,所以存放在其中的數據,一定要考慮到丟失的情況。

數據共享的幾種方式

在編寫OpenResty Lua 代碼的過程中,會遇到,在一個請求的不同階段、不同 worker 之間共享數據的情況,還可能需要在 Lua 和 C 代碼之間共享數據。

OpenResty 中常見的幾種數據共享的方法有如下幾種:

第一種是Nginx中的變量

它可以在 Nginx C 模塊之間共享數據,也可以在 C 模塊和 OpenResty 提供的 lua-nginx-module 之間共享數據

location /foo {
        set $my_var ''; # this line is required to create $my_var at config time
        content_by_lua_block {
        ngx.var.my_var = 123;
        ...
    }
}

使用 Nginx 變量這種方式來共享數據是比較慢的,因為它涉及到 hash 查找和內存分配。同時,這種方法有其局限性,只能用來存儲字符串,不能支持復雜的 Lua 類型。

第二種是ngx.ctx,可以在同一個請求的不同階段之間共享數據

它其實就是一個普通的 Lua 的 table,所以速度很快,還可以存儲各種 Lua 的對象。它的生命周期是請求級別的,當一個請求結束的時候,ngx.ctx 也會跟着被銷毀掉。

一個典型的使用場景用 ngx.ctx 來緩存 Nginx 變量這種昂貴的調用,並在不同階段都可以 使用到它:

location /test {
    rewrite_by_lua_block {
        ngx.ctx.host = ngx.var.host
    }
    access_by_lua_block {
        if (ngx.ctx.host == 'openresty.org') then
            ngx.ctx.host = 'test.com'
        end
    }
    content_by_lua_block {
        ngx.say(ngx.ctx.host)
    }
}

如果使用 curl 訪問:

curl -i 127.0.0.1:8080/test -H 'host:openresty.org'

 

就會打印出 test.com,可以表明 ngx.ctx 的確是在不同階段共享了數據。當然,還可以自己動手修改上面的例子,保存 table 等更復雜的對象,而非簡單的字符串。

需要注意的是,正因為 ngx.ctx 的生命周期是請求級別的,所以它並不能在模塊級別進行緩存。比如,在 foo.lua 文件中這樣使用就是錯誤的:

local ngx_ctx = ngx.ctx
    local function bar()
    ngx_ctx.host = 'test.com'
end

應該在函數級別進行調用和緩存:

    local ngx = ngx
    local function bar()
        ngx_ctx.host = 'test.com'
    end

 

第三種方法是使用模塊級別的變量,在同一個 worker 內的所有請求之間共享數據

-- mydata.lua
local _M = {}
local data = {
    dog = 3,
    cat = 4,
    pig = 5,
}
function _M.get_age(name)
    return data[name]
end

return _M

在 nginx.conf 的配置如下:

location /lua {
    content_by_lua_block {
        local mydata = require "mydata"
        ngx.say(mydata.get_age("dog"))
    }
}

在這個示例中,mydata 就是一個模塊,它只會被 worker 進程加載一次,之后,這個 worker 處理的所有 請求,都會共享 mydata 模塊的代碼和數據。

mydata 模塊中的 data 這個變量,就是 模塊級別的變量,它位於模塊的 top level,也就是模塊最 開始的位置,所有函數都可以訪問到它。

可以把需要在請求間共享的數據,放在模塊的 top level 變量中。一般我們只用這種方式來保存只讀的數據。如果涉及到寫操作,可能會有 race condition,這是非常難以定位的 bug。

第四種,用 shared dict 來共享數據,這些數據可以在多個 worker 之間共享

這種方法是基於紅黑樹實現的,性能很好,但也有自己的局限性——你必須事先在 Nginx 的配置文件中, 聲明共享內存的大小,並且這不能在運行期更改:

lua_shared_dict dogs 10m;

shared dict 同樣只能緩存字符串類型的數據,不支持復雜的 Lua 數據類型。這也就意味着,當需要存放 table 等復雜的數據類型時,將不得不使用 json 或者其他的方法,來序列化和反序列化,這自然會帶來不小的性能損耗。

 

共享字典

前面三種數據共享的范圍都是在請求級別,或者單個 worker 級別。所以,在當前的 OpenResty 的實現中,只有 shared dict 可以完成 worker 間的數據共享,並借此實現 worker 之間的通信,

共享字典本身,它對外提供了 20多個 Lua API,不過所有的這些 API 都是原子操作,不用擔心多個 worker 和高並發的情況下的競爭問題。

字典讀寫類

字典讀寫類的 API,是共享字典最常用的功能。下面是一個最簡單的示例:

$ resty --shdict='dogs 1m' -e 'local dict = ngx.shared.dogs
    dict:set("Tom", 56)
    print(dict:get("Tom"))'

 

除了 set 外,OpenResty 還提供了 safe_set、add、safe_add、replace 這四種寫入的方法。這里 safe 前綴的含義是,在內存占滿的情況下,不根據 LRU 淘汰舊的數據,而是寫入失敗並返回 no memory 的錯誤信息。

除了 get 外,OpenResty 還提供了 get_stale 的讀取數據的方法,相比 get 方法,它多了一個過期數據的返回值:

value, flags, stale = ngx.shared.DICT:get_stale(key)

隊列操作類

它是 OpenResty 后續新增的功能,提供了和 Redis 類似的接口。隊列中的每一個元素, 都用 ngx_http_lua_shdict_list_node_t 來描述

管理類


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM