聊一聊nginx中KeepAlive的设置

news2024/11/17 9:49:13

文章目录

    • 问题
    • 分析
    • 为什么要有KeepAlive?
    • TCP KeepAlive和HTTP的Keep-Alive是一样的吗?
    • Nginx的TCP KeepAlive如何设置
    • Apache中KeepAlive和KeepAliveTimeOut
    • 参考资料

问题

之前工作中遇到一个KeepAlive的问题,现在把它记录下来,场景是这样的:

nginx_lvs_client

从上图可以看出,用户通过Client访问的是LVS的VIP, VIP后端挂载的RealServer是Nginx服务器。 Client可以是浏览器也可以是一个客户端程序。一般情况下, 这种架构不会出现问题,但是如果Client端把请求发送给Nginx,Nginx的后端需要一段时间才能返回结果,超过1分30秒就会有问题,使用LVS作为负载均衡设备看到的现象就是1分30秒之后, Client和Nginx链接被断开,没有数据返回。

分析

原因是LVS默认保持TCP的Session为90s,超过90s没有TCP报文在链接上传输,LVS就会给两端发送RESET报文断开链接。LVS这么做的原因相信大家都知道一二,我所知道的原因主要有两点:

1.节省负载均衡设备资源,每一个TCP/UDP的链接都会在负载均衡设备上创建一个Session的结构, 链接如果一直不断开,这种Session结构信息最终会消耗掉所有的资源,所以必须释放掉。
2.另外释放掉能保护后端的资源,如果攻击者通过空链接,链接到Nginx上,如果Nginx没有做合适 的保护,Nginx会因为链接数过多而无法提供服务。
这种问题不只是在LVS上有,之前在商用负载均衡设备F5上遇到过同样的问题,F5的Session断开方式和LVS有点区别,F5不会主动发送RESET给链接的两端,Session消失之后,当链接中一方再次发送报文时会接收到F5的RESET,
之后的现象是再次发送报文的一端TCP链接状态已经断开,而另外一端却还是ESTABLISH状态。

知道是负载均衡设备原因之后,第一反应就是通过开启KeepAlive来解决。到此这个问题应该是结束了,但是我发现过一段时间总又有人提起KeepAlive的问题,甚至发现由于KeepAlive的理解不正确浪费了很多资源,原本能使用LVS的应用放在了公网下沉区,或者换成了商用F5设备(F5设备的Session断开时间要长一点,默认应该是5分钟)。所以我决定把我知道的KeepAlive知识点写篇博客分享出来。

为什么要有KeepAlive?

在谈KeepAlive之前,我们先来了解下简单TCP知识(知识很简单,高手直接忽略)。首先要明确的是在TCP层是没有“请求”一说的,经常听到在TCP层发送一个请求,这种说法是错误的。TCP是一种通信的方式,“请求”一词是事务上的概念,HTTP协议是一种事务协议,如果说发送一个HTTP请求,这种说法就没有问题。也经常听到面试官反馈有些面试运维的同学,基本的TCP三次握手的概念不清楚, 面试官问TCP是如何建立链接,面试者上来就说,假如我是客户端我发送一个请求给服务端,服务端发送一个请求给我。。。这种一听就知道对TCP基本概念不清楚。下面是我通过wireshark抓取的一个TCP建立握手的过程。(命令行基本上用TCPdump,后面我们还会用这张图说明问题):

tcp_session_create

现在我看只要看前3行,这就是TCP三次握手的完整建立过程,第一个报文SYN从发起方发出,第二个报文SYN,ACK是从被连接方发出,第三个报文ACK确认对方的SYN,ACK已经收到,如下图:

tcp_syn_synack_ack

但是数据实际上并没有传输,请求是有数据的,第四个报文才是数据传输开始的过程,细心的读者应该能够发现wireshark把第四个报文解析成HTTP协议,HTTP协议的GET方法和URI也解析出来,所以说TCP层是没有请求的概念,HTTP协议是事务性协议才有请求的概念,TCP报文承载HTTP协议的请求(Request)和响应(Response)。

