阿萍的博客 - Linux 性能优化
type: channel
link: https://aping-dev.com/index.php/category/Linux-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/
items: 套路篇：监控的思路
items: 案例篇：动态追踪怎么用？
items: 案例篇：服务吞吐量下降很厉害，怎么分析？
items: 案例篇：内核线程 CPU 利用率太高，我该怎么办？
items: 案例篇：为什么应用容器化后，启动慢了很多？
items: 服务器总是时不时丢包，我该怎么办？
items: 套路篇：网络性能优化的几个思路
items: 案例篇：网络请求延迟变大了，我该怎么办？
items: 案例篇：如何优化 NAT 性能？
items: 案例篇：怎么缓解 DDoS 攻击带来的性能下降问题？

套路篇：监控的思路
type: item
date: 2021-11-28T11:18:00+00:00
description: 1 系统监控系统监控需要涵盖系统的整体资源使用情况，比如 CPU、内存、磁盘、文件系统和网络等各种系统资源。1.1 USE 法USE 法把系统资源的性能指标简化为了三个类别，使用率、饱和度和错误数。USE 方法的指标直接表明了系统的资源瓶颈。1.2 监控系统需要建立监控系统，将监控的性能指标存下来；然后，根据监控到的状态自动分析和定位大概的瓶颈来源；最后，再通过告警系统把问题及时汇报给相关的团队处理。一个完整的监控系统包括数据采集、数据存储、数据查询、数据处理、告警、可视化等多个模块。开源的监控工具包括 Zabbix、Nagios、Prometheus 等。Prometheus 架构图：Retrivel 负责采集数据，包括 Pull 模式和 Push 模式。TSDB 负责将采集到的数据持久化到 SSD 等磁盘中，还提供了数据查询和基本的数据处理功能，也就是 PromQL 语言。Alertmanager 提供了告警功能，包括基于 PromQL 语言的触发条件、告警规则的配置管理以及告警的发送等。还支持通过分组、抑制或静默等多种方式来聚合同类告警，并减少告警数量。web UI 提供了简单的可视化界面，用于执行 PromQL 查询语句，配合 Grafana 可以构建强大的图形界面。2 应用监控应用程序的核心指标，不再是资源的使用情况，而是请求数、错误率和响应时间。这些指标不仅直接关系到用户的使用体验，还反映应用整体的可用性和可靠性。另外，如下三个指标也很重要：进程的资源使用情况。应用程序之间的调用情况。应用程序内部核心逻辑执行情况，需要应用程序提供指标。为了迅速定位跨应用的性能瓶颈，可以使用 Zipkin、Jaeger、Pinpoint 等各类开源工具，来构建全链路跟踪系统。全链路跟踪除了可以快速定位跨应用的性能问题外，还可以生成线上系统的调用拓扑图。2.1 日志监控日志是指标上下文信息的来源。对日志监控来说，最经典的方法，就是使用 ELK 技术栈，即使用 Elasticsearch、Logstash 和 Kibana 这三个组件的组合。Logstash 负责从各个日志源采集日志，然后进行预处理。Elasticsearch 负责对日志索引，提供搜索引擎。Kibana 负责对日志进行可视化分析，包括日志搜索、处理、仪表板展示等。在资源紧张的环境中，往往使用资源消耗更低的 Fluentd 替代 Logstash。参考倪朋飞. Linux 性能优化实战.

