热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

58房产Nginx网络调优实践

背景分析问题

导语

本文讲述了 Nginx 作为负载均衡组件,在应对超高并发的情况下所遇到性能问题,以及针对这些具体问题的分析、定位、并最终通过整体调优 kernel、网卡、Nginx 参数,达到 QPS 从20000+ 提高到 40000+ 的完整过程。。

背景

58 集团房产 HBG 有一些内网 API ,通讯使用的是 HTTP协议,并且使用 Nginx 做负载均衡。随着业务增长,发现高峰期有很多 HTTP 错误,其中最多的错误反映在 PHP curl 中就是 “Connection timed out after xx ”,但此时 Nginx 服务器本身负载却非常低,因此肯定不是服务器出现瓶颈导致超时的问题,基于此背景需要对 Nginx 做网络调优,来解决超时的问题。

分析问题

前面提到了业务出现最多的错误是 “Connection timed out after xx”,这个错误其实就是 TCP 握手失败,握手失败其实就是丢包,丢包最常见原因如下

A、网卡丢包(包含syn 包和数据包)

B、内核 syn 包丢失

C、中间链路丢包,比如交换机、路由器

本文章重点说明原因A和B。

解决问题

1、 解决网卡丢包的问题 我们能从 ifconfig 命令中看到网卡的相关统计信息,其中和错误相关的有 3 个分别是 overruns、dropped 、errors。

58房产Nginx 网络调优实践

A、errors : 收到的数据包错误,比如 CRC 校验错误,不常见

B、dropped : 从网卡fifo 队列复制到 内核空间错误,比如内核无内存可用,不常见

C、overruns:数据进入网卡 fifo 队列时被丢弃(就是没有进入网卡)最常见的错误,常见的原因就是中断不均衡,默认内核将所有的网卡中断都绑定在 CPU core 0 ,高并发下 CPU core 0 有太多中断,当处理不过来时,fifo 队列没有被及时消费,后续数据没有进入网卡直接被丢弃。

1.1 网卡 overruns 如何解决

前分析网卡 overruns 是因为中断都默认绑定在了 CPU core 0 ,比如下图 CPU core 0 全部时间都在处理软中断,肯定有很多中断来不及响应。

58房产Nginx 网络调优实践

所以只要将网卡中断和 CPU core num 进行一一绑定就行,举例如下

$ cat /proc/interrupts | grep eth | awk '{print $1,$NF}'

77: eth0-0

78: eth0-1

79: eth0-2

80: eth0-3

81: eth0-4

82: eth0-5

83: eth0-6

84: eth0-7


$ echo 0 > /proc/irq/77/smp_affinity_list

$ echo 1 > /proc/irq/78/smp_affinity_list

.....

2、解决内核 syn 包丢失的问题 通过抓包发现,基本上都是 TCP 握手的第一个 syn 包发了两次,并且间隔 1s ,这种现象很明显就是包丢弃,因为抓包能抓到,所以第一个被明确丢弃,具体如下图

58房产Nginx 网络调优实践

内核针对 syn 包丢弃有 3 个统计参数 TcpExtListenOverflows、TcpExtListenDrops、TcpExtTCPBacklogDrop,如何查看如下

$ nstat -za | grep -Ei '(TcpExtListenOverflows|TcpExtListenDrops|TcpExtTCPBacklogDrop)'

TcpExtListenOverflows 0 0.0

TcpExtListenDrops 0 0.0

TcpExtTCPBacklogDrop 0 0.0

可以看到 syn 包丢失有多种原因,下面逐步分析其中典型的 3 个原因。

2.1 syn 包丢失 - TcpExtListenOverflows & TcpExtListenDrops 同时增大

此案例比较简单,从 TcpExtListenOverflows 明显可以看出是溢出导致,具体就是 TCP 三次握手成功的 accept-queue 满了,nginx 还没有来得及 accept ,此时新进来的握手请求只能丢弃,具体如下图

58房产Nginx 网络调优实践

