nginx之upstream机制

1. 概述

upstream机制是事件驱动框架与HTTP框架的综合,它既属于HTTP框架的一部分,又可以处理所有基于TCP的应用层协议(不限于HTTP)。它不仅没有任何阻塞地实现了Nginx与上游服务器的交互,同时又很好地解决了一个请求、多个TCP连接、多个读/写事件间的复杂关系。

为了帮助Nginx实现反向代理功能,upstream机制除了提供基本的与上游交互的功能之外,还实现了转发上游应用层协议的响应包体到下游客户端的功能(与下游之间当然还是使用HTTP)。

在这些过程中,upstream机制使用内存时极其“节省”,特别是在转发响应包体时,它从不会把一份上游的协议包复制多份。考虑到上下游间网速的不对称,upstream机制还提供了以大内存和磁盘文件来缓存上游响应的功能。

Nginx访问上游服务器的流程大致可以分为以下6个阶段:启动upstream机制、连接上游服务器、向上游服务器发送请求、接收上游服务器的响应包头、处理接收到的响应包体、结束请求。

upstream机制其实是由ngx_http_upstream_module模块实现的,它是一个HTTP模块,使用upstream机制时客户端的请求必须基于HTTP。

基于事件驱动架构的upstream机制所要访问的就是所有支持TCP的上游服务器(下游协议是HTTP,而上游协议可以是基于TCP的任何协议)。因此,既有ngx_http_proxy_module模块基于upstream机制实现了HTTP的反向代理功能,也有类似ngx_http_memcached_module的模块基于upstream机制使得请求可以访问memcached服务器。