案例篇：动态追踪怎么用？
type: item
date: 2021-11-27T11:21:00+00:00
description: 使用动态追踪，可以在不修改代码、不重启服务的情况下，动态了解应用程序或者内核的行为，这对排查线上问题、特别是不容易重现的问题尤其有效。Linux 提供了一系列的动态追踪机制，比如 ftrace、perf、eBPF 等1 ftrace当想要了解某个内核函数的调用过程时， 可以使用 ftrace 跟踪到它的执行过程。ftrace 的使用接口与 procfs 类似，通过 debugfs 以普通文件的形式向用户空间提供访问接口。切到 debugfs 的挂载点：$ cd /sys/kernel/debug/tracing $ ls README instances set_ftrace_notrace trace_marker_raw available_events kprobe_events set_ftrace_pid trace_options ...查询所有支持的跟踪器：$ cat available_tracers hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nopfunction 表示跟踪函数执行。funcion_graph 表示跟踪函数的调用关系。查询支持的函数和事件：$ cat available_filter_functions $ cat available_events接下来以 ls 命令为例，看下 ftrace 的使用方法。一：ls 调用 open 系统调用打开目录文件，而 open 在内核中对应的函数名称为 do_sys_open。所以，需要将跟踪函数设为 open：$ echo do_sys_open > set_graph_function二：配置跟踪项，开启函数调用跟踪，并跟踪调用进程：$ echo function_graph > current_tracer $ echo funcgraph-proc > trace_options三：开启跟踪：$ echo 1 > tracing_on四：执行一个 ls 命令后，再关闭跟踪：$ ls $ echo 0 > tracing_on五：查看跟踪结果：$ cat trace # tracer: function_graph # # CPU TASK/PID DURATION FUNCTION CALLS # | | | | | | | | | 0) ls-12276 | | do_sys_open() { 0) ls-12276 | | getname() { 0) ls-12276 | | getname_flags() { 0) ls-12276 | | kmem_cache_alloc() { 0) ls-12276 | | _cond_resched() { 0) ls-12276 | 0.049 us | rcu_all_qs(); 0) ls-12276 | 0.791 us | } 0) ls-12276 | 0.041 us | should_failslab(); 0) ls-12276 | 0.040 us | prefetch_freepointer(); 0) ls-12276 | 0.039 us | memcg_kmem_put_cache(); 0) ls-12276 | 2.895 us | } 0) ls-12276 | | __check_object_size() { 0) ls-12276 | 0.067 us | __virt_addr_valid(); 0) ls-12276 | 0.044 us | __check_heap_object(); 0) ls-12276 | 0.039 us | check_stack_object(); 0) ls-12276 | 1.570 us | } 0) ls-12276 | 5.790 us | } 0) ls-12276 | 6.325 us | } ...第一列是运行的 CPU第二列是任务名称和进程 ID第三列是函数执行延迟最后一列是函数调用关系图也可以用 trace-cmd 代替 ftrace。安装 trace-cmd：# Ubuntu $ apt-get install trace-cmd # CentOS $ yum install trace-cmdftrace 的五步可简化为两步：$ trace-cmd record -p function_graph -g do_sys_open -O funcgraph-proc ls $ trace-cmd report ... ls-12418 [000] 85558.075341: funcgraph_entry:…

