APISIX架構分析:如何動態管理Nginx集羣?

開源版Nginx最爲人詬病的就是不具備動態配置、遠程API及集羣管理的能力,而APISIX作爲CNCF畢業的開源七層網關,基於etcd、Lua實現了對Nginx集羣的動態管理。
apisix架構圖

apisix架構圖

讓Nginx具備動態、集羣管理能力並不容易,因爲這將面臨以下問題:

  • 微服務架構使得上游服務種類多、數量大,這導致路由規則、上游Server的變更極爲頻率。而Nginx的路由匹配是基於靜態的Trie前綴樹、哈希表、正則數組實現的,一旦server_name、location變動,不執行reload就無法實現配置的動態變更;
  • Nginx將自己定位於ADC邊緣負載均衡,因此它對上游並不支持HTTP2協議。這增大了OpenResty生態實現etcd gRPC接口的難度,因此通過watch機制接收配置變更必然效率低下;
  • 多進程架構增大了Worker進程間的數據同步難度,必須選擇1個低成本的實現機制,保證每個Nginx節點、Worker進程都持有最新的配置;

等等。

APISIX基於Lua定時器及lua-resty-etcd模塊實現了配置的動態管理,本文將基於APISIX2.8、OpenResty1.19.3.2、Nginx1.19.3分析APISIX實現REST API遠程控制Nginx集羣的原理。

接下來我將分析APISIX的解決方案。

基於etcd watch機制的配置同步方案

管理集羣必須依賴中心化的配置,etcd就是這樣一個數據庫。APISIX沒有選擇關係型數據庫作爲配置中心,是因爲etcd具有以下2個優點:

  1. etcd採用類Paxos的Raft協議保障了數據一致性,它是去中心化的分佈式數據庫,可靠性高於關係數據庫;
  2. etcd的watch機制允許客戶端監控某個key的變動,即,若類似/nginx/http/upstream這種key的value值發生變動,watch的客戶端會立刻收到通知,如下圖所示:

基於etcd同步nginx配置

基於etcd同步nginx配置

因此,不同於Orange採用MySQL、Kong採用PostgreSQL作爲配置中心(這二者同樣是基於OpenResty實現的API Gateway),APISIX採用了etcd作爲中心化的配置組件。

因此,你可以在生產環境的APISIX中通過etcdctl看到如下的類似配置:

1
2
3
# etcdctl get  "/apisix/upstreams/1"
/apisix/upstreams/1
{"hash_on":"vars","nodes":{"httpbin.org:80":1},"create_time":1627982128,"update_time":1627982128,"scheme":"http","type":"roundrobin","pass_host":"pass","id":"1"}
 

其中,/apisix這個前綴可以在conf/config.yaml中修改,比如:

1
2
3
4
etcd:
  host:  
    - "http://127.0.0.1:2379"   
  prefix: /apisix                 # apisix configurations prefix
 

而upstreams/1就等價於nginx.conf中的http { upstream 1 {} }配置。類似關鍵字還有/apisix/services/、/apisix/routes/等,不一而足。

那麼,Nginx是怎樣通過watch機制獲取到etcd配置數據變化的呢?有沒有新啓動一個agent進程?它通過HTTP/1.1還是gRPC與etcd通訊的?

ngx.timer.at定時器

APISIX並沒有啓動Nginx以外的進程與etcd通訊。它實際上是通過ngx.timer.at這個定時器實現了watch機制。爲了方便對OpenResty不太瞭解的同學,我們先來看看Nginx中的定時器是如何實現的,它是watch機制實現的基礎。

Nginx的紅黑樹定時器

Nginx採用了epoll + nonblock socket這種多路複用機制實現事件處理模型,其中每個worker進程會循環處理網絡IO及定時器事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//參見Nginx的src/os/unix/ngx_process_cycle.c文件
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    for ( ;; ) {
        ngx_process_events_and_timers(cycle);
    }
}

// 參見ngx_proc.c文件
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    timer = ngx_event_find_timer();
    (void) ngx_process_events(cycle, timer, flags);
    ngx_event_process_posted(cycle, &ngx_posted_accept_events);
    ngx_event_expire_timers();
    ngx_event_process_posted(cycle, &ngx_posted_events);
}
 