基本结构

  • ngx_http_upstream_t

    struct ngx_http_upstream_s {
        // 处理读事件的回调方法,每一个阶段都有不同的read_event_handler
        ngx_http_upstream_handler_pt     read_event_handler;
        // 写事件回调,每一个阶段都有不同的write_event_handler
        ngx_http_upstream_handler_pt     write_event_handler;
    
        // 表示主动向上游服务器发起的连接
        ngx_peer_connection_t            peer;
    
        // 打开缓存时,会使用pipe成员来向下游客户端转发响应
        ngx_event_pipe_t                *pipe;
    
        ngx_chain_t                     *request_bufs;
    
    	// 定义了向下游发送响应的方式
        ngx_output_chain_ctx_t           output;
        ngx_chain_writer_ctx_t           writer;
    
        // 使用upstream机制时的各种配置
        ngx_http_upstream_conf_t        *conf;
        ngx_http_upstream_srv_conf_t    *upstream;
    #if (NGX_HTTP_CACHE)
        ngx_array_t                     *caches;
    #endif
    
        // 直接转发响应时,会把headers_in中设置的头部添加到要发送到下游客户端的响应头部headers_out中
        ngx_http_upstream_headers_in_t   headers_in;
    
        // 用于解析主机域名
        ngx_http_upstream_resolved_t    *resolved;
    
        ngx_buf_t                        from_client;
    
        // 接收上游服务器响应包头的缓冲区
        ngx_buf_t                        buffer;
        // 表示来自上游服务器的响应包体的长度
        off_t                            length;
    
        // 转发响应时,这个链表指向上一次向下游转发响应到现在这段时间内接收自上游的缓存响应;不需要转发时,指向的是包体。
        // 以下3个都是在buffer为0时使用
        ngx_chain_t                     *out_bufs;
        // 转发响应式,表示上一次向下游转发响应时没有发送完的内容
        ngx_chain_t                     *busy_bufs;
        // 这个链表将用于回收out_bufs中已经发送给下游的ngx_buf_t结构体
        ngx_chain_t                     *free_bufs;
    
        // 处理包体前的初始化方法
        ngx_int_t                      (*input_filter_init)(void *data);
        // 处理包体的方法
        ngx_int_t                      (*input_filter)(void *data, ssize_t bytes);
        // 用于传递HTTP模块自定义的数据结构
        void                            *input_filter_ctx;
    
    #if (NGX_HTTP_CACHE)
        ngx_int_t                      (*create_key)(ngx_http_request_t *r);
    #endif
        // HTTP模块实现的create_request方法用于构造发往上游服务器的请求
        ngx_int_t                      (*create_request)(ngx_http_request_t *r);
        // 与上游服务器的通信失败后 ,重试时使用
        ngx_int_t                      (*reinit_request)(ngx_http_request_t *r);
        // 解析上游服务器返回响应的包头
        ngx_int_t                      (*process_header)(ngx_http_request_t *r);
        void                           (*abort_request)(ngx_http_request_t *r);
        // 请求结束时会调用
        void                           (*finalize_request)(ngx_http_request_t *r,
                                             ngx_int_t rc);
        // 在上游返回的响应出现Location或者Refresh头部表示重定向时,使用
        ngx_int_t                      (*rewrite_redirect)(ngx_http_request_t *r,
                                             ngx_table_elt_t *h, size_t prefix);
        ngx_int_t                      (*rewrite_cookie)(ngx_http_request_t *r,
                                             ngx_table_elt_t *h);
    
        ngx_msec_t                       start_time;
    
    	// // 用于表示上游响应的错误码、包体长度等信息
        ngx_http_upstream_state_t       *state;
    
        ngx_str_t                        method;
        ngx_str_t                        schema;
        ngx_str_t                        uri;
    
    #if (NGX_HTTP_SSL || NGX_COMPAT)
        ngx_str_t                        ssl_name;
    #endif
    
        ngx_http_cleanup_pt             *cleanup;
    
        // 是否指定文件缓存路径的标志位
        unsigned                         store:1;
        unsigned                         cacheable:1;
        unsigned                         accel:1;
        unsigned                         ssl:1;
    #if (NGX_HTTP_CACHE)
        unsigned                         cache_status:3;
    #endif
    	// 向下游转发上游的响应包体时,是否开启更大的内存及临时磁盘文件用于缓存来不及发送到下游的响应包体
        unsigned                         buffering:1;
        unsigned                         keepalive:1;
        unsigned                         upgrade:1;
    
        unsigned                         request_sent:1;
        unsigned                         request_body_sent:1;
        unsigned                         request_body_blocked:1;
        unsigned                         header_sent:1;
    };
    
  • ngx_http_upstream_conf_t

    它会影响访问上游服务器的方式。同时,该结构体中的大量成员是与如何转发上游服务的包体的。

    typedef struct {
        // 它会定义上游服务器的配置
        ngx_http_upstream_srv_conf_t    *upstream;
    
        // 建立TCP 连接的超时时间,实际上就是写事件添加到定时器中时设置的超时时间
        ngx_msec_t                       connect_timeout;
        // 发送请求的超时时间,通常就是写事件添加到定时器中设置的超时时间
        ngx_msec_t                       send_timeout;
        // 接收响应的超时时间,通常就是读事件添加到定时器中设置的超时时间
        ngx_msec_t                       read_timeout;
        ngx_msec_t                       next_upstream_timeout;
    
        // TCP 的SO_SNOLOWAT 选项,表示发送缓冲区的下限
        size_t                           send_lowat;
        // 定义了接收头部的缓冲区分配的内存大小(ngx_http_upstream_t 中的buffer 缓冲区)
        size_t                           buffer_size;
    
        size_t                           limit_rate;
    
        // 会设置到ngx_event_pipe_t 结构体的busy_size 成员中
        size_t                           busy_buffers_size;
        // 指定了临时文件的最大长度。它将限制ngx_event_pipe_t 结构体中的temp_file
        size_t                           max_temp_file_size;
        // 表示将缓冲区中的响应写入临时文件时一次写入字符流的最大长度
        size_t                           temp_file_write_size;
    
        size_t                           busy_buffers_size_conf;
        size_t                           max_temp_file_size_conf;
        size_t                           temp_file_write_size_conf;
    
        // 以缓存响应的方式转发上游服务器的包体时所使用的内存大小
        ngx_bufs_t                       bufs;
    
        /* ngx_http_upstream_t 结构体中保存解析完的包头的headers_in 成员
        	gnore_headers 可以按照二进制位使得upstream 在转发包头时跳过对某些头部的处理*/
        ngx_uint_t                       ignore_headers;
        // 以二进制位来表示一些错误码,如果处理上游响应时发现这些错误码,那么在没有将响应转发给下游客户端时,将会选择下一个上游服务器
        ngx_uint_t                       next_upstream;
        // store_access 表示所创建的目录、文件的权限
        ngx_uint_t                       store_access;
        ngx_uint_t                       next_upstream_tries;
        // 决定转发响应方式的标志位,buffering 为1 时表示打开缓存,这时认为上游的网速快于下游的网速
        ngx_flag_t                       buffering;
        ngx_flag_t                       request_buffering;
        ngx_flag_t                       pass_request_headers;
        ngx_flag_t                       pass_request_body;
    
        /*当它为1 时,表示与上游服务器交互时将不检查Nginx 与下游客户端间的连接是否断开。*/
        ngx_flag_t                       ignore_client_abort;
        // 当解析上游响应的包头时,如果解析后设置到headers_in 结构体中的status_n 错误码大于400 ,则会试图把它与error_page 中指定的错误码相匹配,如果匹配上,则发送error_page 中指定的响应
        ngx_flag_t                       intercept_errors;
        // 为1 ,则会试图复用临时文件中已经使用过的空间
        ngx_flag_t                       cyclic_temp_file;
    
        ngx_flag_t                       force_ranges;
    	// 存放临时文件的路径
        ngx_path_t                      *temp_path;
    	// 不转发的头部
        ngx_hash_t                       hide_headers_hash;
        ngx_array_t                     *hide_headers;
        ngx_array_t                     *pass_headers;
    
        // 连接上游服务器时使用的本机地址
        ngx_http_upstream_local_t       *local;
    
        ngx_flag_t                       socket_keepalive;
    
    #if (NGX_HTTP_CACHE)
        ngx_shm_zone_t                  *cache_zone;
        ngx_http_complex_value_t        *cache_value;
    
        ngx_uint_t                       cache_min_uses;
        ngx_uint_t                       cache_use_stale;
        ngx_uint_t                       cache_methods;
    
        off_t                            cache_max_range_offset;
    
        ngx_flag_t                       cache_lock;
        ngx_msec_t                       cache_lock_timeout;
        ngx_msec_t                       cache_lock_age;
    
        ngx_flag_t                       cache_revalidate;
        ngx_flag_t                       cache_convert_head;
        ngx_flag_t                       cache_background_update;
    
        ngx_array_t                     *cache_valid;
        ngx_array_t                     *cache_bypass;
        ngx_array_t                     *cache_purge;
        ngx_array_t                     *no_cache;
    #endif
    	// 需要将上游的响应存放到文件中,store_lengths 将表示存放路径的长度,而store_values 表示存放路径
        ngx_array_t                     *store_lengths;
        ngx_array_t                     *store_values;
    
    #if (NGX_HTTP_CACHE)
        signed                           cache:2;
    #endif
        signed                           store:2;
        // 如果将intercept_404 标志位设为1 ,当上游返回404 时会直接转发这个错误码给下游,而不会去与error_page 进行比较
        unsigned                         intercept_404:1;
        // 动态地决定是以上游网速优先还是以下游网速优先,可以改变buffering的值
        unsigned                         change_buffering:1;
        unsigned                         pass_trailers:1;
        unsigned                         preserve_output:1;
    
    #if (NGX_HTTP_SSL || NGX_COMPAT)
        ngx_ssl_t                       *ssl;
        ngx_flag_t                       ssl_session_reuse;
    
        ngx_http_complex_value_t        *ssl_name;
        ngx_flag_t                       ssl_server_name;
        ngx_flag_t                       ssl_verify;
    
        ngx_http_complex_value_t        *ssl_certificate;
        ngx_http_complex_value_t        *ssl_certificate_key;
        ngx_array_t                     *ssl_passwords;
    #endif
    	// 使用upstream 的模块名称
        ngx_str_t                        module;
    
        NGX_COMPAT_BEGIN(2)
        NGX_COMPAT_END
    } ngx_http_upstream_conf_t;
    

