现在你的直觉里浮现这么个情景: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;
};
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