nginx源码分析之upstream设计

关于nginx的神秘武器江湖从没停止对它的渲染,没错的就是upstream。nginx有很多杀手锏比如高性能、高并发、强悍的稳定性。但是如果没有upstream它将失色很多。upstream让nginx成为代理服务器,可以连接fastcgi协议的php,可以连接memcached、redis、mongodb、mysql handlersocket,uwsgi协议的服务器,甚至一切服务器。本文将一步步揭开它的神秘色彩。

现在你的直觉里浮现这么个情景:upstream是nginx的一个神秘机制,它让nginx可以连接后端的某台服务器。不管后端有多少台服务器,它总能根据你指定的策略挑出其中一台,连接它、发送请求数据、得到后端服务器的响应,最后再响应给客户端。如果这个描述让你清楚upstream替nginx做了什么,我们将深度探索它的实现。不用担心它将比你想象中的简单很多。

nginx中如何使用upstream,配置如下:
upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;
    server backup1.example.com:8080   backup;
    server backup2.example.com:8080   backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

nginx的生命周期有3个重要过程:解析配置、启动进程、处理请求。我们将围绕这3个阶段分析upstream如何被实现。

1、解析配置:

nginx可以有多个upstream,每个upstream有自己的名称,比如上面的upstream。每个upstream对应着它指定的多台服务器信息。服务器可以是域名、ip、unix。最终会解析成可以socket操作的ip:port,所以如果是域名,通过getservername会转化成更多的服务器信息。然后proxy_pass会绑定到具体的某个upstream,以便nginx处理请求时找到usptream里的某个服务器。这里有个很重要的细节,如果proxy_pass的值不是upstream name,它会将它的值创建一个upstream,然后绑定它。这个也适用于fastcgi, memcached等其它用到upstream的模块。

结构体如下:

typedef struct {
    ngx_http_upstream_conf_t       upstream;
...
} ngx_http_proxy_loc_conf_t;


typedef struct {
    ngx_http_upstream_srv_conf_t    *upstream;


    ngx_msec_t                       connect_timeout;
    ngx_msec_t                       send_timeout;
    ngx_msec_t                       read_timeout;
    ngx_msec_t                       timeout;
    ngx_msec_t                       next_upstream_timeout;


    size_t                           send_lowat;
...
} ngx_http_upstream_conf_t;


struct ngx_http_upstream_srv_conf_s {
    ngx_http_upstream_peer_t         peer;
    void                           **srv_conf;


    ngx_array_t                     *servers;  /* ngx_http_upstream_server_t */


    ngx_uint_t                       flags;
    ngx_str_t                        host;
    u_char                          *file_name;
    ngx_uint_t                       line;
    in_port_t                        port;
    in_port_t                        default_port;
    ngx_uint_t                       no_port;  /* unsigned no_port:1 */
};


typedef struct {
    ngx_str_t                        name;
    ngx_addr_t                      *addrs;
    ngx_uint_t                       naddrs;
    ngx_uint_t                       weight;
    ngx_uint_t                       max_fails;
    time_t                           fail_timeout;


    unsigned                         down:1;
    unsigned                         backup:1;
} ngx_http_upstream_server_t;


typedef struct {
    ngx_http_upstream_init_pt        init_upstream;
    ngx_http_upstream_init_peer_pt   init;
    void                            *data;
} ngx_http_upstream_peer_t;


struct ngx_http_upstream_rr_peer_s {
    struct sockaddr                *sockaddr;
    socklen_t                       socklen;
    ngx_str_t                       name;
    ngx_str_t                       server;
        
    ngx_int_t                       current_weight;
    ngx_int_t                       effective_weight;
    ngx_int_t                       weight;


    ngx_uint_t                      conns;
};



upstreams = {}; server_infos = []; upstreams[upstream_name] = server_infos; server_infos = [server1, server2, ...];
umcf->upstreams = [ngx_http_upstream_srv_conf_t*, ngx_http_upstream_srv_conf_t*, ...];
plcf->upstream = ngx_http_upstream_conf_t;
plcf->upstream.upstream = ngx_http_upstream_srv_conf_t*;
plcf->upstream.upstream.servers = [ngx_http_upstream_server_t*, ngx_http_upstream_server_t*, ...];
plcf->upstream.upstream.servers[i].addrs = [ngx_addr_t*, ngx_addr_t*, ...];

在init_main发生后:
plcf->upstream.upstream.peer.init = ngx_http_upstream_init_round_robin_peer;
plcf->upstream.upstream.peer.data = [ngx_http_upstream_rr_peer_t*, ngx_http_upstream_rr_peer_t*, ...];

聪明的你可以预见到,在nginx处理请求时,如何找到具体的某台服务器就是根据peer的init和data计算出来的。

2、启动进程
松口气先,upstream在nginx的启动进程阶段,什么也没发生。

3、处理请求
处理请求要做的就是找到某台服务器,根据指定的协议发送请求,接收响应并解析,最后响应给客户端。

upstream是如何找到指定的某台服务器的呢?

nginx中有个结构体ngx_http_request_t:对应的就是请求


struct ngx_http_request_s {
    uint32_t                          signature;         /* "HTTP" */
    ngx_connection_t                 *connection;
    ...
    ngx_http_upstream_t              *upstream;
    ngx_array_t                      *upstream_states;
                                         /* of ngx_http_upstream_state_t */
    ...
};

struct ngx_http_upstream_s {
    ...
    ngx_peer_connection_t            peer;
    ...
};

struct ngx_peer_connection_s {
    ngx_connection_t                *connection;


    struct sockaddr                 *sockaddr;
    socklen_t                        socklen;
    ngx_str_t                       *name;


    ngx_uint_t                       tries;
    ngx_msec_t                       start_time;


    ngx_event_get_peer_pt            get;
    ngx_event_free_peer_pt           free;
    void                            *data;
};




解析配置阶段最后所说,nginx根据peer的init和data的处理计算。让request有了upstream,upstream有了peer,peer的socketaddr, socketlen有了值,这样nginx可以连接到具体的某台服务器了吧。

本来想针对这块写一大篇篇幅的,但是阅读源码的乐趣在于体会细节,写作一向只是梳理流程,除了这些结构体,其它都是根据这思路写下来,可能难免有失误。

3、upstream还有什么?
可以说upstream占了http的一半。定时器、超时处理、请求处理、如何构造请求、如何解析响应、长连接、策略等等。哪块小主题想了解更深入的,看阅读量再考虑继续深入。
nginx不止是http服务器,也是mail代理服务器,还是stream代理服务器。是的,1.9版本nginx开始支持stream(tcp)。但是http和stream都支持upstream,mail却不支持。
nginx也可以处理成websocket服务器,rtmp服务器。如果upstream的功能独立出http, stream,那将是非常出色的一次重构。

4、我想写的可能您感兴趣的
nginx的subrequest是非常神奇的一个机制,通过它可以做很酷的事情,比如我写过的nginx动态代理等,这个是否引起您的兴趣呢。告诉我您感兴趣的,包括模块、源码、nginx的消息等。nginx将支持javascrit,Igor Sysoev大神将实现自己的轻量级js虚拟机。 如果对lua的源码如何实现感兴趣,可以尝试写这块。^-^

本文结束!阅读原文: http://nglua.com/reads/5.html

你可能感兴趣的:(nginx源码分析之upstream设计)