Nginx长连接学习之二


背景

距离最开始学习Nginx的长连接已经一年半;
距离最开始学习Linux的TCP内核参数也已经过去了一年. 最近产品再次出现了TCP链接相关的问题.
因为一开始不知道部署模式已经变更, 我先排除了内存参数的问题. 结果很打脸, 还是内核参数问题导致的, 但是可能比较隐晦.
因为Nginx服务器使用的CentOS9_Stream 并且进行了内核升级. 这里想还是继续总结一下, 因为我已经忘记了一年半以前学习的内容.
参数都已经还给了互联网.

问题场景描述

本次压测场景出现了 CPU忽高忽低的问题.
最开始不清楚部署已经增加了nginx, 一直以为是直连springboot应用
所以一开始一直怀疑是 类似于有进程执行了强制safepoint导致的停顿.
所以这个教训告诉大家, 一定要详细了解系统的部署拓扑图, 没有拓扑图前不要进行任何推测. 增加了nginx的error log等信息后发现了大量的
cannot assign port 等的提示, 基本上实锤了与2023年的问题完全相同. TCP四元组端口号不足导致的问题.

基础知识说明

第一: tcp内核参数
tcp内核参数其实是一个很重要的点.
ulimit里面的nofile nproc等参数 其实属于安全部分的设置, 他的数值受限制于内核参数的上限.
除此之外还有类似于tcp内核参数, 文件数, 内存数相关的内核参数. 第二: nginx
nginx 可以作为web服务器, 也可以作为反向代理服务器.
作为web服务器时比较单纯, 只跟客户端沟通就可以了.
但是作为proxy代理服务器时就比较复杂:
一方面要作为 链接 客户端的 web服务器进行交互,
另一方面要作为 客户端与backend的应用服务器进行交互.
所以代理服务器时 他不仅仅是服务器, 同时还是客户端. 第三: linux内核
Linux内核是Linux操作系统的基础. 很多参数其实会作用于Linux内核上面
但是也有很多是无法通过参数进行修改的. 比如今天想讨论的 time_wait持续时间的问题.
如果无法通过参数进行修改, 那么可能需要重新编译内核. 但是如果没有足够的技术储备,不建议如此使用.
会导致运维起来难度增加, 并且存在后续的升级和维护困难.
这一点参照了 解bug之路 公众号里面的Linux源码学习相关

问题现象

通过如下命令在linux服务器上面查询

netstat -ano |grep ^tcp |awk '{print $6}'|sort |uniq -c|sort -k1hr

发现有较多的 time_wait 相关的信息
114229 TIME _WAIT 因为后面有四到五台应用服务器 平均米格应用服务器已经快3万个 time wait的链接对应了.
所以nginx 会出现 cannot assign port的问题.

解决问题的方式-修改内核参数

1. 修改linux内核参数
这里很早之前总结过:
最新核的参数其实是:
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.ip_local_port_range = 2048 65500
net.ipv4.ip_local_reserved_ports = 5200,6379,7005,8001-8100 需要注意 tcp_max_tw_buckets 是针对全局 整个系统的.
一般设置到 5000 时 新增的 time_wait的 端口就会将旧的端口冲掉.
有一定很小的概率出现 四元组冲突. 需要注意. 另外注意 ip_local_port_range 是针对每个四元组来的, 不同的IP会选择 整个range进行port分配
所以建议增加一个 reserved的参数, 保证重启服务器时不会出现因为端口号占用 导致启动失败. 需要注意 这三个参数的修改并非解决问题, 而是避开了问题, 将能够支撑的QPS/TPS进行了增加.
彻底解决应该不能依靠这类参数修改类解决

解决问题的方式-重新编译内核