案例篇：服务吞吐量下降很厉害，怎么分析？
type: item
date: 2021-11-27T11:20:00+00:00
description: 1 现象查看服务性能：# 默认测试时间为 10s，请求超时 2s $ wrk --latency -c 1000 http://192.168.0.30 Running 10s test @ http://192.168.0.30 2 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 14.82ms 42.47ms 874.96ms 98.43% Req/Sec 550.55 1.36k 5.70k 93.10% Latency Distribution 50% 11.03ms 75% 15.90ms 90% 23.65ms 99% 215.03ms 1910 requests in 10.10s, 573.56KB read Non-2xx or 3xx responses: 1910 Requests/sec: 189.10 Transfer/sec: 56.78KB吞吐量只有 189，并且所有 1910 个请求收到的都是异常响应（非 2xx 或 3xx）。吞吐量太低了，并且请求处理都失败了。根据 wrk 输出的统计结果，可以看到总共传输的数据量只有 573 KB，肯定不是带宽受限导致的。所以，应该从请求数的角度来分析。2 排查2.1 连接数优化分析请求数，特别是 HTTP 的请求数，可以从 TCP 连接数入手。将测试延长到 30min：$ wrk --latency -c 1000 -d 1800 http://192.168.0.30在另一个终端中观察 TCP 连接数：$ ss -s Total: 177 (kernel 1565) TCP: 1193 (estab 5, closed 1178, orphaned 0, synrecv 0, timewait 1178/0), ports 0 Transport Total IP IPv6 * 1565 - - RAW 1 0 1 UDP 2 2 0 TCP 15 12 3 INET 18 14 4 FRAG 0 0 0wrk 并发 1000 请求时，建立连接数只有 5，而 closed 和 timewait 状态的连接则有 1100 多。内核中的连接跟踪模块，有可能会导致 timewait 问题。服务运行在 Docker 内，而 Docker 使用的 iptables ，就会使用连接跟踪模块来管理 NAT。通过 dmesg 查看系统日志，如果有连接跟踪出了问题，应该会看到 nf_conntrack 相关的日志：$ dmesg | tail [88356.354329] nf_conntrack: nf_conntrack: table full, dropping packet [88356.354374] nf_conntrack: nf_conntrack: table full, dropping packet 查看当前连接跟踪数和最大限制：$ sysctl net.netfilter.nf_conntrack_max net.netfilter.nf_conntrack_max = 200 $ sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_count = 200连接跟踪数最大限制只有 200，且全部被占了。将 nf_conntrack_max 增大：# 将连接跟踪限制增大到 1048576 $ sysctl -w net.netfilter.nf_conntrack_max=1048576重新测试 nginx 性能：# 默认测试时间为 10s，请求超时 2s $ wrk --latency -c 1000 http://192.168.0.30 ... 54221 requests in 10.07s, 15.16MB read Socket errors: connect 0, read 7, write 0, timeout 110 Non-2xx or 3xx responses: 45577 Requests/sec: 5382.21 Transfer/sec: 1.50MB吞吐量已经从刚才的 189 增大到了 5382。看起来性能提升了将近 30 倍，但是有 4 万多响应异常，即真正成功的只有 8000 多个。2.2 工作进程优化由于这些响应并非 Socket error，说明 Nginx 已经收到了请求，只不过，响应的状态码并不是我们期望的 2xx （表示成功）或 3xx（表示重定向）。所以，需要搞清楚 Nginx 真正的响应。查看 nginx 容器日志：$ do…

