前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解nginx stream proxy 模块的ssl连接原理

深入理解nginx stream proxy 模块的ssl连接原理

作者头像
码农心语
发布2024-04-09 15:53:17
2510
发布2024-04-09 15:53:17
举报
文章被收录于专栏:码农心语码农心语

1. 源起

  我一直来对ssl建立连接的过程一知半解,以前分析nginx代码的时候一旦碰到ssl连接部分的代码都是直接跳过,前面在分析ngx_http_upstream_dynamic_module的时候正好想到了是不是可以给它添加一个能够支持https健康检查的功能,所以今天决定沉下心来仔细分析一下nginx本身的与上游服务器建立连接的实现逻辑。

  为了简单起见,我决定选用ngx_stream_proxy_module模块作为分析学习的目标,因为相对于ngx_http_stream_proxy_module来说,前者逻辑上更加纯粹,少了七层的业务逻辑,让我能够更加专注地去分析关于ssl连接处理逻辑部分。

  希望这次通过分析,能够对ssl连接的建立以及其后续的读写交互的实现逻辑有个整体的把握,在此基础上,将来为ngx_http_upstream_dynamic_module实现一个能够支持https主动健康检测的功能。

  这次,我准备采用官方原生的最新稳定版nginx 1.24.0作为分析对象。为什么不用tengine呢?因为现在的tengine还夹杂了国密openssl的支持逻辑,采用官方原生版本更加纯粹,让分析的目标更加聚焦。

2. 分析验证环境的配置

  配置一个分析验证环境来进行测试分析还是非常简单的,过程如下:

  1. 从官网下载好nginx 1.24.0版本后进行解压,然后用以下命令进行配置:

代码语言:javascript
复制
./configure --prefix=`pwd` --with-stream --with-stream_ssl_module

  这样nginx从源码层面就开启了支持ssl的TCP代理能力。

  2. 对nginx.conf文件进行配置,配置内容如下:

代码语言:javascript
复制
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


stream {
  server {
    listen 9000;
    proxy_ssl on;               /* 连接上游服务器采用ssl协议 */
    proxy_pass 104.193.88.77:443;
  }
}

  3. 启动nginx,进行curl测试:

代码语言:javascript
复制
 curl "http://127.0.0.1:9000/test/" -v
 