很明显这种 case 只需要适当增加 accept-queue 大小就行
acccept-queue = min(nginx backlog, net.core.somaxconn)
nginx backlog 默认 511
net.core.somaxconn 默认 128
如果系统未修改参数,那么默认 accept-queue= 128 ,这个值太小了,一般 2048 比较合适 具体调整如下,网络上介绍的文章比较多
注:nginx backlog 是全局配置 ,一个 port 只需要在一个地方添加即可
案例 2.1 解决方案

# linux kernel

sysctl -w net.core.somaxcOnn=2048

sysctl -w net.ipv4.tcp_max_syn_backlog=4096


# nginx

server {

listen 80 backlog=2048

.....

}

2.2 syn 包丢失 - TcpExtListenOverflows 不变 & TcpExtListenDrops增大
相比 3.1.1 案例,这个案例 TcpExtListenOverflows 不变,原因更加复杂。当时解决问题的时候也是偶然发现内核有错误堆栈,才分析出具体原因。
具体原因是 TCP 的内存分配采用的是 slab + buddy算法,slab 块不够时,通过buddy 内存分配算法申请内存,如果高并发情况下 buddy 分配的内存刚好用完(此时系统仍然有可用的 free 内存),此时会因为申请不到 buddy 内存而直接丢弃TCP 请求,具体堆栈如下,这个堆栈网上能搜到很多,有其他程序比如 MySQL 也会出现

58房产Nginx 网络调优实践

这个堆栈有几个重要的信息
A、nginx tcp_v4_syn_recv_sock (第一个syn 请求过来) 时申请内存失败
B、TCP 申请内存用的是 slab 算法 (kmem_cache_alloc)
C、kmem_cache_alloc申请失败时,通过 buddy 内存算法来补充 slab 空间
D、申请内存的 buddy order = 1 表示需要申请的内存大小是 2 * 4k = 8k
简单介绍下 buddy 内存算法
buddy 内存情况可以通过 /proc/buddyinfo 来查看

58房产Nginx 网络调优实践

简单说明 第 3 列 556 表示 numa node 0 Normal 区域 order = 2 有 556 块,占用大小是 556 * 2 * 2 * 4K,也就是有556 个连续的16K内存
buddy 内存分配原则是高级别可以给低级别用 ,但是低级别不能给高级别用
比如 order = 1 可用,order = 0 不可用,此时如果申请 order = 0,会从 order = 1 借用1个,拆成2个,1个给申请方,另外一个划归 order = 0
buddy 内存是预分配的,可能在高并发瞬间用完,这个时候就会有内存分配失败的情况。
下面的图是出问题时 nginx 的 buddyinfo 监控

可以发现 Normal 区域 order = 0 有 174851 块,但是 order > 0 都没有可用内存,nginx 刚好要申请的 order = 1,所以申请内存失败,syn 包丢弃
针对这种情况阿里的工程师给了一个解决方案

# numa 在当前node回收内存

sysctl -w vm.zone_reclaim_mode=1

# 调大系统最小内存阈值

sysctl -w vm.min_free_kbytes=512000

真实测试下来并没有起到做大作用,只能再进行深入分析。首先发现 Nginx 机器物理内存非常大有 128G,进一步分析发现大部分内存都处于 cache 模式,此时可以联想到大部分内存都因为写巨大的 nginx 日志而处于文件 cache 模式,解决问题的关键变成为尽快释放 cache 缓存。
通过 echo 1 > /proc/sys/vm/drop_caches 释放 cache 会瞬间影响机器负载,还好 linux 有个 vm.extra_free_kbytes 参数,可以控制回收内存的时机
案例 2.2 解决方案

# 设置更大的水位线,让 kswapd 提前启动,

# 比如我们nginx 服务器内存是128G,但是由于写入的日志非常大,大量的日志

# 使得内存都被划分到 cache 区域,只有 kswapd 启动后才会回收

sysctl -w vm.extra_free_kbytes= 1048576


# 调大系统最小内存阈值

sysctl -w vm.min_free_kbytes=1048576

2.3 syn 包丢失 - TcpExtTCPBacklogDrop 增大
相比前面2个 案例 3.2.1、3.2.2, 这个更加复杂也难易理解,主要是因为是在多核多进程 lock 时发生,先看下面流程图,相比 3.2.1 的图更加复杂,当某一个核 比如 CPU core 0 处于 accept ->lock 状态, 这个时候,其他 CPU core 收到的 syn 数据包不能直接进入 syn-queue ,而是进入到了 backlog-queue ,backlog-queue 的大小是sk_rcvbuf + sk_sndbuf