现在才是开始说明为什么要有KeepAlive。 链接建立之后,如果应用程序或者上层协议一直不发送数据,或者隔很长时间才发送一次数据,当链接很久没有数据报文传输时如何去确定对方还在线,到底是掉线了还是确实没有数据传输,链接还需不需要保持,这种情况在TCP协议设计中是需要考虑到的。TCP协议通过一种巧妙的方式去解决这个问题,当超过一段时间之后,TCP自动发送一个数据为空的报文给对方,如果对方回应了这个报文,说明对方还在线,链接可以继续保持,如果对方没有报文返回,并且重试了多次之后则认为链接丢失,没有必要保持链接。

如何开启KeepAlive
KeepAlive并不是默认开启的,在Linux系统上没有一个全局的选项去开启TCP的KeepAlive。需要开启KeepAlive的应用必须在TCP的socket中单独开启。Linux Kernel有三个选项影响到KeepAlive的行为:

1.net.ipv4.tcp_keepalive_intvl = 75
2.net.ipv4.tcp_keepalive_probes = 9
3.net.ipv4.tcp_keepalive_time = 7200

tcp_keepalive_time的单位是秒,表示TCP链接在多少秒之后没有数据报文传输启动探测报文; tcp_keepalive_intvl单位是也秒,表示前一个探测报文和后一个探测报文之间的时间间隔,tcp_keepalive_probes表示探测的次数。

TCP socket也有三个选项和内核对应,通过setsockopt系统调用针对单独的socket进行设置:

TCP_KEEPCNT: 覆盖 tcp_keepalive_probes
TCP_KEEPIDLE: 覆盖 tcp_keepalive_time
TCP_KEEPINTVL: 覆盖 tcp_keepalive_intvl

举个例子,以我的系统默认设置为例,kernel默认设置的tcp_keepalive_time是7200s, 如果我在应用程序中针对socket开启了KeepAlive,然后设置的TCP_KEEPIDLE为60,那么TCP协议栈在发现TCP链接空闲了60s没有数据传输的时候就会发送第一个探测报文。

TCP KeepAlive和HTTP的Keep-Alive是一样的吗?

估计很多人乍看下这个问题才发现其实经常说的KeepAlive不是这么回事,实际上在没有特指是TCP还是HTTP层的KeepAlive,不能混为一谈。TCP的KeepAlive和HTTP的Keep-Alive是完全不同的概念。TCP层的KeepAlive上面已经解释过了。 HTTP层的Keep-Alive是什么概念呢? 在讲述TCP链接建立的时候,我画了一张三次握手的示意图,TCP在建立链接之后, HTTP协议使用TCP传输HTTP协议的请求(Request)和响应(Response)数据,一次完整的HTTP事务如下图:

http_session

各位看官请注意,这张图我简化了HTTP(Req)和HTTP(Resp),实际上的请求和响应需要多个TCP报文。从图中可以发现一个完整的HTTP事务,有链接的建立, 请求的发送,响应接收,断开链接这四个过程,早期通过HTTP协议传输的数据以文本为主,一个请求可能就把所有要返回的数据取到,但是,现在要展现一张完整的页面需要很多个请求才能完成,如图片,JS,CSS等,如果每一个HTTP请求都需要新建并断开一个TCP,这个开销是完全没有必要的,开启HTTP Keep-Alive之后,能复用已有的TCP链接,当前一个请求已经响应完毕,服务器端没有立即关闭TCP链接,而是等待一段时间接收浏览器端可能发送过来的第二个请求,通常浏览器在第一个请求返回之后会立即发送第二个请求,如果某一时刻只能有一个链接,同一个TCP链接处理的请求越多,开启KeepAlive能节省的TCP建立和关闭的消耗就越多。当然通常会启用多个链接去从服务器器上请求资源,但是开启了Keep-Alive之后,仍然能加快资源的加载速度。HTTP/1.1之后默认开启Keep-Alive, 在HTTP的头域中增加Connection选项。当设置为Connection:keep-alive表示开启,设置为Connection:close表示关闭。实际上HTTP的KeepAlive写法是Keep-Alive,跟TCP的KeepAlive写法上也有不同。 所以TCP KeepAlive和HTTP的Keep-Alive不是同一回事情。