*   Trying 127.0.0.1:9000...
* Connected to 127.0.0.1 (127.0.0.1) port 9000 (#0)
> GET /test/ HTTP/1.1
> Host: 127.0.0.1:9000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 Internal Server Error
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
< Date: Wed, 07 Feb 2024 02:57:01 GMT
< Server: bfe
< 
* Connection #0 to host 127.0.0.1 left intact

  从上面的信息中已经可以看到上游服务器已经响应了,只不过它响应是500错误,这个无所谓,至少表明https透明代理的功能已经正常工作了。

3. 源码分析

  本文主要聚焦在ssl连接逻辑的分析,所以中间会跳过和ssl逻辑不太相关的代码,虽然可能这部分对nginx本身的功能逻辑非常重要。另外,本文也假设只是TCP代理,不对UDP代理进行分析。

  下面直接进入主题,从代理模块的请求入口点开始分析。

3.1 代理模块的请求入口点分析

  代理模块的请求入口点是ngx_stream_proxy_handler函数,一旦客户端和nginx建立了TCP连接后,nginx就会调用代理模块的这个函数,开始与上游服务器建立连接。

  该函数源码主要就是以下列出的三个步骤:

代码语言:javascript
复制
static void
ngx_stream_proxy_handler(ngx_stream_session_t *s)
{
  /* 创建ngx_stream_upstream_t上下文,对它进行必要的初始化,
     并关联到ngx_stream_session_t中 */
  /* 如果上游服务器的地址已经解析好就调用ngx_stream_proxy_connect开始连接上游服务器 */
  /* 如果上游服务器的地址需要域名解析则开启异步解析流程 */   
}

  因此,我们需要重点关注的是ngx_stream_proxy_connect,它负责与上游服务器建立TCP连接。

3.2 发起与上游服务器的连接

代码语言:javascript
复制
static void
ngx_stream_proxy_connect(ngx_stream_session_t *s)
{
    ngx_int_t                     rc;
    ngx_connection_t             *c, *pc;
    ngx_stream_upstream_t        *u;
    ngx_stream_proxy_srv_conf_t  *pscf;

    c = s->connection;

    c->log->action = "connecting to upstream";

    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);

    u = s->upstream;

    u->connected = 0;
    u->proxy_protocol = pscf->proxy_protocol;

    if (u->state) {
        u->state->response_time = ngx_current_msec - u->start_time;
    }

    u->state = ngx_array_push(s->upstream_states);
    if (u->state == NULL) {
        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
        return;
    }

    ngx_memzero(u->state, sizeof(ngx_stream_upstream_state_t));

    u->start_time = ngx_current_msec;

    u->state->connect_time = (ngx_msec_t) -1;
    u->state->first_byte_time = (ngx_msec_t) -1;
    u->state->response_time = (ngx_msec_t) -1;
  
  /* 创建SOCKET,并发起异步连接请求*/
    rc = ngx_event_connect_peer(&u->peer);

    ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "proxy connect: %i", rc);

    if (rc == NGX_ERROR) {
        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
        return;
    }

    u->state->peer = u->peer.name;

    if (rc == NGX_BUSY) {
        ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams");
        ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY);
        return;
    }

    if (rc == NGX_DECLINED) {
      /* 连接失败,则通过负载均衡请求下一个上游服务器 */
        ngx_stream_proxy_next_upstream(s);
        return;
    }

    /* rc == NGX_OK || rc == NGX_AGAIN || rc == NGX_DONE */

    pc = u->peer.connection;

    pc->data = s;
    pc->log = c->log;
    pc->pool = c->pool;
    pc->read->log = c->log;
    pc->write->log = c->log;

    if (rc != NGX_AGAIN) {
      /* 如果连接已经建立成功了,则进一步初始化 */
        ngx_stream_proxy_init_upstream(s);
        return;
    }

  /* 设置连接的epoll读写回调函数 */
    pc->read->handler = ngx_stream_proxy_connect_handler;
    pc->write->handler = ngx_stream_proxy_connect_handler;
  
  /* 开启连接超时定时器 */
    ngx_add_timer(pc->write, pscf->connect_timeout);
}

  这个函数就是发起向上游服务器的一个TCP连接请求,并设置epoll读写的回调函数,本身还和ssl没有太多的关系,其中几个关键步骤已经通过源码注释进行了解释。

3.3 连接回调

  一旦连接成功或者连接超时,nginx的epoll框架最终都会回调到ngx_stream_proxy_connect_handler函数。代码如下:

代码语言:javascript
复制
static void
ngx_stream_proxy_connect_handler(ngx_event_t *ev)
{
    ngx_connection_t      *c;
    ngx_stream_session_t  *s;

    c = ev->data;
    s = c->data;
  
  /* 连接超时,通过负载均衡请求下一个上游服务器 */
    if (ev->timedout) {
        ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT, "upstream timed out");
        ngx_stream_proxy_next_upstream(s);
        return;
    }
  /* 连接成功了,删除超时定时器 */
    ngx_del_timer(c->write);

    ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
                   "stream proxy connect upstream");
  
  /* 检验是否连接成功,如果没有成功,则通过负载均衡请求下一个上游服务器 */
    if (ngx_stream_proxy_test_connect(c) != NGX_OK) {
        ngx_stream_proxy_next_upstream(s);
        return;
    }
    /* 如果连接已经建立成功了,则进一步初始化 */
    ngx_stream_proxy_init_upstream(s);
}

  这里,进入到最关键的环节了,即ngx_stream_proxy_init_upstream。

3.4 TCP连接建立成功后为上下游数据透传做准备

  tcp连接建立成功后,需要在业务层面进一步进行初始化,这就是ngx_stream_proxy_init_upstream的功能。对于非ssl连接,那么tcp socket连接建立后就可以进入到本函数进行处理了;但是对于ssl连接,却需要两次进入本函数进行处理,第一次的时候会发现ssl握手还没有执行,就先跑去执行ssl 握手操作,等握手成功以后,会重新回调本函数执行和非ssl连接一样的后续流程。源码如下:

代码语言:javascript
复制
static void
ngx_stream_proxy_init_upstream(ngx_stream_session_t *s)
{
    u_char                       *p;
    ngx_chain_t                  *cl;
    ngx_connection_t             *c, *pc;
    ngx_log_handler_pt            handler;
    ngx_stream_upstream_t        *u;
    ngx_stream_core_srv_conf_t   *cscf;
    ngx_stream_proxy_srv_conf_t  *pscf;

    u = s->upstream;
    pc = u->peer.connection;

    cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module);

    if (pc->type == SOCK_STREAM
        && cscf->tcp_nodelay
        && ngx_tcp_nodelay(pc) != NGX_OK)
    {
        ngx_stream_proxy_next_upstream(s);
        return;
    }

    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);