58房产Nginx 网络调优实践

假如默认内核参数如下
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
那么默认 backlog-queue = 87380 + 65536 = 152916
看起来还挺大,但是高并发下面其实还是比较小的,backlog 每次进去的数据大小是 800 ~ 1800,不同网卡驱动有些区别,也就是lock 期间最多进入的请求为 152916/{800,1800} = 190 ~ 80,确实有些小。
2种方式增大 backlog-queue
A、调整内核参数 ,修改中间的值 net.ipv4.tcp_rmem & net.ipv4.tcp_wmem
B、nginx 提供了修改的方式,比如如下 server backlog=2048 rcvbuf=131072 sndbuf=131072
修改后的效果,可以看出增大 rcvbuf & sndbuf 还是很有效果 但是有一个机器有些问题,效果不明显

58房产Nginx 网络调优实践

继续分析
上面看到,之所以会产生 TcpExtTCPBacklogDrop 是因为多 CPU core 同时 accept,如果有 1 个 cpu core lock 了 sock,会导致其他 CPU core 收到请求后只能放到 backlog-queue,如果降低lock ,那么就从根本上解决, Nginx 提供了 reuseport 的功能(需要内核支持 SO_REUSEPORT)
无 reuseport 的情况如下图,多个 cpu core 竞争 一个 sock->accept-queue,

58房产Nginx 网络调优实践

使用 reuseport 后如下图,每个cpu core 都分配 1 个一套资源,避免了竞争

58房产Nginx 网络调优实践

开启 reuseport 后,nginx 同一个监听端口会出现多条记录,也就对应多个内核 sock,并且多个 accept-queue,syn-queue,backlog-queue。

58房产Nginx 网络调优实践

效果如下图

58房产Nginx 网络调优实践

2.3 解决方案总结
增加 reuseport 选项

# nginx

server {

listen 80 backlog=2048 rcvbuf=131072 sndbuf=131072 reuseport;

.....

}

3、优化总结

1. 通过绑定网卡中断号到 CPU core ,解决网卡 overruns

2. 调整 backlog 来解决 TcpExtListenOverflows++ & TcpExtListenDrops++

3. 调整 vm.extra_free_kbytes && vm.min_free_kbytes 解决 buddy memory 分配失败的问题导致的 TcpExtListenDrops++

4. nginx 增大 rcvbuf & sndbuf ,并且开启 reuseport 来解决 TcpExtTCPBacklogDrop++

4、内存说明

4.1 backlog 每次进去的数据大小是 800 ~ 1800
backlog 进入的是一个数据包,也就是 1 个 sk_buff,1个 sk_buff会包含 (sizeof sk_buff)+ data (比如 1500 以太包最大值),此时并没有进入 TCP 栈处理。
4.2 申请内存的 buddy order = 1 表示需要申请的内存大小是 2 * 4k = 8k
内核内存的特点就是大小固定(因为结构体都是固定大小),针对这种场景内核设计了 slab 内存算法,将大块内存分割成固定的小块,大块内存采用 buddy 算法,小块内存采用 slab 算法,比如固定申请大小为 2600 大小的内存,就可以采用 2个连续的4K内存 2700 * 3 = 8100 ,这样可以分成3个小块,实际其实更加复杂,可以参考后面的参考文献,slab 的情况可以查看 /proc/slabinfo
slab 情况查看如下

58房产Nginx 网络调优实践

总结

因为篇幅原因每个案例只能简单介绍,继续深挖还可以写很多内容,比如 backlog 为啥 511 有点小。Nginx 调优的地方有很多,本文章只介绍了网络的部分,其中部分是网络上已经很成熟的方案,其他的都是自己通过实践总结的,并且起到作用。调优本身是一个实践的过程,本文讲述的内容都是实践过并且有用的,但是不一定在其他场景100%适用,不过分析过程可以作为参考。

参考文献

Linux Used内存到底哪里去了?:http://blog.yufeng.info/archives/2456