2. 启动upstream

upstream机制的启动是从ngx_http_upstream_init中开始的,ngx_http_upstream_init方法将会根据ngx_http_upstream_conf_t中的成员初始化upstream,同时会开始连接上游服务器,下边来看看它的流程:

第2步中设置Nginx与下游客户端之间TCP连接的检查方法是:

  if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {
        r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;
        r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;
    }

第3步中调用请求中ngx_http_upstream_t结构体里由某个HTTP模块实现的create_request方法,构造发往上游服务器的请求。

第4步中,调用ngx_http_cleanup_add方法向这个请求main成员指向的原始请求中的cleanup链表末尾添加一个新成员,然后把handler回调方法设为ngx_http_upstream_cleanup,这意味着当请求结束时,一定会调用ngx_http_upstream_cleanup方法。

第5步调用ngx_http_upstream_connect方法向上游服务器发起连接。

要发起连接,需要找到发起连接的地址,通过host与端口去获取该配置ngx_http_upstream_srv_conf_t uscf

   for (i = 0; i < umcf->upstreams.nelts; i++) {

            uscf = uscfp[i];

            if (uscf->host.len == host->len
                && ((uscf->port == 0 && u->resolved->no_port)
                     || uscf->port == u->resolved->port)
                && ngx_strncasecmp(uscf->host.data, host->data, host->len) == 0)
            {
                goto found;
            }
        }