#if (NGX_STREAM_SSL)

    if (pc->type == SOCK_STREAM && pscf->ssl_enable) {

        if (u->proxy_protocol) {
            if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) {
                return;
            }

            u->proxy_protocol = 0;
        }
    /* 如果开启了ssl但是ssl还没有进行握手,就先进行握手操作,
       握手成功后会重新回调本函数进行后续处理
    */
        if (pc->ssl == NULL) {
            ngx_stream_proxy_ssl_init_connection(s);
            return;
        }
    }

#endif

    c = s->connection;

    if (c->log->log_level >= NGX_LOG_INFO) {
        ngx_str_t  str;
        u_char     addr[NGX_SOCKADDR_STRLEN];

        str.len = NGX_SOCKADDR_STRLEN;
        str.data = addr;

        if (ngx_connection_local_sockaddr(pc, &str, 1) == NGX_OK) {
            handler = c->log->handler;
            c->log->handler = NULL;

            ngx_log_error(NGX_LOG_INFO, c->log, 0,
                          "%sproxy %V connected to %V",
                          pc->type == SOCK_DGRAM ? "udp " : "",
                          &str, u->peer.name);

            c->log->handler = handler;
        }
    }

    u->state->connect_time = ngx_current_msec - u->start_time;

    if (u->peer.notify) {
        u->peer.notify(&u->peer, u->peer.data,
                       NGX_STREAM_UPSTREAM_NOTIFY_CONNECT);
    }

    if (u->upstream_buf.start == NULL) {
        p = ngx_pnalloc(c->pool, pscf->buffer_size);
        if (p == NULL) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

        u->upstream_buf.start = p;
        u->upstream_buf.end = p + pscf->buffer_size;
        u->upstream_buf.pos = p;
        u->upstream_buf.last = p;
    }

    if (c->buffer && c->buffer->pos <= c->buffer->last) {
        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
                       "stream proxy add preread buffer: %uz",
                       c->buffer->last - c->buffer->pos);

        cl = ngx_chain_get_free_buf(c->pool, &u->free);
        if (cl == NULL) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

        *cl->buf = *c->buffer;

        cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module;
        cl->buf->temporary = (cl->buf->pos == cl->buf->last) ? 0 : 1;
        cl->buf->flush = 1;

        cl->next = u->upstream_out;
        u->upstream_out = cl;
    }
  /* proxy_protocol 协议处理,发送proxy_protocol请求报文 */
    if (u->proxy_protocol) {
        ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0,
                       "stream proxy add PROXY protocol header");

        cl = ngx_chain_get_free_buf(c->pool, &u->free);
        if (cl == NULL) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

        p = ngx_pnalloc(c->pool, NGX_PROXY_PROTOCOL_V1_MAX_HEADER);
        if (p == NULL) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

        cl->buf->pos = p;

        p = ngx_proxy_protocol_write(c, p,
                                     p + NGX_PROXY_PROTOCOL_V1_MAX_HEADER);
        if (p == NULL) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }

        cl->buf->last = p;
        cl->buf->temporary = 1;
        cl->buf->flush = 0;
        cl->buf->last_buf = 0;
        cl->buf->tag = (ngx_buf_tag_t) &ngx_stream_proxy_module;

        cl->next = u->upstream_out;
        u->upstream_out = cl;

        u->proxy_protocol = 0;
    }
  /* 设置上下游数据传输速率 */
    u->upload_rate = ngx_stream_complex_value_size(s, pscf->upload_rate, 0);
    u->download_rate = ngx_stream_complex_value_size(s, pscf->download_rate, 0);

    u->connected = 1;
    
  /* 这里重新设置连接的epoll读写回调函数 */
    pc->read->handler = ngx_stream_proxy_upstream_handler;
    pc->write->handler = ngx_stream_proxy_upstream_handler;

    if (pc->read->ready) {
        ngx_post_event(pc->read, &ngx_posted_events);
    }

    ngx_stream_proxy_process(s, 0, 1);
}

  这个函数和SSL连接相关的重点部分就是以下这部分代码了。