Nginx的TCP KeepAlive如何设置

开篇提到我最近遇到的问题,Client发送一个请求到Nginx服务端,服务端需要经过一段时间的计算才会返回, 时间超过了LVS Session保持的90s,在服务端使用Tcpdump抓包,本地通过wireshark分析显示的结果如第二副图所示,第5条报文和最后一条报文之间的时间戳大概差了90s。在确定是LVS的Session保持时间到期的问题之后,我开始在寻找Nginx的TCP KeepAlive如何设置,最先找到的选项是keepalive_timeout,从同事那里得知keepalive_timeout的用法是当keepalive_timeout的值为0时表示关闭keepalive,当keepalive_timeout的值为一个正整数值时表示链接保持多少秒,于是把keepalive_timeout设置成75s,但是实际的测试结果表明并不生效。显然keepalive_timeout不能解决TCP层面的KeepAlive问题,实际上Nginx涉及到keepalive的选项还不少,Nginx通常的使用方式如下:

nginx

从TCP层面Nginx不仅要和Client关心KeepAlive,而且还要和Upstream关心KeepAlive, 同时从HTTP协议层面,Nginx需要和Client关心Keep-Alive,如果Upstream使用的HTTP协议,还要关心和Upstream的Keep-Alive,总而言之,还比较复杂。所以搞清楚TCP层的KeepAlive和HTTP的Keep-Alive之后,就不会对于Nginx的KeepAlive设置错。我当时解决这个问题时候不确定Nginx有配置TCP keepAlive的选项,于是我打开Ngnix的源代码,在源代码里面搜索TCP_KEEPIDLE,相关的代码如下:

519 #if (NGX_HAVE_KEEPALIVE_TUNABLE)
 520                 
 521         if (ls[i].keepidle) { 
 522             if (setsockopt(ls[i].fd, IPPROTO_TCP, TCP_KEEPIDLE,
 523                            (const void *) &ls[i].keepidle, sizeof(int))
 524                 == -1)
 525             {
 526                 ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
 527                               "setsockopt(TCP_KEEPIDLE, %d) %V failed, ignored",
 528                               ls[i].keepidle, &ls[i].addr_text);
 529             }
 530         }

从代码的上下文我发现TCP KeepAlive可以配置,所以我接着查找通过哪个选项配置,最后发现listen指令的so_keepalive选项能对TCP socket进行KeepAlive的配置。

so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]
// on表示开启
// off表示关闭

有些系统提供跟精确的控制,比如linux:

 keepidle表示等待时间,keepintvl表示探测报的发送间隔,keepcnt表示探测报文发送的次数。

以上三个参数只能使用一个,不能同时使用, 比如so_keepalive=on, so_keepalive=off或者so_keepalive=30s::(表示等待30s没有数据报文发送探测报文)。通过设置listen 80,so_keepalive=60s::之后成功解决Nginx在LVS保持长链接的问题,避免了使用其他高成本的方案。在商用负载设备上如果遇到类似的问题同样也可以通过这种方式解决。

Apache中KeepAlive和KeepAliveTimeOut

调优apache参数一般都是根据场景去设置,这里分享下KeepAlive和KeepAliveTimeOut的关系与区别。

在Apache的httpd.conf中,KeepAlive指的是保持连接活跃,类似于数据库中的永久连接。若将KeepAlive设置为On,那么来自同一客户端的请求就不需要再一次连接,避免每次请求都要新建一个连接而加重服务器的负担。

