惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 布谷鸟过滤器 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading 布谷鸟过滤器 Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式
TCP 100 万长连接的参数调优
2019-01-05 · via 蛮荆

2019-01-05 计算机网络 高性能网络编程

基本概念

1. 文件描述符限制

  • 系统级别限制:操作系统会设置一个全局的文件描述符限制,控制整个系统能同时打开的最大文件数
  • 用户级别限制:每个用户会有一个文件描述符的限制,控制这个用户能够同时打开的最大文件数
  • 进程级别限制:每个进程也会有一个文件描述符的限制,控制单个进程能够同时打开的最大文件数

2. 服务器 TCP 连接数量上限

一个服务端的 TCP 网络应用,理论上可以支持的最大连接数量是多少?

$$ TCP 四元组 = 客户端 IP + 客户端 Port + 服务端 IP + 服务端 Port $$

其中服务端 IP、服务端 Port 已经固定了 (就是监听的 TCP 程序),所以理论的连接数量上限就取决于 (客户端 IP * 客户端 Port) 的组合数量了。

$$ 客户端 IP 数量 * 客户端 Port 数量 = 2^{32} * 2^{16} = 2^{48} $$

当然如果服务端程序监听 1 ~ 65535 的所有端口号,理论的连接数量上限就变为:

$$ 2^{32} * 2^{16} * 2^{16} = 2^{64} $$

当然实际情况下肯定达不到这样的上限数量,原因有三:

  1. IP 地址中有分类地址 (A, B, C 类)、内网地址、保留地址 (D, E 类),其中后两者无法用于公网通信
  2. 某些端口会被保留,仅供专门程序使用,例如 DNS (53), HTTPS (443)
  3. 服务器内存大小有上限,一个 TCP 套接字会关联内存缓冲区、文件描述符等资源

综上所述,一个服务端的 TCP 网络应用,可以支持的最大连接数量主要取决于其内存大小 (内核参数都已经调优的情况下)。

如何测试?

在测试设备不充足的情况下,如何测试百万连接数量场景?核心思路:突破 TCP 四元组限制即可。

  1. 客户端配置多个 IP, 这样每个 IP 地址就有大约 64K 个端口号可以使用,向服务端发起连接之前,绑定不同的 IP 地址即可
  2. 服务端监听多个端口号,客户端只需要连接不同的服务端号口即可

too many open files

首先来看一个高并发场景下的 “经典问题”: too many open files, 产生这个问题的根本原因是: 短时间内打开大量网络 (文件) 连接,超过了操作系统对单个进程允许打开的文件描述符(file descriptor)数量限制

想要单机支持 100 万链接,需要调优哪些参数呢?

解决方案

Soft open files 是 Linux 系统参数,影响系统单个进程能够打开最大的文件句柄数量。

$ ulimit -n

# 默认输出 1024 或者 65535
1024

表示单个进程同时最多只能维持 1024 个网络 (例如 TCP) 连接。

可以通过增大该参数,来支持更大的网络连接数量。

1. 临时性调整

只在当前会话 (终端) 中有效,退出或重启后失效

$ ulimit -HSn 1048576

2. 永久性设置

修改配置文件 /etc/security/limits.conf:

$ sudo vim /etc/security/limits.conf

# 追加如下内容 (例如支持百万连接)
# 重启永久生效

# 单个进程可以打开的最大进程数量
#   表示可以针对不同用户配置不同的值 
#   当然实际情况中,网络应用一般会独享整个主机/容器所有资源
# 调整文件描述符限制
# 注意: 实际生效时会以两者中的较小值为准 (所以最好的方法就是保持两个值相同)
* soft nofile 1048576
* hard nofile 1048576
root soft nofile 1048576
root hard nofile 1048576

运行 sysctl -p 命令生效,重启之后仍然有效。

3. 其他设置