ngx_event_expire_timers函數會調用所有超時事件的handler方法。事實上,定時器是由紅黑樹(一種平衡有序二叉樹)實現的,其中key是每個事件的絕對過期時間。這樣,只要將最小節點與當前時間做比較,就能快速找到過期事件。

OpenResty的Lua定時器

當然,以上C函數開發效率很低。因此,OpenResty封裝了Lua接口,通過ngx.timer.at將ngx_timer_add這個C函數暴露給了Lua語言:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//參見OpenResty /ngx_lua-0.10.19/src/ngx_http_lua_timer.c文件
void
ngx_http_lua_inject_timer_api(lua_State *L)
{
    lua_createtable(L, 0 /* narr */, 4 /* nrec */);    /* ngx.timer. */

    lua_pushcfunction(L, ngx_http_lua_ngx_timer_at);
    lua_setfield(L, -2, "at");

    lua_setfield(L, -2, "timer");
}
static int
ngx_http_lua_ngx_timer_at(lua_State *L)
{
    return ngx_http_lua_ngx_timer_helper(L, 0);
}
static int
ngx_http_lua_ngx_timer_helper(lua_State *L, int every)
{
    ngx_event_t             *ev = NULL;
    ev->handler = ngx_http_lua_timer_handler;
    ngx_add_timer(ev, delay);
}
 

因此,當我們調用ngx.timer.at這個Lua定時器時,就是在Nginx的紅黑樹定時器里加入了ngx_http_lua_timer_handler回調函數,這個函數不會阻塞Nginx。

下面我們來看看APISIX是怎樣使用ngx.timer.at的。

APISIX基於定時器實現的watch機制

Nginx框架爲C模塊開發提供了許多鉤子,而OpenResty將部分鉤子以Lua語言形式暴露了出來,如下圖所示:
openresty鉤子

openresty鉤子

APISIX僅使用了其中8個鉤子(注意,APISIX沒有使用set_by_lua和rewrite_by_lua,rewrite階段的plugin其實是APISIX自定義的,與Nginx無關),包括:

  • init_by_lua:Master進程啓動時的初始化;
  • init_worker_by_lua:每個Worker進程啓動時的初始化(包括privileged agent進程的初始化,這是實現java等多語言plugin遠程RPC調用的關鍵);
  • ssl_certificate_by_lua:在處理TLS握手時,openssl提供了一個鉤子,OpenResty通過修改Nginx源碼以Lua方式暴露了該鉤子;
  • access_by_lua:接收到下游的HTTP請求頭部後,在此匹配Host域名、URI、Method等路由規則,並選擇Service、Upstream中的Plugin及上游Server;
  • balancer_by_lua:在content階段執行的所有反向代理模塊,在選擇上游Server時都會回調init_upstream鉤子函數,OpenResty將其命名爲 balancer_by_lua;
  • header_filter_by_lua:將HTTP響應頭部發送給下游前執行的鉤子;
  • body_filter_by_lua:將HTTP響應包體發送給下游前執行的鉤子;
  • log_by_lua:記錄access日誌時的鉤子。
    準備好上述知識後,我們就可以回答APISIX是怎樣接收etcd數據的更新了。

nginx.conf的生成方式

每個Nginx Worker進程都會在init_worker_by_lua階段通過http_init_worker函數啓動定時器:

1
2
3
init_worker_by_lua_block {
    apisix.http_init_worker()
}
 

關於nginx.conf配置語法,你可以參考我的這篇文章《從通用規則中學習nginx模塊的定製指令》。你可能很好奇,下載APISIX源碼後沒有看到nginx.conf,這段配置是哪來的?

這裏的nginx.conf實際是由APISIX的啓動命令實時生成的。當你執行make run時,它會基於Lua模板apisix/cli/ngx_tpl.lua文件生成nginx.conf。請注意,這裏的模板規則是OpenResty自實現的,語法細節參見lua-resty-template。生成nginx.conf的具體代碼參見apisix/cli/ops.lua文件:

1
2
3
4
5
6
7
8
9
local template = require("resty.template")
local ngx_tpl = require("apisix.cli.ngx_tpl")
local function init(env)
    local yaml_conf, err = file.read_yaml_conf(env.apisix_home)
    local conf_render = template.compile(ngx_tpl)
    local ngxconf = conf_render(sys_conf)

    local ok, err = util.write_file(env.apisix_home .. "/conf/nginx.conf",
                                    ngxconf)
 

當然,APISIX允許用戶修改nginx.conf模板中的部分數據,具體方法是模仿conf/config-default.yaml的語法修改conf/config.yaml配置。其實現原理參見read_yaml_conf函數:

1
2
3
4
5
6
7
8
function _M.read_yaml_conf(apisix_home)
    local local_conf_path = profile:yaml_path("config-default")
    local default_conf_yaml, err = util.read_file(local_conf_path)

    local_conf_path = profile:yaml_path("config")
    local user_conf_yaml, err = util.read_file(local_conf_path)
    ok, err = merge_conf(default_conf, user_conf)
end
 

可見,ngx_tpl.lua模板中僅部分數據可由yaml配置中替換,其中conf/config-default.yaml是官方提供的默認配置,而conf/config.yaml則是由用戶自行覆蓋的自定義配置。如果你覺得僅替換模板數據還不夠,大可以直接修改ngx_tpl模板。

APISIX獲取etcd通知的方式

APISIX將需要監控的配置以不同的前綴存入了etcd,目前包括以下11種:

  • /apisix/consumers/:APISIX支持以consumer抽象上游種類;
  • /apisix/global_rules/:全局通用的規則;
  • /apisix/plugin_configs/:可以在不同Router間複用的Plugin;
  • /apisix/plugin_metadata/:部分插件的元數據;
  • /apisix/plugins/:所有Plugin插件的列表;
  • /apisix/proto/:當透傳gRPC協議時,部分插件需要轉換協議內容,該配置存儲protobuf消息定義;
  • /apisix/routes/:路由信息,是HTTP請求匹配的入口,可以直接指定上游Server,也可以掛載services或者upstream;
  • /apisix/services/:可以將相似的router中的共性部分抽象爲services,再掛載plugin;
  • /apisix/ssl/:SSL證書公、私鑰及相關匹配規則;
  • /apisix/stream_routes/:OSI四層網關的路由匹配規則;
  • /apisix/upstreams/:對一組上游Server主機的抽象;

這裏每類配置對應的處理邏輯都不相同,因此APISIX抽象出apisix/core/config_etcd.lua文件,專注etcd上各類配置的更新維護。在http_init_worker函數中每類配置都會生成1個config_etcd對象:

1
2
3
4
5
6
7
8
function _M.init_worker()
    local err
    plugin_configs, err = core.config.new("/plugin_configs", {
        automatic = true,
        item_schema = core.schema.plugin_config,
        checker = plugin_checker,
    })
end
 

而在config_etcd的new函數中,則會循環註冊_automatic_fetch定時器:

1
2
3
function _M.new(key, opts)
    ngx_timer_at(0, _automatic_fetch, obj)
end
 

_automatic_fetch函數會反覆執行sync_data函數(包裝到xpcall之下是爲了捕獲異常):

1
2
3
4
5
6
local function _automatic_fetch(premature, self)
    local ok, err = xpcall(function()
        local ok, err = sync_data(self)
    end, debug.traceback)
    ngx_timer_at(0, _automatic_fetch, self)
end
 

sync_data函數將通過etcd的watch機制獲取更新,它的實現機制我們接下來會詳細分析。

總結下:

APISIX在每個Nginx Worker進程的啓動過程中,通過ngx.timer.at函數將_automatic_fetch插入定時器。_automatic_fetch函數執行時會通過sync_data函數,基於watch機制接收etcd中的配置變更通知,這樣,每個Nginx節點、每個Worker進程都將保持最新的配置。如此設計還有1個明顯的優點:etcd中的配置直接寫入Nginx Worker進程中,這樣處理請求時就能直接使用新配置,無須在進程間同步配置,這要比啓動1個agent進程更簡單!

lua-resty-etcd庫的HTTP/1.1協議