KeepAlive的连接活跃时间当然是受KeepAliveTimeOut限制的。如果第二次请求和第一次请求之间超过KeepAliveTimeOut的时间的话,第一次连接就会中断,再新建第二个连接。所以,一般情况下,图片较多的网站应该把KeepAlive设为On。但是KeepAliveTimeOut应该设置为多少秒就是一个值得讨论的问题了。这里如果KeepAliveTimeOut设置的时间过短,比如设置=1秒,那么Apache就会频繁的建立New Link,就会耗费不少的资源;反过来,如果KeepAliveTimeOut设置的时间过长,比如设置超过200秒,那么APACHE中肯定有很多无用的连接会占用服务器的资源,也不是一件好事。所以,到底要把KeepAliveTimeOut设置为多少,要看网站的流量、服务器的配置而定。

其实,这和数据库比如mysql中的连接机制有点类似,KeepAlive相当于mysql_connectmysql_pconnect,KeepAliveTimeOut相当于wait_timeout。

参考资料

-《TCP/IP协议详解VOL1》–网络基础知识详尽介绍

  • http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/#overview
  • http://nginx.org/en/docs/http/ngx_http_core_module.html
  • Nginx Source code: https://github.com/alibaba/tengine

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【BP靶场portswigger-客户端11】跨站点脚本XSS-20个实验(上)

前言: 介绍: 博主:网络安全领域狂热爱好者(承诺在CSDN永久无偿分享文章)。 殊荣:CSDN网络安全领域优质创作者,2022年双十一业务安全保卫战-某厂第一名,某厂特邀数字业务安全研究员&…

ssm权限管理系统1

先说一个声明,这个文章可能不完整,也就是说,我只是列举出了项目里面部分疑问难点,然后你们想做,可以去b站查看这个项目的视频。我这里会上传每一部分源代码 svn: 安装好svn服务器之后,我们需要去服务器建…

Python之勒让德多项式

文章目录勒让德多项式简介求导和积分求根和反演拟合勒让德多项式简介 Legendre多项式是一种非常重要的正交多项式,在物理学中有着广泛的应用,例如点电荷在空间中的激发电势就具备勒让德多项式的形式。其表达形式为 Pn(x)12nn!dndxn{(x2−1)n}P_n(x)\fra…

D. Rorororobot(线段树寻找区间最大值)

Problem - 1709D - Codeforces 有一个网格,由n行和m列组成。行的编号是从1到n,从下到上。列从左至右编号为1至m。第i列的底部ai单元被封锁(第1,2,...,ai行的单元),其余n-ai单元没有被封锁。 一个机器人正在穿越这个网…

制冷设备远程监控解决方案

大中型工业制冷设备方面在国内发展还是处在一个发展期,前景广阔,在船舶行业,大型化,个性化,大冷量化是趋势,在石油石化领域,工艺分离、结晶浓缩、提纯催化需求低温,而在建筑领域&…

Vue+element 实现影响榜功能

目录 一、英雄榜实现 1、引入element Lib 和VUE 2、设置滚动条动态显示 1)设置对应的VUE参数名:scrollHiddenVar 2)VUE data定义 3)mounted 设置鼠标监听事件 4)监听方法 3、设置element Table 4、table 数组…

CentOS 7 升级 GCC/G++ 版本

CentOS 7官方源的 GCC 最新版本是 4.8.5,该版本发布于2015年,年代久远且不支持高版本的 C 编译了,因此有必要安装高版本的 GCC。   红帽(RedHat)中拥有多个已经编译好了的高版本 GCC,但未更新到 base 和 …

再说多线程(四)——Semaphore类

Semaphore提供了更精细化的多线程控制,如果你看过上一节的Mutex介绍,那么你应该很容易理解Semaphore类。我们直接先以例子开头,然后在介绍这个类。1.Semaphore实例看下面的代码:using System; using System.Threading;namespace S…

【爪洼岛冒险记】第3站:任务1:学会int和String之间的转换;任务2:将Java运算符全部收入囊中~ 任务3:拿下Java中的类型提升

