前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >APISIX架构分析:如何动态管理Nginx集群?

APISIX架构分析:如何动态管理Nginx集群?

作者头像
陶辉
发布2023-10-18 11:18:34
9480
发布2023-10-18 11:18:34
举报
文章被收录于专栏:陶辉笔记

开源版Nginx最为人诟病的就是不具备动态配置、远程API及集群管理的能力,而APISIX作为CNCF毕业的开源七层网关,基于etcd、Lua实现了对Nginx集群的动态管理。

让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的客户端会立刻收到通知,如下图所示:

因此,不同于Orange采用MySQL、Kong采用PostgreSQL作为配置中心(这二者同样是基于OpenResty实现的API Gateway),APISIX采用了etcd作为中心化的配置组件。

因此,你可以在生产环境的APISIX中通过etcdctl看到如下的类似配置:

代码语言:javascript
复制
# 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中修改,比如:

代码语言:javascript
复制
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及定时器事件:

代码语言:javascript
复制
//参见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语言:

代码语言:javascript
复制
//参见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语言形式暴露了出来,如下图所示:

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函数启动定时器:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
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函数:

代码语言:javascript
复制
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对象:

代码语言:javascript
复制
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定时器:

代码语言:javascript
复制
function _M.new(key, opts)
    ngx_timer_at(0, _automatic_fetch, obj)
end

_automatic_fetch函数会反复执行sync_data函数(包装到xpcall之下是为了捕获异常):

代码语言:javascript
复制
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源码:

代码语言:javascript
复制
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:

然而,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目录:

我们可以验证下watchdir函数的实现细节:

代码语言:javascript
复制
-- 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就会生成以下配置:

代码语言:javascript
复制
server {
    listen 9080 default_server reuseport;

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

这样,Nginx接收到的/apisix/admin请求将被http_admin函数处理:

代码语言:javascript
复制
-- /apisix/init.lua文件
function _M.http_admin()
    local ok = router:dispatch(get_var("uri"), {method = get_method()})
end

admin接口能够处理的API参见github文档,其中,当method方法与URI不同时,dispatch会执行不同的处理函数,其依据如下:

代码语言:javascript
复制
-- /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上游时:

代码语言:javascript
复制
# 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):

代码语言:javascript
复制
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函数的内容:

代码语言:javascript
复制
-- /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字典决定的:

代码语言:javascript
复制
-- /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函数的实现:

代码语言:javascript
复制
-- /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中的日志验证:

代码语言:javascript
复制
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配置块的?》
  1. 在上述2个过程中,如果含有正则表达式,则基于数组顺序(在nginx.conf中出现的次序)依次匹配。

上述过程虽然执行效率极高,却是写死在find_config阶段及Nginx HTTP框架中的,一旦变更必须在nginx -s reload后才能生效!因此,APISIX索性完全抛弃了上述流程!

从nginx.conf中可以看到,访问任意域名、URI的请求都会匹配到http_access_phase这个lua函数:

代码语言:javascript
复制
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代码就会重建这棵基数树:

代码语言:javascript
复制
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的设计。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-08-102,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基于etcd watch机制的配置同步方案
    • ngx.timer.at定时器
      • Nginx的红黑树定时器
      • OpenResty的Lua定时器
      • APISIX基于定时器实现的watch机制
    • lua-resty-etcd库的HTTP/1.1协议
    • APISIX配置与插件的远程变更
      • 通过Nginx的/apisix/admin/接口修改配置
        • 为什么新配置不reload就可以生效?
        • 小结
        相关产品与服务
        负载均衡
        负载均衡(Cloud Load Balancer,CLB)提供安全快捷的四七层流量分发服务,访问流量经由 CLB 可以自动分配到多台后端服务器上,扩展系统的服务能力并消除单点故障。轻松应对大流量访问场景。 网关负载均衡(Gateway Load Balancer,GWLB)是运行在网络层的负载均衡。通过 GWLB 可以帮助客户部署、扩展和管理第三方虚拟设备,操作简单,安全性强。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档