sync_data函數到底是怎樣獲取etcd的配置變更消息的呢?先看下sync_data源碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
local etcd         = require("resty.etcd")
etcd_cli, err = etcd.new(etcd_conf)

local function sync_data(self)
    local dir_res, err = waitdir(self.etcd_cli, self.key, self.prev_index + 1, self.timeout)
end

local function waitdir(etcd_cli, key, modified_index, timeout)
    local res_func, func_err, http_cli = etcd_cli:watchdir(key, opts)
    if http_cli then
        local res_cancel, err_cancel = etcd_cli:watchcancel(http_cli)
    end
end
 

這裏實際與etcd通訊的是lua-resty-etcd庫。它提供的watchdir函數用於接收etcd發現key目錄對應value變更後發出的通知。

watchcancel函數又是做什麼的呢?這其實是OpenResty生態的缺憾導致的。etcd v3已經支持高效的gRPC協議(底層爲HTTP2協議)。你可能聽說過,HTTP2不但具備多路複用的能力,還支持服務器直接推送消息,關於HTTP2的細節可以參照我的這篇文章《深入剖析HTTP3協議》,從HTTP3協議對照理解HTTP2:
http2的多路複用與服務器推送

http2的多路複用與服務器推送

然而,Lua生態目前並不支持HTTP2協議!所以lua-resty-etcd庫實際是通過低效的HTTP/1.1協議與etcd通訊的,因此接收/watch通知也是通過帶有超時的/v3/watch請求完成的。這個現象其實是由2個原因造成的:

  1. Nginx將自己定位爲邊緣負載均衡,因此上游必然是企業內網,時延低、帶寬大,所以對上游協議不必支持HTTP2協議!
  2. 當Nginx的upstream不能提供HTTP2機制給Lua時,Lua只能基於cosocket自己實現了。HTTP2協議非常複雜,目前還沒有生產環境可用的HTTP2 cosocket庫。

使用HTTP/1.1的lua-resty-etcd庫其實很低效,如果你在APISIX上抓包,會看到頻繁的POST報文,其中URI爲/v3/watch,而Body是Base64編碼的watch目錄:

APISIX與etcd通過HTTP1通訊

APISIX與etcd通過HTTP1通訊

我們可以驗證下watchdir函數的實現細節:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- lib/resty/etcd/v3.lua文件
function _M.watchdir(self, key, opts)
    return watch(self, key, attr)
end

local function watch(self, key, attr)
    callback_fun, err, http_cli = request_chunk(self, 'POST', '/watch',
                                                opts, attr.timeout or self.timeout)
    return callback_fun
end

local function request_chunk(self, method, path, opts, timeout)
    http_cli, err = utils.http.new()
    -- 發起TCP連接
    endpoint, err = http_request_chunk(self, http_cli)
    -- 發送HTTP請求
    res, err = http_cli:request({
        method  = method,
        path    = endpoint.api_prefix .. path,
        body    = body,
        query   = query,
        headers = headers,
    })
end

local function http_request_chunk(self, http_cli)
    local endpoint, err = choose_endpoint(self)
    ok, err = http_cli:connect({
        scheme = endpoint.scheme,
        host = endpoint.host,
        port = endpoint.port,
        ssl_verify = self.ssl_verify,
        ssl_cert_path = self.ssl_cert_path,
        ssl_key_path = self.ssl_key_path,
    })

    return endpoint, err
end
 

可見,APISIX在每個worker進程中,通過ngx.timer.at和lua-resty-etcd庫反覆請求etcd,以此保證每個Worker進程中都含有最新的配置。

APISIX配置與插件的遠程變更

接下來,我們看看怎樣遠程修改etcd中的配置。

我們當然可以直接通過gRPC接口修改etcd中相應key的內容,再基於上述的watch機制使得Nginx集羣自動更新配置。然而,這樣做的風險很大,因爲配置請求沒有經過校驗,進面導致配置數據與Nginx集羣不匹配!

通過Nginx的/apisix/admin/接口修改配置

APISIX提供了這麼一種機制:訪問任意1個Nginx節點,通過其Worker進程中的Lua代碼校驗請求成功後,再由/v3/dv/put接口寫入etcd中。下面我們來看看APISIX是怎麼實現的。

