构建api gateway之 动态插件

时间:2023-02-10 11:06:29

之前已经拆解细点逐个介绍了 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 就好。

目录