Linux内核分析:页回收导致的cpu load瞬间飙高的问题分析与思考 :http://www.mamicode.com/info-detail-1420432.html

伙伴系统之避免碎片--Linux内存管理(十六) :https://blog.csdn.net/gatieme/article/details/52694362

Linux page allocation failure 的问题处理 - zonereclaimmode :https://yq.aliyun.com/articles/228285/

Linux服务器Cache占用过多内存导致系统内存不足问题的排查解决(续):https://www.cnblogs.com/panfeng412/p/drop-caches-under-linux-system-2.html

如何释放Linux的cache :https://blog.csdn.net/william_m999/article/details/94898565

tcp的半连接与完全连接队列:https://www.jianshu.com/p/ff26312e67a9

tcp sk_backlog(后备队列分析): https://www.cnblogs.com/alreadyskb/p/4386565.html

TCP套接口的skbacklog接收队列: https://blog.csdn.net/sinat20184565/article/details/88861077

kernel 3.10内核源码分析--slab原理及相关代码 : http://blog.chinaunix.net/uid-20671208-id-4655765.html

作者简介

宋武斌,HBG二手房经纪人技术部架构师,目前负责三网经纪人APP以及三网后端技术架构的优化和建设工作。

阅读推荐


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 我们


推荐阅读
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 深入解析 Synchronized 锁的升级机制及其在并发编程中的应用
    深入解析 Synchronized 锁的升级机制及其在并发编程中的应用 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 在优化Nginx与PHP的高效配置过程中,许多教程提供的配置方法存在诸多问题或不良实践。本文将深入探讨这些常见错误,并详细介绍如何正确配置Nginx和PHP,以实现更高的性能和稳定性。我们将从Nginx配置文件的基本指令入手,逐步解析每个关键参数的最优设置,帮助读者理解其背后的原理和实际应用效果。 ... [详细]
  • Nginx 反向代理配置与应用指南
    本文详细介绍了 Nginx 反向代理的配置与应用方法。首先,用户可以从官方下载页面(http://nginx.org/en/download.html)获取最新稳定版 Nginx,推荐使用 1.14.2 版本。下载并解压后,通过双击 `nginx.exe` 文件启动 Nginx 服务。文章进一步探讨了反向代理的基本原理及其在实际应用场景中的配置技巧,包括负载均衡、缓存管理和安全设置等,为用户提供了一套全面的实践指南。 ... [详细]
  • 在 CentOS 6.5 系统上部署 VNC 服务器的详细步骤与配置指南
    在 CentOS 6.5 系统上部署 VNC 服务器时,首先需要确认 VNC 服务是否已安装。通常情况下,VNC 服务默认未安装。可以通过运行特定的查询命令来检查其安装状态。如果查询结果为空,则表明 VNC 服务尚未安装,需进行手动安装。此外,建议在安装前确保系统的软件包管理器已更新至最新版本,以避免兼容性问题。 ... [详细]
  • 尽管我们尽最大努力,任何软件开发过程中都难免会出现缺陷。为了更有效地提升对支持部门的协助与支撑,本文探讨了多种策略和最佳实践,旨在通过改进沟通、增强培训和支持流程来减少这些缺陷的影响,并提高整体服务质量和客户满意度。 ... [详细]
  • 探讨Redis的最佳应用场景
    本文将深入探讨Redis在不同场景下的最佳应用,包括其优势和适用范围。 ... [详细]
  • 本文介绍如何通过 Python 的 `unittest` 和 `functools` 模块封装一个依赖方法,用于管理测试用例之间的依赖关系。该方法能够确保在某个测试用例失败时,依赖于它的其他测试用例将被跳过。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 在什么情况下MySQL的可重复读隔离级别会导致幻读现象? ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 性能测试中的关键监控指标与深入分析
    在软件性能测试中,关键监控指标的选取至关重要。主要目的包括:1. 评估系统的当前性能,确保其符合预期的性能标准;2. 发现软件性能瓶颈,定位潜在问题;3. 优化系统性能,提高用户体验。通过综合分析这些指标,可以全面了解系统的运行状态,为后续的性能改进提供科学依据。 ... [详细]
author-avatar
dgsfdg3t4543
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有