网上很多资料都说 net.ipv4.tcp_fin_timeout 等内核参数可以修改 time_wait的时间
但是更多大佬都说了 是内核编译时就已经决定的一个参数
https://zhuanlan.zhihu.com/p/286537295
这个文章里面的就说的很清楚(需要去读原文,才能看懂下面这一段)
/* 这边TCP_TIMEWAIT_LEN 60 * HZ */
inet_twsk_schedule(tw, &tcp_death_row, TCP_TIMEWAIT_LEN,
TCP_TIMEWAIT_LEN); 他只受到 TCP_TIMEWAIT_LEN 参数的影响, 但是修改的话 需要重新编译内核.
并且作者后面也说了, 在time_wait 的每个slot大于100个时有可能会加大每个slot内的time wait的处理时间.
导致一个time wait的链接最大处理时间超过 112秒((7.5s+7.5s)*7+7.5s)). 并且他也发现某些内核, 会将等待事件从 60/8 的 7.5秒修改为 1秒,
这样最大的等待事件会变成 68秒. (8.5s+7+7.5s) 需要说明 这种处理方式跟内核参数修改是类似的, 也是为了缓解问题, 并非最终解决问题. 文章里面也发现, 不同内核下面网络栈的表现是不一样的, 可能某些非stable版本的系统, 会尝试进行的调整time_wait等变量
然后使用该内核的用户相当于免费的小白鼠进行测试. 如果出现了异常, 可能就是内核版本的修改不太适应本地应用系统额特性 世界就是一个草台班子, 任何修改都可能触发意想不到的问题. 所以先改代码的都是勇士, 都是需要被尊重的.

插播-长连接的优势

1)对响应时间要求较高;
2)服务走的是公网,客户端与服务端的TCP建立的三次握手和断开的四次挥手都需要40ms左右(真实数据包计算出来的),共需要80ms左右;
3)每个接入方使用的IP就若干个,需要建立的请求连接有限。
4)使用长连接技术,可以大幅减少TCP频繁握手的次数,极大提高响应时间;
同时,即使使用长连接技术,也不需要消耗很多的系统资源用来缓存sockets会话信息。 来源: https://www.cnblogs.com/kevingrace/p/9364404.html

解决问题的方式-长连接

最后一种可能是较好的解决问题的方式是 长连接.
知识背景里面也提到了.
Nginx 主要是有 服务器端 和 客户端的双重角色.
所以解决问题也需要两者都是进行考虑. 但是跟之前解决问题的思路一样. 所有的问题都是环环相扣的
配置nginx只是问题解决的一部分, 并不能够将所有的问题全部解决.
可能需要客户端浏览器以及后端应用服务器同步进行修改才可以. 主要的参数有:
keepalive
keepalive_timeout
keepalive_requests
proxy_timeout
等参数. 这里的配置其实比较繁琐, 需要仔细理解.

解决问题的方式-长连接-nginx作为服务端

http {
keepalive_timeout 120s; #客户端链接超时时间。为0的时候禁用长连接。即长连接的timeout
keepalive_requests 10000; #在一个长连接上可以服务的最大请求数目。
#当达到最大请求数目且所有已有请求结束后,连接被关闭。默认值为100。即每个连接的最大请求数
}

解决问题的方式-长连接-nginx作为客户端

http {
upstream backend {
server 192.168.0.1:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.0.2:8080 weight=1 max_fails=2 fail_timeout=30s;
keepalive 300; #这个很重要!
keepalive_requests 10000;
} server {
listen 8080 default_server;
server_name ""; location / {
proxy_pass http://backend;
proxy_http_version 1.1; #设置http版本为1.1
proxy_set_header Connection ""; #设置Connection为长连接(默认为no)
proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 600s; #新增配置1
proxy_send_timeout 120s; #新增配置2
}
}
}

注意事项

需要注意, 如果采用websocket的话. 默认应该是长连接.
但是需要使用单点的站点进行升级处理. Nginx支持WebSocket。
对于Nginx将升级请求从客户端发送到后台服务器,必须明确设置Upgrade和Connection标题。
这也算是上面情况所非常用的场景。
HTTP的Upgrade协议头机制用于将连接从HTTP连接升级到WebSocket连接,Upgrade机制使用了Upgrade协议头和Connection协议头。
为了让Nginx可以将来自客户端的Upgrade请求发送到后端服务器,Upgrade和Connection的头信息必须被显式的设置。

延伸理解之一

Chrome浏览器以及Springboot的服务器端其实也有类似的设置.