案例篇：内核线程 CPU 利用率太高，我该怎么办？
type: item
date: 2021-11-21T11:22:00+00:00
description: 在排查网络问题时，经常会碰到的一个问题是，内核的 CPU 利用率较高。比如高并发场景下，内核线程 ksoftrqd 的 CPU 使用率就会比较高，是网络收发的软中断导致的。接下来看下如何分析内核线程的性能问题。1 内核线程Linux 启动中有 3 个特殊的进程：0 号进程为 idle 进程，在初始化完 1 号和 2 号进程后演变为空闲任务。1 号进程为 init 进程，通常是 systemd 进程，在用户态运行，管理其他的用户态进程。2 号进程 kthreadd 进程，在内核态运行，管理内核线程。所以要查找内核线程，从 2 和进程开始，找它的子孙即可：$ ps -f --ppid 2 -p 2 UID PID PPID C STIME TTY TIME CMD root 2 0 0 12:02 ? 00:00:01 [kthreadd] root 9 2 0 12:02 ? 00:00:21 [ksoftirqd/0] root 10 2 0 12:02 ? 00:11:47 [rcu_sched] root 11 2 0 12:02 ? 00:00:18 [migration/0] ... root 11094 2 0 14:20 ? 00:00:00 [kworker/1:0-eve] root 11647 2 0 14:27 ? 00:00:00 [kworker/0:2-cgr]容易发送性能问题的内核线程：kswapd0：用于内存回收。kworker：执行内核工作队列，分为绑定 CPU 和未绑定 CPU 两类。migration：负载均衡过程中，将进程迁移到 CPU 上。每个 CPU 都有一个 migration 内核线程。jbd2/sda1-8：journaling blocking device，为文件系统提供日志功能。sda1-8 表示磁盘分区和设备号。每个使用 ext4 文件系统的磁盘分区都有一个 jbd2 内核线程。pdflush：将内存中的脏页写入磁盘。已在 3.10 中和入到 kworker 中。2 现象运行 hping3 命令，模拟客户端请求：# -S 参数表示设置 TCP 协议的 SYN（同步序列号），-p 表示目的端口为 80 # -i u10 表示每隔 10 微秒发送一个网络帧 # 注：如果你在实践过程中现象不明显，可以尝试把 10 调小，比如调成 5 甚至 1 $ hping3 -S -p 80 -i u10 192.168.0.30观察系统和进程 CPU 使用情况：$ top top - 08:31:43 up 17 min, 1 user, load average: 0.00, 0.00, 0.02 Tasks: 128 total, 1 running, 69 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.3 us, 0.3 sy, 0.0 ni, 66.8 id, 0.3 wa, 0.0 hi, 32.4 si, 0.0 st %Cpu1 : 0.0 us, 0.3 sy, 0.0 ni, 65.2 id, 0.0 wa, 0.0 hi, 34.5 si, 0.0 st KiB Mem : 8167040 total, 7234236 free, 358976 used, 573828 buff/cache KiB Swap: 0 total, 0 free, 0 used. 7560460 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9 root 20 0 0 0 0 S 7.0 0.0 0:00.48 ksoftirqd/0 18 root 20 0 0 0 0 S 6.9 0.0 0:00.56 ksoftirqd/1 2489 root 20 0 876896 38408 21520 S 0.3 0.5 0:01.50 docker-containe 3008 root 20 0 44536 3936 3304 R 0.3 0.0 0:00.09 top 1 root 20 0 78116 9000 6432 S 0.0 0.1 0:11.77 systemd ...两个 CPU 的软中断都超过了 30%，而 CPU 使用率最高的正好是两个软中断内核线程 ksoftrqd/0、ksoftrqd/1。我们知道大概是因为大量的网络收发导致的，但是它到底在执行什么逻辑，我们却不知道，而 strace、pstack、lsof 等工具不适合内核线程。3 排查3.1 perf 分析可以用 perf 分析一下进程号为 9 的 ksoftrqd：# -a 表示追踪所有的 CPU…

案例篇：为什么应用容器化后，启动慢了很多？
type: item
date: 2021-11-20T11:37:00+00:00
description: 1 现象一启动容器：# -m 表示设置内存为512MB $ docker run --name tomcat --cpus 0.1 -m 512M -p 8080:8080 -itd feisky/tomcat:8确认容器启动：$ curl localhost:8080 curl: (56) Recv failure: Connection reset by peer2 排查查看容器日志：$ docker logs -f tomcat Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /docker-java-home/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar只打印了环境变量，说明还在启动中，所以没有响应。多次尝试访问 tomcat：$ for ((i=0;i<30;i++)); do curl localhost:8080; sleep 1; done curl: (56) Recv failure: Connection reset by peer curl: (56) Recv failure: Connection reset by peer # 这儿会阻塞一会 Hello, wolrd! curl: (52) Empty reply from server curl: (7) Failed to connect to localhost port 8080: Connection refused curl: (7) Failed to connect to localhost port 8080: Connection refused接续查看容器日志：18-Feb-2019 12:43:32.719 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/docs] 18-Feb-2019 12:43:33.725 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/docs] has finished in [1,006] ms 18-Feb-2019 12:43:33.726 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/manager] 18-Feb-2019 12:43:34.521 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/manager] has finished in [795] ms 18-Feb-2019 12:43:34.722 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] 18-Feb-2019 12:43:35.319 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"] 18-Feb-2019 12:43:35.821 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 24…