代码语言:javascript
复制
#if (NGX_STREAM_SSL)

if (pc->type == SOCK_STREAM && pscf->ssl_enable) {

  if (u->proxy_protocol) {
    if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) {
      return;
    }

    u->proxy_protocol = 0;
  }

  if (pc->ssl == NULL) {
    ngx_stream_proxy_ssl_init_connection(s);
    return;
  }
}
#endif
代码语言:javascript
复制

  这段代码首先判断是否开启了proxy_protocol协议向上游服务器发起请求,关于proxy_protocol的内容可以参考网络协议之:haproxy的Proxy Protocol代理协议,这里略过。  接下去就是关键的初始化连接的ssl上下文了“ngx_stream_proxy_ssl_init_connection”。

  连接建立成功后(包括非ssl的tcp连接建立和ssl的握手成功两种情况),最后都需要设置读写回调函数:

代码语言:javascript
复制
    pc->read->handler = ngx_stream_proxy_upstream_handler;
    pc->write->handler = ngx_stream_proxy_upstream_handler;
代码语言:javascript
复制

  在这个ngx_stream_proxy_upstream_handler回调函数中,可以预见就是接收数据,然后发送到对端,这里的逻辑和ssl关系不大,不再赘述。  但是有一个问题,普通的tcp连接和ssl连接在ngx_stream_proxy_upstream_handler里面最终都要操作系统的recv/send 或者read/write 之类的函数进行socket数据的读写,但是ssl连接通过操作系统的这些函数读取到的肯定是ssl加密后的数据,而write也必然需要发送加密的数据,这是如何做到的呢?这里暂且留一个悬念,留待3.7节进行说明。

3.5 TCP连接的ssl上下文初始化

代码语言:javascript
复制
static void
ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s)
{
    ngx_int_t                     rc;
    ngx_connection_t             *pc;
    ngx_stream_upstream_t        *u;
    ngx_stream_proxy_srv_conf_t  *pscf;

    u = s->upstream;
    
  /* 获取与上游的连接 */
    pc = u->peer.connection;
    
  /* 获取stream proxy模块配置*/
    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);

  /* 创建ssl上下文,并将当前的socket句柄绑定到ssl上下文中 */
    if (ngx_ssl_create_connection(pscf->ssl, pc, NGX_SSL_BUFFER|NGX_SSL_CLIENT)
        != NGX_OK)
    {
        ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
        return;
    }

  /* 如果设置了上游服务器需要校验客户端证书或者通过proxy_ssl_server_name设置了SNI则
     需要将上游服务器的hostname绑定到ssl上下文中, ngx_stream_proxy_ssl_name函数
     最终会调用SSL_set_tlsext_host_name函数进行绑定
  */
    if (pscf->ssl_server_name || pscf->ssl_verify) {
        if (ngx_stream_proxy_ssl_name(s) != NGX_OK) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }
    }

  /* 如果设置了客户端证书, 这里进行证书的绑定 */
    if (pscf->ssl_certificate
        && pscf->ssl_certificate->value.len
        && (pscf->ssl_certificate->lengths
            || pscf->ssl_certificate_key->lengths))
    {
        if (ngx_stream_proxy_ssl_certificate(s) != NGX_OK) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }
    }

  /* 如果开启了ssl会话的复用功能,这里进行相关设置 */
    if (pscf->ssl_session_reuse) {
        pc->ssl->save_session = ngx_stream_proxy_ssl_save_session;

        if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) {
            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
            return;
        }
    }

    s->connection->log->action = "SSL handshaking to upstream";
  
  /* 发起调用openssl底层接口进行异步ssl握手 */
    rc = ngx_ssl_handshake(pc);

    if (rc == NGX_AGAIN) {
    
    /* 如果异步握手操作尚未完成,则设置连接超时定时器 */
        if (!pc->write->timer_set) {
            ngx_add_timer(pc->write, pscf->connect_timeout);
        }

    /* 设置ssl握手成功后的回调函数 */
        pc->ssl->handler = ngx_stream_proxy_ssl_handshake;
        return;
    }

  /* openssl底层握手已经成功,进行后续的处理 */
    ngx_stream_proxy_ssl_handshake(pc);
}

  这个函数的逻辑基本上可以分为两个部分,第一部分是创建ssl的上下文然后准备ssl握手的相关信息;第二部分是发起异步ssl握手操作。由于是异步操作,因此可能是立即完成了,或者要等后续epoll框架等待网络i/o事件发生后才能完成,这个时候就需要设置回调函数ngx_stream_proxy_ssl_handshake来接收完成事件。无论怎样,只要连接最终ssl握手成功,都会进入到ngx_stream_proxy_ssl_handshake函数中。

