传道授业解惑也
单单网络连接的方面的参数就有n多个,这是因为作为代理服务器的时候,需要建立两个全双工通道,connect , read, write 都可能面临超时,两个connect过程,(client->nginx)3+2(client->nginx)+3(nginx->upstream)+2(upstream->nginx)一共8个TCP操作,任何一个操作失败,整个连接就无法正常工作。
我的nginx作为反向代理服务,所以fastcgi就跳过
proxy_connect_timeout 60s; 连接upstream的超时时间
proxy_send_timeout 1800; 和upstream通信write数据的时候超时时间
proxy_read_timeout 1800; 和upstream通信read数据的时候超时时间
proxy_buffers 64 8k;
proxy_buffer_size 16k; HTTP响应头缓存
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_redirect off;
nginx代理前面还有个LB,找到网上通用的配置,大家基本是这么设置的
set_real_ip_from 10.10.4.11; 排除上游服务IP
real_ip_header X-Forwarded-For; remote_addr从过来的请求的x-forwarded-for这个头里面查找设置
real_ip_recursive on; 递归查找
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
这里一顿猛如虎的set ip操作可以用下面的伪代码表示
if $remote_addr == "10.10.4.11" :
if $real_ip_recursive == on:
for $ip in X-Forwared-For.split(","):
if $ip != "10.10.4.11":
$remote_addr = $ip;
else:
if X-Forwared-For.split(",")[0] != "":
$remote_addr = X-Forwared-For.split(",")[0]
else:
//pass
$HTTP_X-Real-IP = $remote_addr
$proxy_add_x_forwarded_for = $X-Forwared-For + "," + $remote_addr
如上这样可以确保nginx代理获取到的IP是真实的客户端IP,这里set_real_ip不建议设置为某个IP段,而是设置精确的IP,不然IP段里面的机器都可以伪造x-forwarded-for IP过来,但是这样设置还存在一个问题,proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 这句其实是多余的,猛如虎的操作之后其实我的$remote_addr变成了clientip(这是因为这个模块的配置会发生在nginx的第一个阶段post-read,之后所有的配置都会收到它的影响), 所以$proxy_add_x_forwarded_for的值是clientiip+”,”+clientip, 我的下游会得到的x-forwarded-for 其实是clientip, clientip,这里传递两个没啥必要。
这里比较尴尬的就是x-forwarded-for这条链路是不正确的,下面这样设置可以保证正确的链路
proxy_set_header X-Forwarded-For "$X-Forwared-For + $realip_remote_addr"
$realip_remote_addr 这个变量根据官方的解释keeps the original client address (1.9.7),需要高版本
server_names_hash_max_size 10240;
server_names_hash_bucket_size 128;
nginx的hash表在解决hash碰撞的时候使用拉链法解决,整个hash表的初始化是在加载配置的时候确定的,server_names_hash_max_size 用于控制hash表大小,server_names_hash_bucket_size用于控制一个hash bucket就是单链表的最大长度,所以如果像我上面这么设置,最理想的情况,可以负载10240*128个域名。
如果内存足够,为了优化,server_names_hash_max_size大小我建议差不多保持域名个数的1.5倍大小,然后按照2的幂次方去设置,server_names_hash_bucket_size这个参数不要太大,会降低hash查找速率,影响性能,比如我的代理服务器3000个域名,我设置了10240,这样可以保证查找速率在O(1),为啥这么设置具体原理可以看我之前写的hashtable原理分析。
运维设置的nginx参数,问题来了,设置了这个对我的反向代理nginx会有性能提升吗?有点好奇,研究了一下
tcp_nodelay on;
tcp_nopush on;
sendfile on;
tcp_nodelay on; 设置之后表示关闭古老的解决网络拥堵的算法,新算法就是tcp_nopush on;具体的可以搜文章百度一大堆解释 在nginx作为web server的时候,传统的模式是这样工作的,例如一个请求资源/music/1.mp3,会经历以下syscall
硬盘 >> kernel buffer >> user buffer>> kernel socket buffer >>协议栈
然后sendfile这个api是这么工作的
硬盘 >> kernel buffer (快速拷贝到kernelsocket buffer) >>协议栈
从上面来看,sendfile系统调用通过直接把数据copy到内核socket提升性能,但是作为反向代理服务器的时候,数据处理不是走的这个流程 ,那么难道真的是没啥用吗?其实不是,当你的反向代理开启缓存的时候,数据是nginx从本地缓存文件读取的,这个时候的nginx就相当于web server的角色。
sniffer分析出来的流量和nginx日志分析出来的流量相差很多,翻阅日志明细,发现很多499状态码的请求
查阅文档是这么解释的,默认proxy_ignore_client_abort 是关闭的,此时在请求过程中如果客户端端主动关闭请求或者客户端网络断掉,那么 Nginx 会记录 499,同时 request_time 是 「后端已经处理」的时间,而 upstream_response_time 为 “-“ (已验证), 如果proxy_ignore_client_abort on, 那么客户端主动断掉连接之后,Nginx 会等待后端处理完(或者超时),然后 记录 「后端的返回信息」 到日志。所以,如果后端 返回 200, 就记录 200 ;如果后端放回 5XX ,那么就记录 5XX 。。 在我的场景就会有一个尴尬的问题,如果我设置proxy_ignore_client_abort on两边的流量是可以对齐了,但是会带来下面的问题
如果我设置这个参数,流量又对不上,这不是nginx的锅,上层应用不会设计连接中断的时候计算发送和返回的数据大小。
在带宽资源比较稀缺的情况,建议开启数据压缩,由于gzip算法比较古老,覆盖率比较广,br算法比较新,通用的代理服务器还是采用gzip比较合适,条件允许的话可以开启br算法。
gzip on; //开启gzip压缩
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on; //判断客户端是否支持gzip
这里还有个ngx_http_gzip_static_module模块,如果你的客户端支持gzip,那么开启此模块可以让nginx直接读取gz文件返回给客户端,减少nginx压缩时间,也就是说你本地有个style.css 直接用gzip命令提前压缩为style.css.gz, nginx会再客户端支持gzip的时候读取style.css.gz, 不支持的时候读取style.css
$bytes_sent和$body_bytes_sent的区别,前者算上了HTTP协议的协议头,但是不包括TCP协议头,后者就是client和server通信传输的数据,如果想依赖nginx的日志计算流量,只具备参考性,在网络环境质量比较差的时候,会有大量的TCP重传,和在4层工作的sniffer程序对比相差3-4倍都是正常的。
这个问题其实我之前也一直有疑惑,后端承载了3000+ 服务的我瑟瑟发抖,经常上服务器ss -s看看连接数,担心我的nginx承受不住。
首先客户端发出请求和我们的listen端口建立连接,这个不需要我们考虑,端口可以复用,应为linux一切皆文件,一个socket就是一个fd,所以和系统允许一个进程打开的最大文件数量有关,nginx里面的参数worker_connections,8个worker就是8个进程8*worker_connections/2个并发,这里/2是因为需要维持一个双向通道;nginx连接后端upstream的时候,可以和每个upstream建立65535个连接,而不是我们的nginx代理只能和所有的upstream维持65535个连接,实际我们不可能一个应用会有那么高并发,有的话也只需要增加upstream的负载数量就能解决。
linux系统先调整进程最大open file数量,ulimit -c unlimited等同于ulimit -n 65535, 然后同时nginx 配置如下,这个worker_connections不会超过ulimit的最大值65535
worker_connections 65535;
所以结论是很难被消耗完,这个问题还是比较复杂,可以参考http://blog.51cto.com/liuqunying/1420556
尤其是$host和$http_host很难区分,之前写lua的时候一直不知道用哪一个
$server_name就是nginx里面定义的一个一个server_name,比较好理解,这里有个小坑,如果你在一行里面定义了多个server_name, 那么server_name永远等于第一个server_name的值;
$http_host是HTTP协议里面parse出来的host值,nginx把每一个HTTP header定义为http_header的形式,比如cookie就是$http_cookie, 所以这里也很好理解;
host变量比较复杂一些,它是nginx core里面的变量,它的值按照如下优先级获得:
作为反向代理的时候一般监听80和443端口,然后通过域名访问,这个时候$host一般等于$http_host, 如果nginx不是监听在80或者433上,我们需要带域名和端口访问,这个时候$host是域名, 而$http_host是域名+port
作为一个实至名归的透明代理,要做到让后端upstream感觉自己就是和用户在通信,所以我们必须把客户端原始IP设置为remote_ip
Haproxy配置可以参考 https://www.cnblogs.com/qiyebao/p/6001427.html, 我就不多说了,nginx 在配置server的时候需要配置如下:
listen 443 ssl proxy_protocol;
set_real_ip_from hxproxy_ip; #排除上游haproxy ip
real_ip_recursive on;
real_ip_header proxy_protocol;
HTTP/2 时代,我们果断升级与时俱进,但是nginx上面有很多lua代码,迁移的时候发现部分API无法使用 ngx.req.raw_header 目前还不支持HTTP/2, 迁移之前需要做足够的测试
我这里使用Resourcemanager系统举例,在新版本里面,ResourceManager HA通过主动/备用架构实现 - 在任何时间点,其中一个RM是活动的,并且一个或多个RM处于待机模式,等待接管,如果活动发生任何事情。转换到活动的触发器来自管理员(通过CLI)或通过集成的故障转移控制器(当启用自动故障转移时), 由于内部已经实现了这种高可用,导致如果使用nginx去反代,不知道upstream该怎么写了。
curl -i 172.18.14.8:8088
HTTP/1.1 307 TEMPORARY_REDIRECT
Cache-Control: no-cache
Expires: Thu, 26 Apr 2018 06:32:06 GMT
Date: Thu, 26 Apr 2018 06:32:06 GMT
Pragma: no-cache
Expires: Thu, 26 Apr 2018 06:32:06 GMT
Date: Thu, 26 Apr 2018 06:32:06 GMT
Pragma: no-cache
Content-Type: text/plain; charset=UTF-8
curl -i 172.18.14.9:8088
HTTP/1.1 302 Found
Cache-Control: no-cache
Expires: Thu, 26 Apr 2018 06:31:59 GMT
Date: Thu, 26 Apr 2018 06:31:59 GMT
Pragma: no-cache
Expires: Thu, 26 Apr 2018 06:31:59 GMT
Date: Thu, 26 Apr 2018 06:31:59 GMT
Pragma: no-cache
Content-Type: text/plain; charset=UTF-8
通过curl两个机器的地址我们会发现返回的http状态码不同,再借助nginx的健康检查,我们可以把请求全部转发到活动的RM节点,相当于废弃了自带的高可用,用nginx实现高可用。
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD /cluster HTTP/1.0\r\n\r\n";
check_http_expect_alive http_4xx;
因为自带的nginx健康检查模块无法区分302或者307,只能识别3xx, 所以选择蹩脚的访问/cluster这个路径,活动的机器会返回405状态,不活动的机器返回的是307,配置之后,10秒之后只有活动的机器会显示up,请求将都被转发到此机器,实现了反向代理
上面的方式其实比较的挫,如果想要优雅的解决,可以使用nginx lua的健康检测模块
先说下环境,数据传输需要经过代理服务器,第一阶段从内网到代理服务器,这里走内网通信,传输速度很快,不存在问题,第二阶段代理服务器到公网,这里网络质量很不好,带宽小,经常丢包。
一个应用并发上传文件,由于网络质量不好,导致丢包,客户端频繁重试提交,nginx待传输数据过大,导致n个ESTABLISHED连接僵死在nginx上,临时方案,使用tcpkill干掉这些连接。
发现错误日志[error] 13907#0: *1217694104 upstream sent too big header while reading response header from upstream
后端服务器的响应头,也就是HTTP协议的头,会放到proxy_buffer_size当中,这个大小默认等于proxy_buffers当中的设置单个缓冲区的大小。 部分应用不符合规范,比如超长cookie就会导致出现这个问题。 配置如下解决, 具体还是可以根据实际情况慢慢加大调整,实在不行就针对局部server调整
proxy_buffer_size 16k;
tengine使用了2年多,发现tengine社区的维护更新太慢了,很多新特性无法使用,然而我们的场景却需要这些新特性,于是决定把线上的nginx版本升级
升级到nginx1.14.1版本遇到的问题
为了大幅度降低网络IO, 需要在前后段nginx的传输过程开启全gzip,不管你client支持不支持gzip,后端nginx都会返回gzip压缩过的数据
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 6;
gzip_vary on;
proxy_set_header官方文档给的解释是:These directives are inherited from the previous level if and only if there are no proxy_set_header directives defined on the current level
翻译一下: proxy_set_header这个参数会继承全局配置,当且仅当局部配置没有使用proxy_set_header, 之前没有仔细看文档,一直以为这个参数的继承参数和正常理解一样,局部配置继承全局配置,并且会覆盖全局配置, 后端服务有依赖全局的proxy_set_header,这里确实后端服务也有问题不应该和代理服务偶尔,在上线测试新功能时,在局部的server域里面加了proxy_set_header,导致全局的proxy_set_header无法生效,线上发生事故
总结一下:还是有必要进行充分的测试,即便多花一点时间,不能想当然的应该不会出问题。
###2019-02-26 静态资源被截断
反向代理静态资源的时候做了缓存 网络糟糕的情况客户端中断和代理的连接,导致代理和后端的upstream也被中断,可能这个时候代理和后端的upstream的传输没有完成,导致缓存不完整,解决方案加上proxy_ignore_client_abort on
评论
暂无评论~~