关于ngixn中upstream机制的思考

网上搜过upstream相关分析的同学可能都已经有了大概的了解了,而且很多大牛分析的也很棒,我这里为什么还要多说几句呢?一来是总结自己的一些理解,二来是对前辈分析的一些补充,希望能带给大家更多启发。
先给出阿里技术牛们的总结和分析,供大家参考:
http://tengine.taobao.org/book/chapter_05.html

http://www.pagefault.info/?p=251

看nginx官方wiki给出的配置例子(稍作修改):

upstream backend { 
    server 211.20.10.11 weight=5;
    server 198.172.10.9:8080 weight=10;
}

server {
    location / {
        proxy_pass http://backend;
    }
}
(事先声明下,upstream中的后端连接选择算法不是这里讨论的重点,我们仅仅会涉及round robbin算法,像ip_hash或者更新的机制如keepalive不在讨论之列)
对于上述的upstream配置,在系统初始化阶段会创建一个结构来保存它,注意是该结构是初始化阶段分配的内存,它的生命周期不跟随一个具体的request。这里必须强调的一点就是,在分析nginx代码时,我们要记清楚什么结构的生存期是跟随request的,什么结构不是。这个对你从总体上把握系统的框架很有用。参考函数ngx_http_upstream_init_round_robin。

其实upstream模块的运作,主要的驱动是xxx_pass,比如上面的proxy_pass。除此之外,还有memcache_pass,fastcgi_pass等,upstream相关配置结构的建立,并不一定是非要配置upstream这个指令才会去做,有时proxy_pass直接就是跟一个可解析的域名,这个时候upstream的初始化工作也会正常运转起来,这里我们不讨论这个情况。

那么一个具体的request如何跟这个upstream配置系统相关联?一般是通过peer.init函数指针,在这里round robbin使用的是ngx_http_upstream_init_round_robin_peer,当然ip_hash有自己的处理函数。从配置上看,凡是请求到location /的request,他们都关联到同一个upstream配置,这点没有问题,我们设计也会这么做,但是既然大家公用一个结构,那么需不需要互斥呢?如果大家都要修改其中的某个成员。。。实际上,nginx中一个请求不会中多个进程中同时处理,一个request生老病死都在一个worker内。其次,由于在单个进程内,nginx非阻塞对各个请求请求进行异步处理,具体来讲,在一个请求处理在发生EAGAIN(一些系统调用,或者主动放弃情况)之前,是不会转去处理另一个请求的,所以也就不存在互斥的问题。好像扯远了。。。我们继续讨论。

通过示例配置,可以看出upstream结构管理了两个后端服务器,那么在使用时,通过选择优先级最高(round robbin)的一个后端,来发起连接。算法如何去选择,大家可以直接去读源码。这里我们关心的是,连接异常的后续处理,毕竟异常和及其细节处理的好坏直接决定一个服务是否稳定。

情况1:当前选择的后端机器,连接异常(超时或者出错)
因为这里的socket都是非阻塞的,所以我们直接connect往往不会立即成功,一般会返回经典的EINPROGRESS错误码,这种情况下,你需要做的就是加一个定时器,并且如果之前没有向epoll添加读事件的话,还要加一个read event。为什么这么做是必要的(事实上nginx就是这么做的)?首先,如果连接成功或者出错,我们注册的读事件会被epoll上报,我们在事件处理函数中,会做处理;如果连接超时了(如对端物理断网了),那么我们的超时定时器就会触发。这样,无论失败还是成功,这个连接我们都可以合理的处理掉,面对异常你不能置之不理。

那么超时或者出错之后,upstream需要做什么呢?nginx使用ngx_http_upstream_next来处理,超时和出错分别用状态NGX_HTTP_UPSTREAM_FT_TIMEOUT和NGX_HTTP_UPSTREAM_FT_ERROR表示。在ngx_http_upstream_next中,如果确实是某个后端连接出了问题,一般nginx会再次调用ngx_http_upstream_connect,来找其他可用的后端尝试连接。

这里值得的一提的是,一个后端连接出问题,会被nginx记小本本的。当所以的后端都出问题的时候(获取后端连接返回NGX_BUSY),nginx只好找备胎来用了,如果有的话。如果没有备胎,或者备胎也出问题了,那没辙, 502给你好了。

情况2:后端连接成功,但是nginx收到的响应头是异常的
这里的异常我们简单的认为成不是我们期望的状态码,比如我们想得到“200 OK”,但是却得到了 “404 Not found”或者其他。那nginx后续的处理怎样?我认为可以有两种处理,第一就是直接将这个响应发给客户端。这样做的问题就是,nginx无法感知后端机器上内容,也就是说当前连接的后端上面根本没有客户端想要的文件,但是其他机器上可能有。面对的后端是一个内容上面的集群,在内容上这样处理显然不合适。所以比较合适的处理是,让nginx再次去后端尝试。。。但是这样也不是办法,如果你有很多后端机器,只有一个有客户端想要的内容,那么运气差的话。。。

先不讨论集群方面的优化,我们看nginx是怎么处理的。

当upstream收到异常响应时,不得不提一个指令“proxy_next_upstream”,看官方介绍吧。

关于ngixn中upstream机制的思考_第1张图片

关于用法,描述的很明确。再看一些相应的代码:

static ngx_conf_bitmask_t ngx_http_proxy_next_upstream_masks[] = {
    { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
    { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT },
    { ngx_string("invalid_header"), NGX_HTTP_UPSTREAM_FT_INVALID_HEADER },
    { ngx_string("http_500"), NGX_HTTP_UPSTREAM_FT_HTTP_500 },
    { ngx_string("http_502"), NGX_HTTP_UPSTREAM_FT_HTTP_502 },
    { ngx_string("http_503"), NGX_HTTP_UPSTREAM_FT_HTTP_503 },
    { ngx_string("http_504"), NGX_HTTP_UPSTREAM_FT_HTTP_504 },
    { ngx_string("http_404"), NGX_HTTP_UPSTREAM_FT_HTTP_404 },
    { ngx_string("updating"), NGX_HTTP_UPSTREAM_FT_UPDATING },
    { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF },
    { ngx_null_string, 0 }
};
上述的信息告诉我们proxy模块中支持的特殊响应码处理有哪些,当然fastcgi和memcache等这些用到upstream的模块都有类似的数组。对于proxy_next_upstream指令配置中的给出的http_xxx,意思是让nginx在后端返回这些响应时,去尝试其他的后端(函数ngx_http_upstream_test_next)。当然nginx原生支持的就这些,如果大家有自己的需求,可以尝试去改这块代码。不过个人的建议是,改nginx的核心代码要慎重。

你可能感兴趣的:(Stream)