🌱博主简介:是瑶瑶子啦,一名大一计科生,目前在努力学习C进阶,JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛! 📜所属专栏:爪洼岛冒险记 ✈往期博文回顾:【爪洼岛冒险记】第2站&#xff…

Allegro172版本如何快速打开和关闭层面操作指导

Allegro172版本如何快速打开和关闭层面操作指导 在做PCB设计的时候,打开和关闭某个层面是非常频繁的操作,尤其是丝印等等层面。 Allgeo升级到了172版本的时候,可以将常用的层面添加到Visibility菜单里,就不需要频繁打开颜色管理器打卡和关闭层面了,如下图 具体操作如下 打…

常见锁策略,CAS,synchronized原理

1.常见锁策略锁策略不仅仅局限于java,任何与"锁"相关的话题(操作系统,数据库...),都会涉及到锁策略,这些策略是给锁的实现者用来参考的1.1乐观锁vs悲观锁这个不是两把具体的锁.而是两类锁,是在锁冲突的概率上进行区分的乐观锁指的是预测锁竞争不是很激烈(做的工作相对…

Ambari2.7.5安装Flink1.14

文章目录下载Flink配置安装源下载ambari-flink-service服务修改配置文件创建用户和组重启Ambari登录Ambari安装Flink提交Flink任务Flink 直接单独提交到 On Yarn指定Flink在Yarn跑的容器运行Flink异常异常1异常2异常3下载Flink配置安装源 wget https://archive.apache.org/dis…

Goby+AWVS 联动

系列文章 AWVS安装与激活 AWVS扫描Web应用程序 AWVS扫描报告分析 GobyAWVS 联动 1.Goby简介 Goby是针对目标企业梳理最全面的工具,同构goby可以清晰的扫描出ip地址开放的端口,以及端口对应的服务,于此同事会根据开放的端口及应用进行实战…

分享116个ASP源码,总有一款适合您

ASP源码 分享116个ASP源码,总有一款适合您 116个ASP源码下载链接:https://pan.baidu.com/s/1LEs24-feWjvhac6vzyVecg?pwdnosx 提取码:nosx 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下...&am…

2.线性表

##线性结构 基本特点:结构中各元素之间满足线性关系。 线性关系:数据元素之间存在一对一的关系 1.存在唯一的开始元素 2.存在唯一的终止元素 3.除了开始元素和终止元素,其他元素均有且仅有一个直接前驱元素和一个直接后驱元素。 所有元素可排…

Sass进阶指南 -- 写出更优雅的样式表

我以为已经会了,之前在公司写项目基本都是用sass写样式,十分顺手。直到有段时间,我准备参考Element Plus来设计自己组件库的工程结构,看到Element Plus那些优雅的sass用法时,我开始为我的浅薄和无知感到羞愧。这便开始…

Python学习笔记——集合

集合(set)是一个无序的不重复元素序列。可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。创建格式:parame {value01,value02,..…

LeetCode[313]超级丑数

难度:中等题目:超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes中。给你一个整数 n和一个整数数组 primes,返回第 n个 超级丑数 。题目数据保证第 n个 超级丑数 在 32-bit 带符号整数范围内。示例 1&#xff1a…

C++ 类和对象(三)

类和对象(一) 类和对象(二) 日期类 目录 一. 再谈构造函数 1.构造函数体赋值 2.初始化列表 3.隐式类型转换与explicit关键字 4.C11优化 二. static成员 1.概念 2.特性 三. 友元 1.友元函数 2.友元类 四. 内部…

Java 如何设计一款小游戏详细讲解(二)

继续上面(一),Java 如何设计一款小游戏详细讲解(一)步骤步骤一接下来我们主要完成GameJFrame,就是拼图游戏的界面及代码。这里接下来完成菜单的设置,将主界面的代码放到一个方法中,像下面这样,接下来我们书写菜单的代码…