...
}

3. 与上游服务建立连接

ngx_http_upstream_connect方法就是用来连接上游服务器的,由于使用了非阻塞的套接字,当方法返回时与上游之间的TCP连接未必会成功建立,可能还需要等待上游服务器返回TCP的SYN/ACK包。因此,ngx_http_upstream_connect方法主要负责发起建立连接这个动作,如果这个方法没有立刻返回成功,那么需要在epoll中监控这个套接字,当它出现可写事件时,就说明连接已经建立成功了。

流程图如下:

这个过程说白了就是socket连接的connect过程,然后设置回调,等待返回的过程。具体的连接调用的是event的ngx_event_connect_peer进行的连接。

这几个回调如下:

   c->write->handler = ngx_http_upstream_handler;
    c->read->handler = ngx_http_upstream_handler;

    u->write_event_handler = ngx_http_upstream_send_request_handler;
    u->read_event_handler = ngx_http_upstream_process_header;

将这个连接ngx_connection_t上的读/写事件的handler回调方法都设置为ngx_http_upstream_handler。

将upstream机制的write_event_handler方法设为ngx_http_upstream_send_request_handler,实际上决定了向上游服务器发送请求的方法。

将upstream机制的read_event_handler方法为ngx_http_upstream_process_header,也就是接收上游服务器的响应的方法。

一般情况下,建立不会立马成功,也就是需要在需要在connection的回调中去完成,它的源码如下:

static void
ngx_http_upstream_handler(ngx_event_t *ev)
{
    ngx_connection_t     *c;
    ngx_http_request_t   *r;
    ngx_http_upstream_t  *u;

    c = ev->data;
    r = c->data;

    u = r->upstream;
    c = r->connection;

    ngx_http_set_log_request(c->log, r);

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http upstream request: \"%V?%V\"", &r->uri, &r->args);

    if (ev->delayed && ev->timedout) {
        ev->delayed = 0;
        ev->timedout = 0;
    }

    if (ev->write) {
        u->write_event_handler(r, u);

    } else {
        u->read_event_handler(r, u);
    }
	// c是来自客户端的连接,post请求的执行同前文
    ngx_http_run_posted_requests(c);
}

这个过程与上文的ngx_http_request_handler是类似的。调用upstream的回调。

4. 发送请求到上游服务