服务器总是时不时丢包，我该怎么办？
type: item
date: 2021-11-20T11:27:00+00:00
description: 1 现象# -c 表示发送 10 个请求，-S 表示使用 TCP SYN，-p 指定端口为 80 $ hping3 -c 10 -S -p 80 192.168.0.30 HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=5120 rtt=7.5 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=5120 rtt=7.4 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=3.3 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=5120 rtt=3.0 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=3027.2 ms --- 192.168.0.30 hping statistic --- 10 packets transmitted, 5 packets received, 50% packet loss round-trip min/avg/max = 3.0/609.7/3027.2 ms发送了 10 个请求包，却只收到了 5 个回复，50% 的包都丢了。2 排查可能丢包的原因：两台机器连接之间，可能会发生传输失败的错误，比如网络拥塞、线路错误等；网卡收包后，环形缓冲区可能会因为溢出而丢包；链路层，可能会因为网络帧校验失败、QoS 等而丢包；IP 层，可能会因为路由失败、组包大小超过 MTU 等而丢包；传输层，可能会因为端口未监听、资源占用超过内核限制等而丢包；套接字层，可能会因为套接字缓冲区溢出而丢包；应用层，可能会因为应用程序异常而丢包；如果配置了 iptables 规则，可能因为 iptables 过滤规则而丢包。2.1 链路层当缓冲区溢出等原因导致网卡丢包时，Linux 会在网卡收发数据的统计信息中，记录下收发错误的次数。查看网卡的丢包记录：root@nginx:/# netstat -i Kernel Interface table Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg eth0 100 31 0 0 0 8 0 0 0 BMRU lo 65536 0 0 0 0 0 0 0 0 LRU没有发现任何错误，说明容器的虚拟网卡没有丢包。不过要注意，如果用 tc 等工具配置了 QoS，那么 tc 规则导致的丢包，就不会包含在网卡的统计信息中。检查一下 eth0 上是否配置了 tc 规则，是否丢包：root@nginx:/# tc -s qdisc show dev eth0 qdisc netem 800d: root refcnt 2 limit 1000 loss 30% Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0) backlog 0b 0p requeues 0eth0 上面配置了一个网络模拟排队规则（qdisc netem），并且配置了丢包率为 30%（loss 30%）。再看后面的统计信息，发送了 8 个包，但是丢了 4 个。应该是 netem 模块丢弃了 Nginx 的响应包。删除 tc 中的 netem 模块：root@nginx:/# tc qdisc del dev eth0 root netem loss 30%重新执行 hping3 命令后，发现问题没有解决。2.2 网络层和传输层查看协议收发汇总：root@nginx:/# netstat -s Ip: Forwarding: 1 //开启转发 31 total packets received //总收包数 0 forwarded //转发包数 0 incoming packets discarded //接收丢包数 25 incoming packets delivered //接收的数据包数 15 requests sent out //发出的数据包数 Icmp: 0 ICMP messages received //收到的ICMP包数 0 input ICMP …