单个进程打开的文件描述符数量 不能超过 操作系统所有进程文件描述符数量 (/proc/sys/fs/file-max), 所以需要修改对应的值:

$ sudo vim /etc/sysctl.conf

# 操作系统所有进程一共可以打开的文件数量
# 增加/修改以下内容
# 注意: 该设置只对非 root 用户进行限制, root 不受影响
fs.file-max = 16777216

# 进程级别可以打开的文件数量
# 或者可以设置为一个比 soft nofile 和 hard nofile 略大的值
fs.nr_open = 16777216

运行 sysctl -p 命令生效,重启之后仍然有效。

4. 查看配置

$ cat /proc/sys/fs/file-nr

# 第一个数表示当前系统使用的文件描述符数
# 第二个数表示分配后已释放的文件描述符数
# 第三个数等于 file-max
1344    0       1048576

Linux 内核参数调优

想要单机支持 100 万链接,除了刚才的 文件描述符数量 参数调优之外,还需要针对部分内核参数进行调优。

打开系统配置文件 /etc/sysctl.conf,增加 (或修改) 以下配置数据,参数名称及其作用已经写在了注释中。

# 设置系统的 TCP TIME_WAIT 数量,如果超过该值
# 不需要等待 2MSL,直接关闭
net.ipv4.tcp_max_tw_buckets = 1048576

# 将处于 TIME_WAIT 状态的套接字重用于新的连接
# 如果新连接的时间戳 大于 旧连接的最新时间戳
# 重用该状态下的现有 TIME_WAIT 连接,这两个参数主要针对接收方 (服务端)
# 对于发送方 (客户端) ,这两个参数没有任何作用
net.ipv4.tcp_tw_reuse = 1
# 必须配合使用
net.ipv4.tcp_timestamps = 1

# 启用快速回收 TIME_WAIT 资源
# net.ipv4.tcp_tw_recycle = 1
# 能够更快地回收 TIME_WAIT 套接字
# 此选项会导致处于 NAT 网络的客户端超时,建议设置为 0
# 因为当来自同一公网 IP 地址的不同主机尝试与服务器建立连接时,服务器会因为时间戳的不匹配而拒绝新的连接
# 这是因为内核会认为这些连接是旧连接的重传
# 该配置会在 Linux/4.12 被移除 
# 在之后的版本中查看/设置会提示 "cannot stat /proc/sys/net/ipv4/tcp_tw_recycle"
# net.ipv4.tcp_tw_recycle = 0

# 缩短 Keepalive 探测失败后,连接失效之前发送的保活探测包数量
net.ipv4.tcp_keepalive_probes = 3

# 缩短发送 Keepalive 探测包的间隔时间
net.ipv4.tcp_keepalive_intvl = 15

# 缩短最后一次数据包到 Keepalive 探测包的间隔时间

# 减小 TCP 连接保活时间
# 决定了 TCP 连接在没有数据传输时,多久发送一次保活探测包,以确保连接的另一端仍然存在
# 默认为 7200 秒
net.ipv4.tcp_keepalive_time = 600

# 控制 TCP 的超时重传次数,决定了在 TCP 连接丢失或没有响应的情况下,内核重传数据包的最大次数
# 如果超过这个次数仍未收到对方的确认包,TCP 连接将被终止
net.ipv4.tcp_retries2 = 10

# 缩短处于 TIME_WAIT 状态的超时时间
# 决定了在发送 FIN(Finish)包之后,TCP 连接保持在 FIN-WAIT-2 状态的时间 (对 FIN-WAIT-1 状态无效)
# 主要作用是在 TCP 连接关闭时,为了等待对方关闭连接而保留资源的时间
# 如果超过这个时间仍未收到 FIN 包,连接将被关闭
# 更快地检测和释放无响应的连接,释放资源
net.ipv4.tcp_fin_timeout = 15