发送请求到上游服务器主要是依靠在回调ngx_http_upstream_send_request_handler中完成的,当然,如果connect直接建立了,也会在ngx_http_upstream_send_request开始发送。

先来看看回调:

static void
ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
    ngx_http_upstream_t *u)
{
    ngx_connection_t  *c;

    c = u->peer.connection;
    ...
    // 是否超时
    if (c->write->timedout) {
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
        return;
    }
    ...
    // header_sent为1时一定是已经解析完全部的上游响应包头,并且开始向下游发送HTTP的包头了。
    // 到此,是不应该继续向上游发送请求的,所以把write_event_handler设为任何工作都没有做的
    if (u->header_sent && !u->conf->preserve_output) {
        u->write_event_handler = ngx_http_upstream_dummy_handler;

        (void) ngx_handle_write_event(c->write, 0);

        return;
    }

    ngx_http_upstream_send_request(r, u, 1);
}

这里可以看到,在回调函数中,也主要是执行ngx_http_upstream_send_request来向上游服务发送请求。

来看看它的流程:

具体的发送工作是在ngx_http_upstream_send_request_body(r, u, do_write)中完成,然后它调用了ngx_output_chain方法向上游服务器发送ngx_http_upstream_t结构体中的request_bufs链表。

根据是否发送完成,选择继续发送,还是开始接收响应。

第7步,请求已经全部发送到上游服务器了,所以要防止可写事件再次触发而又调用ngx_http_upstream_send_request方法,把write_event_handler设为ngx_http_upstream_dummy_handler方法,该方法不会做任何事情。

5. 接收上游服务的响应

在ngx_http_upstream_t结构体中抽象出了process_header方法,由具体的HTTP模块实现的process_header来解析包头,upstream机制并没有对HTTP模块怎样实现process_header方法进行限制。

如果HTTP模块的目的是实现反向代理,不妨将接收到的包头按照上游的应用层协议与HTTP的关系,把解析出的一些头部适配到ngx_http_upstream_t结构体中的headers_in成员中,这样,upstream机制在图12-5的第8步就会自动地调用ngx_http_upstream_process_headers方法将这些头部设置到发送给下游客户端的HTTP响应包头中。

nginx对包体有3种处理方式:

  • 不转发响应,客户端请求派生出的子请求多半不需要转发包体。
  • 转发响应时下游网速优先,这时不需要开辟大块内存或者磁盘文件来缓存上游的响应。
  • 转发响应时上游网速优先,这时需要开辟内存或者磁盘文件缓存来自上游服务器的响应。

下边分别来看看这些这些接收过程。

接收响应头

接收上游服务的响应回调是在ngx_http_upstream_process_header中完成,它与write相同,也会被反复的调用。

整个过程如下:

主要的循环如下:

 for ( ;; ) {
     	// 调用recv方法在buffer缓冲区中读取上游服务器发来的响应
        n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);

        if (n == NGX_AGAIN) {
			...
            // 调用ngx_handle_read_event方法将读事件再添加到epoll中,等待读事件的下次触发
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_http_upstream_finalize_request(r, u,
                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
                return;
            }
            return;
        }

        if (n == 0) {
            ngx_log_error(NGX_LOG_ERR, c->log, 0,
                          "upstream prematurely closed connection");
        }

        if (n == NGX_ERROR || n == 0) {
      		// 该方法将会根据配置信息决定下一步究竟是重新发起upstream请求,还是结束当前请求
            ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
            return;
        }

        u->state->bytes_received += n;

        u->buffer.last += n;
		...
        // )调用HTTP模块实现的process_header方法解析响应头部,检测其返回值
        rc = u->process_header(r);

        if (rc == NGX_AGAIN) {

            if (u->buffer.last == u->buffer.end) {
                ngx_log_error(NGX_LOG_ERR, c->log, 0,
                              "upstream sent too big header");

                ngx_http_upstream_next(r, u,
                                       NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
                return;
            }

            continue;
        }

        break;
    }

	...
     /* rc == NGX_OK */
    u->state->header_time = ngx_current_msec - u->start_time;

    if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) {

        if (ngx_http_upstream_test_next(r, u) == NGX_OK) {
            return;
        }

        if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {
            return;
        }
    }
	/* 处理已经解析出的头部,该方法将会把已经解析出的头部
	设置到请求ngx_http_request_t结构体的headers_out成员中,
	这样在调用ngx_http_send_header方法发送响应包头给客户端时将会发送这些设置了的头部*/
    if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
        return;
    }
	// 调用ngx_http_upstream_send_response方法开始转发响应给客户端
    ngx_http_upstream_send_response(r, u);