浏览器对并发请求的数目限制是针对域名的,即针对同一域名(包括二级域名)在同一时间支持的并发请求数量的限制。
如果请求数目超出限制,则会阻塞。因此,网站中对一些静态资源,使用不同的一级域名,可以提升浏览器并行请求的数目,加速界面资源的获取速度。 HTTP/1.1中,单个TCP连接,在同一时间只能处理一个http请求,虽然存在Pipelining技术支持多个请求同时发送
但由于实践中存在很多问题无法解决,所以浏览器默认是关闭,所以可以认为是不支持同时多个请求。
HTTP2 提供了多路传输功能,多个http请求,可以同时在同一个TCP连接中进行传输。 Chrome浏览器在http1.1协议时可以对同一个站点使用六个tcp链接进行访问.
页面资源请求时,浏览器会同时和服务器建立多个TCP连接,在同一个TCP连接上顺序处理多个HTTP请求。
所以浏览器的并发性就体现在可以建立多个TCP连接,来支持多个http同时请求。 Chrome浏览器最多允许对同一个域名Host建立6个TCP连接,不同的浏览器有所区别。 如果都是HTTPS的连接,并且在同一域名下,浏览器会先和服务器协商使用HTTP2的Multiplexing功能进行多路传输,
不过未必所有的挂在这个域名下的资源都会使用同一个TCP连接。如果用不了HTTPS或者HTTP2(HTTP2是在HTTPS上实现的),
那么浏览器会就在同一个host建立多个TCP连接,每一个TCP连接进行顺序请求资源。 来源: https://cloud.tencent.com/developer/article/1518678

延伸理解之二

Springboot 开发的应用默认内嵌的tomcat
tomcat 里面有thread的线程池, 数据库连接池, 以及 http的链接池信息
需要注意 springboot 默认的 max-keep-alive-requests 就是100 如果并发量很大, 可以适当调高一下这个数值.
建议跟nginx 里面的 upstream 里面的 keepalive 的参数值尽量保持一致, 可以尽可能的提高并发能力. (此观点待压测证明) 另外需要说明 threads.max基本上是 http-nio-port 的线程数量限制, 可以理解为是一个比较核心的工作线程数量限制.
其他的内部的线程池一般是通过 max 数值单独指定.
需要注意, 线程也会消耗内存, 默认的linux上面的 线程的栈大小是1MB. 但是一般请款下也是延迟分配的.
创建线程后可能会分配 200K左右的内存, 并不会完整使用1MB左右. 也有很多配置信息:
server:
tomcat:
# 当所有可能的请求处理线程都在使用中时,传入连接请求的最大队列长度
accept-count: 1000
# 服务器在任何给定时间接受和处理的最大连接数。一旦达到限制,操作系统仍然可以接受基于“acceptCount”属性的连接。
max-connections: 20000
threads:
# 工作线程的最小数量,初始化时创建的线程数
min-spare: 10
# 工作线程的最大数量 io密集型建议10倍的cpu数,cpu密集型建议cpu数+1,绝大部分应用都是io密集型
max: 500
# 连接器在接受连接后等待显示请求 URI 行的时间。
connection-timeout: 60000
# 在关闭连接之前等待另一个 HTTP 请求的时间。如果未设置,则使用 connectionTimeout。设置为 -1 时不会超时。
keep-alive-timeout: 60000
# 在连接关闭之前可以进行流水线处理的最大HTTP请求数量。当设置为0或1时,禁用keep-alive和流水线处理。当设置为-1时,允许无限数量的流水线处理或keep-alive请求。
max-keep-alive-requests: 1000