# 调整 TCP 接收和发送窗口的大小,以提高吞吐量
# 三个数值分别是 min,default,max,系统会根据这些设置,自动调整 TCP 接收 / 发送缓冲区的大小
net.ipv4.tcp_mem = 8388608 12582912 16777216
net.ipv4.tcp_rmem = 8192 87380 16777216
net.ipv4.tcp_wmem = 8192 65535 16777216

# 定义了系统中每一个端口监听队列的最大长度
net.core.somaxconn = 65535

# 增加半连接队列容量
# 除了系统参数外 (net.core.somaxconn, net.ipv4.tcp_max_syn_backlog)
# 程序设置的 backlog 参数也会影响,以三者中的较小值为准
net.ipv4.tcp_max_syn_backlog = 65535

# 全连接队列已满后,如何处理新到连接 ?
# 如果设置为 0 (默认情况)
#   客户端发送的 ACK 报文会被直接丢掉,然后服务端重新发送 SYN+ACK (重传) 报文
#       如果客户端设置的连接超时时间比较短,很容易在这里就超时了,返回 connection timeout 错误,自然也就没有下文了
#       如果客户端设置的连接超时时间比较长,收到服务端的 SYN+ACK (重传) 报文之后,会认为之前的 ACK 报文丢包了
#       于是再次发送 ACK 报文,也许可以等到服务端全连接队列有空闲之后,建立连接完成
#   当服务端重试次数到达上限 (tcp_synack_retries) 之后,发送 RST 报文给客户端
#       默认情况下,tcp_synack_retries 参数等于 5, 而且采用指数退避算法
#       也就是说,5 次的重试时间间隔为 1s, 2s, 4s, 8s, 16s, 总共 31s
#       第 5 次重试发出后还要等 32s 才能知道第 5 次重试也超时了,所以总共需要等待 1s + 2s + 4s+ 8s+ 16s + 32s = 63s
# 如果设置为 1
#   服务端直接发送 RST 报文给客户端,返回 connection reset by peer
#   设置为 1, 可以避免服务端给客户端发送 SYN+ACK
#   但是会带来另外一个问题: 客户端无法根据 RST 报文判断出,服务端拒绝的具体原因:
#   因为对应的端口没有应用程序监听,还是全队列满了
# 除了系统参数外 (net.core.somaxconn)
# 程序设置的 backlog 参数也会影响,以两者中的较小值为准
# 所以全连接队列大小 = min(backlog, somaxconn)
net.ipv4.tcp_abort_on_overflow = 1

# 增大每个套接字的缓冲区大小
net.core.optmem_max = 81920
# 增大套接字接收缓冲区大小
net.core.rmem_max = 16777216
# 增大套接字发送缓冲区大小
net.core.wmem_max = 16777216

# 增加网络接口队列长度,可以避免在高负载情况下丢包
# 在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数量
net.core.netdev_max_backlog = 65535

# 增加连接追踪表的大小,可以支持更多的并发连接
# 注意:如果防火墙没开则会提示 error: "net.netfilter.nf_conntrack_max" is an unknown key,忽略即可
net.netfilter.nf_conntrack_max = 1048576

# 缩短连接追踪表中处于 TIME_WAIT 状态连接的超时时间
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30

运行 sysctl -p 命令生效,重启之后仍然有效。

注意事项

如果系统已经使用了参数 net.ipv4.tcp_syncookies, 参数 net.ipv4.tcp_max_syn_backlog 将自动失效,详情见 之前的文章

客户端参数

当服务器充当 “客户端角色” 时 (例如代理服务器),连接后端服务器器时,每个连接需要分配一个临时端口号。

# 查询系统配置的临时端口号范围
$ sysctl net.ipv4.ip_local_port_range

# 增加系统配置的临时端口号范围
$ sysctl -w net.ipv4.ip_local_port_range="10000 65535"

Reference

图片来源: https://time.geekbang.org/column/intro/100020901

图片来源: https://time.geekbang.org/column/intro/100020901

扩展阅读