套路篇：网络性能优化的几个思路
type: item
date: 2021-11-14T11:37:00+00:00
description: 1 确定优化目标具体到不同的应用中，每个指标的优化标准可能不同。对于 NAT 网关，直接影响到整个数据中心的网络出入性能，需要达到或接近线性转发，PPS 是主要的性能指标。对于数据库、缓存等，需要快速完成网络收发，低延迟是主要的性能指标。对于 Web 服务，需同时兼顾吞吐量和延迟。可按照协议栈的每一层进行基准测试，底层性能决定高层性能。网络接口层和网络层，每秒可处理的网络包数 PPS 是重要的性能指标，可用内核自带的工具 pktgen 测试。传输层，吞吐量（BPS）、连接数和延迟是重要的性能指标，可用 perf 或 iperf 测试，不过要注意测试不同网络包下的性能。应用层，关注的是吞吐量（BPS）、延迟和每秒请求数等指标，可用 wrk、ab 等测试。2 网络性能工具2.1 根据指标查找工具2.1.1 吞吐量（BPS）sar 可查看网络接口的吞吐量。rxkB/s 和 txkB/s 分别是接收和发送的吞吐量，单位是 KB/ 秒。# 数字1表示每隔1秒输出一组数据 $ sar -n DEV 1 Linux 4.15.0-1035-azure (ubuntu) 01/06/19 _x86_64_ (2 CPU) 13:21:40 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil 13:21:41 eth0 18.00 20.00 5.79 4.25 0.00 0.00 0.00 0.00 13:21:41 docker0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 13:21:41 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.002.1.2 PPSsar 可查看网络接口的 PPS。rxpck/s 和 txpck/s 分别是接收和发送的 PPS，单位为包 / 秒。2.1.3 连接数# head -n 3 表示只显示前面3行 # -l 表示只显示监听套接字 # -n 表示显示数字地址和端口(而不是名字) # -p 表示显示进程信息 $ netstat -nlp | head -n 3 Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 840/systemd-resolve # -l 表示只显示监听套接字 # -t 表示只显示 TCP 套接字 # -n 表示显示数字地址和端口(而不是名字) # -p 表示显示进程信息 $ ss -ltnp | head -n 3 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=840,fd=13)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1459,fd=3))当套接字处于 Established 时：Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数（即接收队列长度）。而 Send-Q 表示还没有被远端主机确认的字节数（即发送队列长度）。当套接字处于 Listening 时：Recv-Q 表示全连接队列的长度。而 Send-Q 表示全连接队列的最大长度。2.1.4 延迟ping 通过 ICMP 测试网络延迟。# -c3表示发送三次ICMP包后停止 $ ping -c3 114.114.114.114 PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data. 64 bytes from 114.114.114.114: icmp_seq=1 ttl=54 time=244 ms 64 bytes from 114.114.114.114: icmp_seq=2 ttl=47 time=244 ms 64 bytes from 114.114.114.114: icmp_seq=3 ttl=67 time=244 ms --- 114.114.114.114 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2001ms rtt min/avg/max/mde…

案例篇：网络请求延迟变大了，我该怎么办？
type: item
date: 2021-11-13T11:40:00+00:00
description: 1 现象$ wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/ Running 10s test @ http://192.168.0.30:8080/ 2 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 43.60ms 6.41ms 56.58ms 97.06% Req/Sec 1.15k 120.29 1.92k 88.50% Latency Distribution 50% 44.02ms 75% 44.33ms 90% 47.62ms 99% 48.88ms 22853 requests in 10.01s, 18.55MB read Requests/sec: 2283.31 Transfer/sec: 1.85MB在并发请求下，平均延迟为 43ms，延迟较大。2 排查使用 tcpdump 抓包：$ tcpdump -nn tcp port 8080 -w nginx.pcap用 Wireshark 打开 nginx.pcap。由于网络包数量较多，可以过滤一下，单击右键并选择 “Follow” -> “TCP Stream”。为了更加直观，继续点击菜单栏里的 Statics -> Flow Graph，选中 “Limit to display filter” 并设置 Flow type 为 “TCP Flows”：第二次 HTTP 请求较慢，特别是客户端在收到服务器第一个分组后，40ms 后才发出了 ACK 响应（图中蓝色行）。40ms 是 TCP 延迟确认（Delayed ACK）的最小超时时间。这是 TCP 针对 ACK 的优化机制，不用每次请求都发送一个 ACK，而是等一会（比如 40ms），看有没有“顺风车”，如果恰好有其它包需要发送，则捎带着 ACK 一起发送过去，否则就超时后发送。因为 40ms 延迟发生在了客户端 wrk，所以有可能是客户端开启了延迟确认。执行 man tcp，会发现只有 TCP 套接字专门设置了 TCP_QUICK 才会开启快速确认模式：TCP_QUICKACK (since Linux 2.4.4) Enable quickack mode if set or disable quickack mode if cleared. In quickack mode, acks are sent immediately, rather than delayed if needed in accordance to normal TCP operation. This flag is not permanent, it only enables a switch to or from quickack mode. Subsequent operation of the TCP protocol will once again enter/leave quickack mode depending on internal protocol processing and factors such as delayed ack timeouts occurring and data transfer. This option should not be used in code intended to be portable.利用 strace 查看 wrk 为套接字设置了哪些选项：$ strace -f wrk --latency -c 100 -t 2 --timeout 2 http://192.168.0.30:8080/ ... setsockopt(52, SOL_TCP, TCP_NODELAY, [1], 4) = 0 ...可以看到并没有设置 TCP_QUICKACK 选项。但是服务端不应该受客户端的影响，回到 Wireshark 重新观察：1173 是之前看到的 ACK 延迟包，697 包和 1175 包共同组成一个 HTTP 响应（ACK 都是 85）。第二个分组 1175 没有和前一个分组 697 一起发送，而是等 697 的 ACK 后才发送了 1175，这跟延迟确认比较像，只是不是 ACK，而是数据包。这里可以想到 Nagle 算法，通过合并小包来提高带宽利用率。Nagle 算法规定，一个 TCP 连接上最多只有一个未被确认的未完成分组；在收到这个分组的 ACK 前不发送别的分组。这些小分组会被组合起来，在收到 ACK 后一起发送出去。Nagle 算法跟客户端的默认延迟确认机制结合起来，就会导致延迟…