下边的函数调用就到了ngx_http_upstream_send_response中,这里根据是否转发包体(header_only),以及是否缓存(buffering),进行了不同的处理。

下游网速优先转发包体

下游优先时,buffering标志为0,其过程如下:

 if (!u->buffering) {
		...
        if (u->input_filter == NULL) {
            // 若没有,则用默认的方法设置input_filter函数
            u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
            u->input_filter = ngx_http_upstream_non_buffered_filter;
            u->input_filter_ctx = r;
        }
		// 设置uptream的读事件回调,以及requst的写事件回调
        u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
        r->write_event_handler =
                             ngx_http_upstream_process_non_buffered_downstream;

        r->limit_rate = 0;
        r->limit_rate_set = 1;

        if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
            return;
        }

        if (clcf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) {
            ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
            return;
        }

        n = u->buffer.last - u->buffer.pos;
		// 确定缓冲区中是否有包体
        if (n) {
            u->buffer.last = u->buffer.pos;

            u->state->response_length += n;
			// 调用input_filter先处理响应包体
            if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
                return;
            }
			// 调用它来处理
            ngx_http_upstream_process_non_buffered_downstream(r);

        } else {
            // 清空buffer缓存
            u->buffer.pos = u->buffer.start;
            u->buffer.last = u->buffer.start;

            if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
                ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
                return;
            }
			// 是否有可读内容
            if (u->peer.connection->read->ready || u->length == 0) {
                ngx_http_upstream_process_non_buffered_upstream(r, u);
            }
        }

        return;
    }

下边不管是ngx_http_upstream_process_non_buffered_downstream,还是upstream的可读事件回调ngx_http_upstream_process_non_buffered_upstream,都会将调用转发包体函数:ngx_http_upstream_process_non_buffered_request, 该函数的流程如下:

这里一边读upstream中的内容,一边调用http模块实现的ngx_http_output_filter来往下游发送。

上游网速优先转发包体

前边讲到的是在ngx_http_upstream_send_response中buffering为0的情况,这里就是buffering为1的情况了。其过程如下:

第5步设置处理上游读事件回调方法为ngx_http_upstream_process_upstream。

第6步设置处理下游写事件的回调方法为ngx_http_upstream_process_downstream。

第7步调用ngx_http_upstream_process_upstream方法处理上游发来的响应包体。

ngx_event_pipe_t的方式是缓存转发的重点,这里先不做介绍了。

7. 结束upstream请求

upstream机制提供了一个类似于http的ngx_http_upstream_finalize_request用于结束upstream请求,除了直接调用ngx_http_upstream_finalize_request方法结束请求以外,还有两种独特的结束请求方法,分别是ngx_http_upstream_cleanup方法和ngx_http_upstream_next方法。

在启动upstream机制时,ngx_http_upstream_cleanup方法会挂载到请求的cleanup链表中,这样,HTTP框架在请求结束时就会调用ngx_http_upstream_cleanup方法。而ngx_http_upstream_cleanup方法实际上还是通过调用ngx_http_upstream_finalize_request来结束请求的。

当处理请求的流程中出现错误时,往往会调用ngx_http_upstream_next方法,当与上游的交互出现错误时,Nginx并不想立刻
认为这个请求处理失败,而是试图多给上游服务器一些机会。

8. 小结

upstream机制的流程是一个使用异步socket客户端的过程,如connect、send、recv的过程。同时,有是向下游服务转发的过程,这个转发是有http模块来实现的。

# nginx 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×