3.6 ssl握手成功后的处理

 接下来在来看看ngx_stream_proxy_ssl_handshake的实现:

代码语言:javascript
复制
static void
ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc)
{
    long                          rc;
    ngx_stream_session_t         *s;
    ngx_stream_upstream_t        *u;
    ngx_stream_proxy_srv_conf_t  *pscf;

    s = pc->data;

    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);

    if (pc->ssl->handshaked) {  /* 如果已经握手成功 */

        if (pscf->ssl_verify) { /* 如果设置了上游服务器证书有效性验证则需要检查验证结果*/
            rc = SSL_get_verify_result(pc->ssl->connection);

            if (rc != X509_V_OK) {
                ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                              "upstream SSL certificate verify error: (%l:%s)",
                              rc, X509_verify_cert_error_string(rc));
                goto failed;
            }

            u = s->upstream;
            
      /* 检查握手收到的上游服务器响应信息中的服务器hostname是否和期望的一致*/
            if (ngx_ssl_check_host(pc, &u->ssl_name) != NGX_OK) {
                ngx_log_error(NGX_LOG_ERR, pc->log, 0,
                              "upstream SSL certificate does not match \"%V\"",
                              &u->ssl_name);
                goto failed;
            }
        }
    
    /* 握手已经成功了,可以删除连接超时定时器了 */
        if (pc->write->timer_set) {
            ngx_del_timer(pc->write);
        }
        
    /* 进入和非ssl一样的流程,为开始透传数据进行准备工作 */
        ngx_stream_proxy_init_upstream(s);

        return;
    }

failed:
  /* 如果握手失败,则选择下一个可用的上游服务器进行连接 */
    ngx_stream_proxy_next_upstream(s);
}

  可以很清晰地看到,ssl连接最终还是回调了ngx_stream_proxy_init_upstream准备数据透传工作。ngx_stream_proxy_init_upstream的分析参考3.4节。

3.7 连接数据的收与发

  以上ssl连接建立成功后,接下去需要进行数据的收和发,因为和上游建立的是ssl连接,那么收到的上游服务器的报文需要解密后发送给下游客户端,反过来,从下游客户端收到的报文又需要经过加密后发送给上游服务器。加解密这个过程必然是openssl帮我们来解决,如果openssl的上层应用能够透明使用那就再好不过了,事实上也的确如此。如下图所示:

  在上文3.5节中没有分析到的ngx_ssl_handshake函数内部,有一段如下代码:

代码语言:javascript
复制
c->recv = ngx_ssl_recv;
c->send = ngx_ssl_write;
c->recv_chain = ngx_ssl_recv_chain;
c->send_chain = ngx_ssl_send_chain;

c->read->ready = 1;
c->write->ready = 1;

  意思是握手成功后,将socket的读写函数替换为对应的ssl的读写函数,譬如ngx_ssl_recv它就是对openssl中对应的SSL_read进行了二次封装,其他几个函数也是类似的。具体在这里就不再展开了。

  再补充一下,如果是非ssl连接,nginx默认进行如下设置:

代码语言:javascript
复制
c->recv = ngx_recv;
c->send = ngx_send;
c->recv_chain = ngx_recv_chain;
c->send_chain = ngx_send_chain;

而这个是在ngx_event_connect_peer函数中完成的。

  至此,ngx_stream_upstream_module的ssl连接的处理过程就全部分析完毕了。总体上来说处理过程还是比较清晰了,后面有时间就可以来考虑一下如何利用这个分析的结果来实现https主动健康检测功能了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-02-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农心语 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 分析验证环境的配置
  • 3. 源码分析
    • 3.1 代理模块的请求入口点分析
      • 3.2 发起与上游服务器的连接
        • 3.3 连接回调
          • 3.4 TCP连接建立成功后为上下游数据透传做准备
            • 3.5 TCP连接的ssl上下文初始化
              • 3.6 ssl握手成功后的处理
                • 3.7 连接数据的收与发
                相关产品与服务
                负载均衡
                负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档