案例篇：如何优化 NAT 性能？
type: item
date: 2021-11-13T11:38:00+00:00
description: Content: # 1 NAT 原理NAT 可以重新 IP 数据包的源 IP 或目的 IP，用来解决公网中 IP 地址短缺问题。原理是内网中的多个机器共用一个公网 IP 来访问外网。SNAT 和 DNAT 示例：2 iptables 与 NATLinux 支持 4 种表，包括 filter（用于过滤）、nat（用于 NAT）、mangle（用于修改分组数据） 和 raw（用于原始数据包）等。nat 表中内置了三个链：PREROUTING，用于路由判断前所执行的规则，比如，对接收到的包进行 DNAT。POSTROUTIG，用于路由判断后执行的规则，比如，对发送或转发的数据包进行 SNAT 或 MASQUERADE。OUTPUT，类似 PREROUTING，但只处理从本机发送出去的包。3 管理 NAT 规则在使用 iptables 配置 NAT 规则时，Linux 需要转发来自其它 IP 的网络包，所以需要开启 Linux 的 IP 转发功能，查看是否开启：$ sysctl net.ipv4.ip_forward net.ipv4.ip_forward = 1输出结果是 1，表示已经开启。如果没有开启，可以手动开启：$ sysctl -w net.ipv4.ip_forward=1 net.ipv4.ip_forward = 1写到 /etc/sysctl.conf 文件中后，重启仍然会生效。3.1 SNAT为一个子网统一配置 SNAT，并由 Linux 选择默认的出口 IP：$ iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -j MASQUERADE为具体的 IP 地址配置 SNAT，并指定转换后的源地址：$ iptables -t nat -A POSTROUTING -s 192.168.0.2 -j SNAT --to-source 100.100.100.1003.2 DNAT$ iptables -t nat -A PREROUTING -d 100.100.100.100 -j DNAT --to-destination 192.168.0.24 现象压测场景下，并发请求数大大降低：# -c 表示并发请求数为5000，-n 表示总的请求数为 10 万 # -r 表示套接字接收错误时仍然继续执行，-s 表示设置每个请求的超时时间为 30s $ ab -c 5000 -n 10000 -r -s 30 http://192.168.0.30:8080/ ... Requests per second: 76.47 [#/sec] (mean) Time per request: 65380.868 [ms] (mean) Time per request: 13.076 [ms] (mean, across all concurrent requests) Transfer rate: 44.79 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1300 5578.0 1 65184 Processing: 0 37916 59283.2 1 130682 Waiting: 0 2 8.7 1 414 Total: 1 39216 58711.6 1021 130682 ...每秒 74 个请求，每个请求延迟为 65s5 排查本文从 NAT 的角度排查。NAT 正常工作至少需要两个步骤：利用 Netfilter 中的钩子函数（hook）修改源地址后目标地址。利用连接跟踪模块 conntrack 关联同一个连接的请求和响应。SystemTap 是 Linux 的一种动态追踪框架，它把用户提供的脚本转换为内核模块来执行，用来检测和跟踪内核的行为。创建一个 dropwatch.stp 脚本：#! /usr/bin/env stap ############################################################ # Dropwatch.stp # Author: Neil Horman <nhorman@redhat.com> # An example script to mimic the behavior of the dropwatch utility # http://fedorahosted.org/dropwatch ############################################################ # Array to hold the list of drop poi…

