关于nginx中upstream机制的思考

本文深入探讨 Nginx 中 upstream 模块的工作原理,包括配置解析、后端服务器选择策略及异常处理机制。特别是针对 roundrobbin 算法的选择过程进行了详细的说明,并对 nginx 如何处理连接异常、响应头异常等情况进行了分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网上搜过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”,看官方介绍吧。

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

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的核心代码要慎重。

<think>嗯,用户想了解如何使用Nginx Stream模块实现内容过滤和修改。首先,我得确认自己对Stream模块的理解。Stream模块主要用于处理四层流量,比如TCP和UDP,不像HTTP模块处理七层。所以内容过滤和修改在这里需要处理原始数据流。 接下来,我需要回忆Nginx的Stream模块是否支持类似HTTP模块的过滤功能。记得在HTTP模块中可以用subrequest或者lua脚本,但Stream模块可能没有这么直接的支持。不过,用户提到了引用中的test-nginx和Lua模块,可能用户对使用Lua有兴趣?比如引用[1]提到Lua模块的测试方法,可能用户知道OpenResty,它扩展了Nginx的功能,支持Lua脚本。 然后,思考如何在Stream模块中实现内容过滤。可能的方法包括使用ngx_stream_lua_module,它允许在TCP/UDP层执行Lua脚本。例如,可以注册处理程序来拦截和修改数据。比如preread阶段读取客户端数据,用Lua进行处理,然后转发。或者使用第三方模块,比如stream_edit,但可能需要自己编译。 另外,引用[2]提到filter chain和copy filter,这可能是在HTTP模块中的处理,但Stream模块的结构不同,需要确认是否有类似的机制。可能需要配置upstream和proxy_pass,在数据流经时用Lua脚本处理。 还要考虑具体步骤:安装OpenResty,配置stream块,使用Lua脚本处理preread和post_output阶段。比如,在客户端发送数据时,用preread阶段捕获并修改,再转发到后端。或者对响应数据在服务器端处理后修改。 需要注意Stream模块的局限性,比如不能像HTTP模块那样解析应用层协议,处理二进制数据可能复杂。需要提醒用户注意性能和数据完整性。 然后生成相关问题,比如优化性能、处理SSL、替代方案等,帮助用户深入。 最后,确保回答符合格式要求:使用中文,行内公式正确,引用标记,代码块正确,问题部分列出相关问题。需要检查引用是否正确对应,比如引用[1]是关于测试,引用[2]是filter chain,引用[3]是请求转换,可能需要在回答中适当引用。</think>使用Nginx Stream模块实现内容过滤和修改主要通过原始TCP/UDP数据处理实现。以下是具体实现方法及注意事项: ### 一、基础实现原理 1. **Stream模块特性**:基于四层网络模型处理原始字节流,支持TCP/UDP协议代理 2. **数据拦截点**: - `preread`阶段:接收客户端数据时触发 - `post_output`阶段:发送服务端响应时触发 3. **核心指令**:`ngx_stream_lua_module`模块提供Lua脚本支持 ### 二、具体实现步骤 1. **安装OpenResty**(含Stream Lua模块): ```bash wget https://openresty.org/package/centos/openresty.repo sudo mv openresty.repo /etc/yum.repos.d/ sudo yum install -y openresty ``` 2. **配置nginx.conf**: ```nginx stream { lua_package_path "/path/to/lua-scripts/?.lua;;"; server { listen 12345; preread_by_lua_block { local data = ngx.ctx.preread -- 实现请求内容修改逻辑 data = ngx.re.gsub(data, "敏感词", "***") ngx.var.preread_data = data } proxy_pass backend_server:54321; post_output_filter_by_lua_block { local chunk = ngx.arg[1] -- 实现响应内容修改逻辑 ngx.arg[1] = string.gsub(chunk, "内部IP", "***") } } } ``` 3. **性能优化配置**: ```nginx lua_socket_buffer_size 4k; lua_check_client_abort on; preread_buffer_size 16k; ``` ### 三、注意事项 1. **数据完整性**:处理二进制协议时需保持报文完整性,建议设置`preread_buffer_size`为协议最大报文长度[^2] 2. **性能影响**:Lua脚本执行会增加约15%-30%的延迟,建议使用C模块进行高性能处理 3. **SSL处理**:需先进行SSL终止才能处理加密内容,可配合`ngx_stream_ssl_preread_module`使用 ### 四、扩展功能实现 1. **协议识别**: ```lua local magic_number = string.sub(data, 1, 4) if magic_number == "\x01\x02\x03\x04" then -- 自定义协议处理 end ``` 2. **动态阻断**: ```lua if ngx.re.find(data, "攻击特征") then ngx.log(ngx.ERR, "检测到恶意流量") ngx.exit(444) end ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值