Nginx长连接学习之二的更多相关文章

  1. Nginx入门篇(二)之Nginx部署与配置文件解析

    一.Nginx编译安装 ()查看系统环境 [root@localhost tools]# cat /etc/redhat-release CentOS Linux release (Core) [ro ...

  2. nginx限制请求之二:(ngx_http_limit_req_module)模块

    相关文章: <高可用服务设计之二:Rate limiting 限流与降级> <nginx限制请求之一:(ngx_http_limit_conn_module)模块> <n ...

  3. spa(单页应用)中,使用history模式时,微信长按识别二维码在ios下失效的问题

    spa(单页应用,vue)中,使用history模式时,微信长按识别二维码在ios下失效的问题. 触发条件: spa单页应用: 路由模式 history 从其他页面跳转到带有微信二维码识别的页面(不是 ...

  4. iOS - WKWebView的使用和长按手势识别二维码并保存

    WKWebView的图片二维码使用: .长按手势识别二维码并保存 .识别二维码跳转;不是链接显示内容点击网址跳转 .解决url包含中文不能编码的问题 .文字带链接网址,点击跳转 .纯文本-文字html ...

  5. 移动端禁止图片长按和vivo手机点击img标签放大图片,禁止长按识别二维码或保存图片【转载】

    移动端禁止图片长按和vivo手机点击img标签放大图片,禁止长按识别二维码或保存图片 img{ pointer-events: none; } 源文地址:https://www.cnblogs.com ...

  6. 用lua nginx module搭建一个二维码

    用lua nginx module搭建一个二维码(qr code)生成器 作者 vinoca 發布於 2014年10月31日 如果有VPS,或者开源的路由器,安装一个nginx,添加lua-nginx ...

  7. Canvas与Image互相转换示例以及利用该技术实现微信长按自动识别二维码功能

    现在扫描二维码已经很普遍,微信扫一扫即可,但是如果二维码是在自己的手机上呢?那就要用到微信里的一个功能了,手指长按二维码,会弹出自动识别的选项,点确定就可以看到二维码的内容了.那么怎么通过前端实现这个 ...

  8. 使用 Docker 和 Nginx 打造高性能的二维码服务

    使用 Docker 和 Nginx 打造高性能的二维码服务 本文将演示如何使用 Docker 完整打造一个基于 Nginx 的高性能二维码服务,以及对整个服务镜像进行优化的方法.如果你的网络状况良好, ...

  9. 微信长按识别二维码,在 vue 项目中的实现

    微信长按识别二维码是 QQ 浏览器的内置功能,该功能的基础一定要使用 img 标签引入图片,其他方式的二维码无法识别. 在 vue 中使用 QrcodeVue 插件 demo1 在 template ...

  10. Nginx常用功能配置二

    Nginx常用功能配置二 Nginx location匹配设置 location作用:可以根据用户请求的URI来执行不同的应用,根据用户请求的网站的地址URL匹配. location语法: locat ...

随机推荐

  1. 如何使用Redisson实现分布式锁?

    在分布式系统中,当多个线程(或进程)同时操作同一个资源时,为了保证数据一致性问题,所以就需要一种机制来确保在同一时间只有一个线程(或进程)能够对资源进行修改,这就是分布式锁的作用. 分布式锁是一种在分 ...

  2. 前端 Git 使用约定

    前端 Git 使用约定 背景 开发前端项目,有以下困惑: 使用哪个分支开发,哪个分支发布 修复线上bug的流程是什么,如何避免修复完了下次却又出现了 cms分支有十多个,是否都有用 如何快速找到之前某 ...

  3. Python——第一章:用户交互

    变量 = input(提示语)首先会在屏幕中显示出提示语, 用户输入内容. 然后把用户输入的内容交给前面的变量 案例1: a = input("请输入第一个数字:") #括号里是提 ...

  4. 2.elasticsearch中的mapping

    mapping 顾名思义,代表了映射关系.是文档中字段和数据类型的映射关系 为什么要了解mapping 虽然elasticsearch中已尽有的动态mapping(Dynamic Mapping),而 ...

  5. zookeeper源码(05)数据存储

    本文详细分析一下zookeeper的数据存储. ZKDatabase 维护zookeeper服务器内存数据库,包括session.dataTree和committedlog数据,从磁盘读取日志和快照后 ...

  6. Angular 集成 Material UI 后组件显示不正常 踩坑日记

    在使用了 npm 下载 Material 后, 项目不能正常使用 Material 组件, 随后又使用官方命令使用 Material 组件, 仍然不能正常使用 Material 组件. npm 命令 ...

  7. 深入理解JavaScript堆栈、事件循环、执行上下文和作用域以及闭包

    1. 堆栈 在JavaScript中,内存堆是内存分配的地方,调用栈是代码执行的地方. 原始类型的保存方式:在变量中保存的是值本身,所以原始类型也被称之为值类型. 对象类型的保存方式:在变量中保存的是 ...

  8. 案例解析丨金蝶K/3 Wise接入华为云RDS数据库SQL Server

    1. 简介 企业或用户将数据中心部署在线下,采用独立软件提供商(Independent Software Vendor)软件进行管理.线下数据运维成本较高,故障容灾单一化,是目前遇到的瓶颈.采用云上数 ...

  9. 实用教程|手把手带你离线部署Walrus

    Walrus 0.4 已于近日发布,新版本中采用的应用模型可以让运维团队仅需配置1次,即可在多模态的基础设施及环境中运行包括应用服务及周边依赖资源在内的全套应用系统.这极大减少了运维人员的工作量,同时 ...

  10. Intellij idea getter setter 模板设置

    通过getter setter 模板设置 在实体类中 自动添加 @XmlTransient  按 [Alt+Insert],弹出的提示中选择 Getter and Setter Xml Entity ...