案例篇：怎么缓解 DDoS 攻击带来的性能下降问题？
type: item
date: 2021-11-06T11:40:00+00:00
description: 1 现象# -w 表示只输出 HTTP 状态码及总时间， # -o表 示将响应重定向到 /dev/null # --connect-timeout 表示连接超时时间 $ curl -w 'Http code: %{http_code}\nTotal time:%{time_total}s\n' -o /dev/null --connect-timeout 10 http://192.168.0.30 ... Http code: 000Total time:10.001scurl: (28) Connection timed out after 10000 milliseconds客户端连接超时，且没有收到服务端的响应。2 排查查看网络：$ sar -n DEV 1 08:55:49 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil 08:55:50 docker0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 08:55:50 eth0 22274.00 629.00 1174.64 37.78 0.00 0.00 0.00 0.02 08:55:50 lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00网络接收的 PPS 已经达到了 20000 多，但是 BPS 却只有 1174 kB，这样每个包的大小就只有 54（1174*1024/22274=54）。用 tcpdump 查看小包是什么：# -i eth0 只抓取eth0网卡， # -n 不解析协议名和主机名 # tcp port 80 表示只抓取 tcp 协议并且端口号为 80 的网络帧 $ tcpdump -i eth0 -n tcp port 80 09:15:48.287047 IP 192.168.0.2.27095 > 192.168.0.30: Flags [S], seq 1288268370, win 512, length 009:15:48.287050 IP 192.168.0.2.27131 > 192.168.0.30: Flags [S], seq 2084255254, win 512, length 009:15:48.287052 IP 192.168.0.2.27116 > 192.168.0.30: Flags [S], seq 677393791, win 512, length 009:15:48.287055 IP 192.168.0.2.27141 > 192.168.0.30: Flags [S], seq 1276451587, win 512, length 009:15:48.287068 IP 192.168.0.2.27154 > 192.168.0.30: Flags [S], seq 1851495339, win 512, length 0 ...都是 SYN 包，可见是受到了 SYN Flood 攻击。查看 SYN 包的数量：$ netstat -n -p | grep SYN_REC | wc -l 1933 缓解 DDos 攻击限制单个攻击源：# 限制syn并发数为每秒1次 $ iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT # 限制单个IP在60秒新建立的连接数为10 $ iptables -I INPUT -p tcp --dport 80 --syn -m recent --name SYN_FLOOD --update --seconds 60 --hitcount 10 -j REJECT对 TCP 优化：$ cat /etc/sysctl.conf # 降低连接每个 SYN_RECV 失败时的默认的重试次数 sysctl -w net.ipv4.tcp_synack_retries=1 # 开启 SYN Cookies，不需要维护半开连接状态 sysctl -w net.ipv4.tcp_syncookies=1要使 /etc/sysctl.conf 生效，需执行 sysctl -p 命令。写到 /etc/sysctl.conf 中后，重启仍然会生效。参考倪朋飞. Linux 性能优化实战.