首先,make run生成的nginx.conf會自動監聽9080端口(可通過config.yaml中apisix.node_listen配置修改),當apisix.enable_admin設置爲true時,nginx.conf就會生成以下配置:

1
2
3
4
5
6
7
8
9
server {
    listen 9080 default_server reuseport;

    location /apisix/admin { 
        content_by_lua_block {
            apisix.http_admin()
        }
    }
}
 

這樣,Nginx接收到的/apisix/admin請求將被http_admin函數處理:

1
2
3
4
-- /apisix/init.lua文件
function _M.http_admin()
    local ok = router:dispatch(get_var("uri"), {method = get_method()})
end
 

admin接口能夠處理的API參見github文檔,其中,當method方法與URI不同時,dispatch會執行不同的處理函數,其依據如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- /apisix/admin/init.lua文件
local uri_route = {
    {
        paths = [[/apisix/admin/*]],
        methods = {"GET", "PUT", "POST", "DELETE", "PATCH"},
        handler = run,
    },
    {
        paths = [[/apisix/admin/stream_routes/*]],
        methods = {"GET", "PUT", "POST", "DELETE", "PATCH"},
        handler = run_stream,
    },
    {
        paths = [[/apisix/admin/plugins/list]],
        methods = {"GET"},
        handler = get_plugins_list,
    },
    {
        paths = reload_event,
        methods = {"PUT"},
        handler = post_reload_plugins,
    },
}
 

比如,當通過/apisix/admin/upstreams/1和PUT方法創建1個Upstream上游時:

1
2
3
4
5
6
7
8
# curl "http://127.0.0.1:9080/apisix/admin/upstreams/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
> {
>   "type": "roundrobin",
>   "nodes": {
>     "httpbin.org:80": 1
>   }
> }'
{"action":"set","node":{"key":"\/apisix\/upstreams\/1","value":{"hash_on":"vars","nodes":{"httpbin.org:80":1},"create_time":1627982128,"update_time":1627982128,"scheme":"http","type":"roundrobin","pass_host":"pass","id":"1"}}}
 

你會在error.log中會看到如下日誌(想看到這行日誌,必須將config.yaml中的nginx_config.error_log_level設爲INFO):

1
2021/08/03 17:15:28 [info] 16437#16437: *23572 [lua] init.lua:130: handler(): uri: ["","apisix","admin","upstreams","1"], client: 127.0.0.1, server: _, request: "PUT /apisix/admin/upstreams/1 HTTP/1.1", host: "127.0.0.1:9080"
 

這行日誌實際是由/apisix/admin/init.lua中的run函數打印的,它的執行依據是上面的uri_route字典。我們看下run函數的內容:

1
2
3
4
5
6
7
8
9
10
11
12
-- /apisix/admin/init.lua文件
local function run()
    local uri_segs = core.utils.split_uri(ngx.var.uri)
    core.log.info("uri: ", core.json.delay_encode(uri_segs))

    local seg_res, seg_id = uri_segs[4], uri_segs[5]
    local seg_sub_path = core.table.concat(uri_segs, "/", 6)

    local resource = resources[seg_res]
    local code, data = resource[method](seg_id, req_body, seg_sub_path,
                                        uri_args)
end
 

這裏resource[method]函數又被做了1次抽象,它是由resources字典決定的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- /apisix/admin/init.lua文件
local resources = {
    routes          = require("apisix.admin.routes"),
    services        = require("apisix.admin.services"),
    upstreams       = require("apisix.admin.upstreams"),
    consumers       = require("apisix.admin.consumers"),
    schema          = require("apisix.admin.schema"),
    ssl             = require("apisix.admin.ssl"),
    plugins         = require("apisix.admin.plugins"),
    proto           = require("apisix.admin.proto"),
    global_rules    = require("apisix.admin.global_rules"),
    stream_routes   = require("apisix.admin.stream_routes"),
    plugin_metadata = require("apisix.admin.plugin_metadata"),
    plugin_configs  = require("apisix.admin.plugin_config"),
}
 

因此,上面的curl請求將被/apisix/admin/upstreams.lua文件的put函數處理,看下put函數的實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- /apisix/admin/upstreams.lua文件
function _M.put(id, conf)
    -- 校驗請求數據的合法性
    local id, err = check_conf(id, conf, true)
    local key = "/upstreams/" .. id
    core.log.info("key: ", key)
    -- 生成etcd中的配置數據
    local ok, err = utils.inject_conf_with_prev_conf("upstream", key, conf)
    -- 寫入etcd
    local res, err = core.etcd.set(key, conf)
end

-- /apisix/core/etcd.lua
local function set(key, value, ttl)
    local res, err = etcd_cli:set(prefix .. key, value, {prev_kv = true, lease = data.body.ID})
end
 

最終新配置被寫入etcd中。可見,Nginx會校驗數據再寫入etcd,這樣其他Worker進程、Nginx節點都將通過watch機制接收到正確的配置。上述流程你可以通過error.log中的日誌驗證:

1
2021/08/03 17:15:28 [info] 16437#16437: *23572 [lua] upstreams.lua:72: key: /upstreams/1, client: 127.0.0.1, server: _, request: "PUT /apisix/admin/upstreams/1 HTTP/1.1", host: "127.0.0.1:9080"
 

爲什麼新配置不reload就可以生效?

我們再來看admin請求執行完Nginx Worker進程可以立刻生效的原理。

開源版Nginx的請求匹配是基於3種不同的容器進行的:

  1. 將靜態哈希表中的server_name配置與請求的Host域名匹配,詳見《HTTP請求是如何關聯Nginx server{}塊的?》
  2. 其次將靜態Trie前綴樹中的location配置與請求的URI匹配,詳見《URL是如何關聯Nginx location配置塊的?》
  3. 在上述2個過程中,如果含有正則表達式,則基於數組順序(在nginx.conf中出現的次序)依次匹配。

上述過程雖然執行效率極高,卻是寫死在find_config階段及Nginx HTTP框架中的,一旦變更必須在nginx -s reload後才能生效!因此,APISIX索性完全拋棄了上述流程!

從nginx.conf中可以看到,訪問任意域名、URI的請求都會匹配到http_access_phase這個lua函數:

1
2
3
4
5
6
7
8
9
server {
    server_name _;
    location / {
        access_by_lua_block {
            apisix.http_access_phase()
        }
        proxy_pass      $upstream_scheme://apisix_backend$upstream_uri;
    }
}
 

而在http_access_phase函數中,將會基於1個用C語言實現的基數前綴樹匹配Method、域名和URI(僅支持通配符,不支持正則表達式),這個庫就是lua-resty-radixtree。每當路由規則發生變化,Lua代碼就會重建這棵基數樹:

1
2
3
4
5
6
7
function _M.match(api_ctx)
    if not cached_version or cached_version ~= user_routes.conf_version then
        uri_router = base_router.create_radixtree_uri_router(user_routes.values,
                                                             uri_routes, false)
        cached_version = user_routes.conf_version
    end
end
 

這樣,路由變化後就可以不reload而使其生效。Plugin啓用、參數及順序調整的規則與此類似。

最後再提下Script,它與Plugin是互斥的。之前的動態調整改的只是配置,事實上Lua JIT的及時編譯還提供了另外一個殺手鐗loadstring,它可以將字符串轉換爲Lua代碼。因此,在etcd中存儲Lua代碼並設置爲Script後,就可以將其傳送到Nginx上處理請求了。

小結

Nginx集羣的管理必須依賴中心化配置組件,而高可靠又具備watch推送機制的etcd無疑是最合適的選擇!雖然當下Resty生態沒有gRPC客戶端,但每個Worker進程直接通過HTTP/1.1協議同步etcd配置仍不失爲一個好的方案。

動態修改Nginx配置的關鍵在於2點:Lua語言的靈活度遠高於nginx.conf語法,而且Lua代碼可以通過loadstring從外部數據中導入!當然,爲了保障路由匹配的執行效率,APISIX通過C語言實現了前綴基數樹,基於Host、Method、URI進行請求匹配,在保障動態性的基礎上提升了性能。

APISIX擁有許多優秀的設計,本文僅討論了Nginx集羣的動態管理,下篇文章再來分析Lua Plugin的設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章