構建api gateway之 動態外掛

2023-02-10 12:00:37

動態外掛

之前已經拆解細點逐個介紹了 tcp 、http 代理相關核心點,現在介紹一個讓 api gateway 變得很靈活的功能實現: 動態外掛。

由於 lua 的動態語言特點,我們可以比較方便做到動態外掛機制。

首先我們來了解這一切的基石:lua 模組載入機制。

lua 模組載入機制

一個模組是什麼樣?

例如: xxxmodule.lua 檔案內容

local module = {} -- 注意不要使用全域性變數,會造成變數汙染,導致無法解除安裝模組
 
-- 定義一個函數
function module.func1()
    io.write("這是一個公有函數!\n")
end

return module

如何載入模組?

Lua提供了一個名為require的函數用來載入模組。要載入一個模組,只需要簡單地呼叫就可以了。例如:

local a = require("xxxmodule")
a.func1() -- "這是一個公有函數!\n"

到底是怎麼工作的呢?

require 函數會在模組path列表搜尋模組,openresty可以指定如下兩種:

lua 庫: lua_package_path "./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;";

c 庫: lua_package_cpath "./?.so;/usr/local/lib/lua/5.1/?.so;";

找到模組檔案之後,就會解析執行整個檔案的內容(類似函數 loadstring),由於最後是return 模組變數,我們就可以使用這個變數的函數等等一切了

如果開啟了 lua_code_cache on, require 函數會將第二步拿到的變數存在 package.loaded 這個table 中,達到快取效果

那麼如何解除安裝呢?

非常簡單,只需一句:

package.loaded['xxxmodule'] = nil

所以基於lua的模組管理,我們就可以非常容易實現外掛模組的管理

lua severless function simple demo

所以我們可以基於這樣的動態機制,實現 lua severless function 或者動態外掛機制,範例如下:

http {
    default_type  application/json;
    lua_code_cache on;

    lua_package_path  "$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;$prefix/src/?.lua;$prefix/src/?/init.lua;;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua;";
    lua_package_cpath "$prefix/deps/lib64/lua/5.1/?.so;$prefix/deps/lib/lua/5.1/?.so;;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;";


    # 簡單模擬模組
    init_by_lua_block {
        MockPackages = {} 
    }
    server {
        listen       8222;
        server_name  localhost;

        location /add {
            
            # 比如替換為 request body 去做模組建立,這裡為了簡單就用寫死的程式碼來模擬
            # 內容為通過 loadstring 轉換 lua code 字串為函數
            # 並將函數結果 當前時間存在全域性變數中
            access_by_lua_block {
                local lua_src = [[
                ngx.update_time()
                return tostring(ngx.now())
                ]]
                local f, e = loadstring(lua_src, "module xxxmodule")
                MockPackages['xxxmodule'] = f()
                ngx.say('add function success')
            }
        }
        
        location /run {

            # 這裡獲取快取結果並輸出出來
            access_by_lua_block {
                if MockPackages['xxxmodule'] then
                    ngx.say(MockPackages['xxxmodule'])
                else 
                    ngx.say('no function')
                end
            }
        }
    }
}

啟動並測試

mkdir -p logs && /usr/bin/openresty -p ./ -c nginx.conf -g 'daemon off;'

call http://127.0.0.1:8222/run return no function

call http://127.0.0.1:8222/add return add function success

call http://127.0.0.1:8222/run return 1624022896.703

call http://127.0.0.1:8222/add return add function success

call http://127.0.0.1:8222/run return 1624022918.674

可以看到值已經被改變了

這種severless function demo的問題

  • 管理以及定位問題

    實際環境會有很多機器範例,對應的severless function 在哪幾臺機器哪幾個nginx中的哪些worker 程序上載入,載入多久, 需要完整規劃方案

  • 資源隔離

    所有的severless function 其實都是在worker內, 所以記憶體cpu等於是共用,一個特耗效能的程式碼必然影響其他

  • 安全問題

    由於多個函數會同在一個worker 程序,無論效能和資源都會收到相互影響 別人可以在其中輕鬆加入惡意程式碼

所以如果在公用的api gateway中,大家還是不要把它當成雲端計算中的 severless function 使用,只是當成一個 動態filter function 就好。

目錄