liuhao163.github.io

杂七杂八


  • Home

  • Categories

  • Tags

  • Archives

  • Sitemap

系统优化-网络-阶段性总结

Posted on 2020-06-01 | Edited on 2022-09-21 | In 性能优化 , 网络

确认优化的目标

根据服务器应用的场景来确定我们优化的目标

防火墙:类似线性转发关注PPS
数据库:关注响应延迟和BPS
web应用:关注响应延迟和BPS以及每秒的请求数

针对应用的场景制定测试的基准

根据我们的网络协议栈,分别对每一层进行基准测试

avator

  1. 数据接口层 and IP层:关注pps
  2. 传输层:关注包的BPS,网络延迟,连接数
  3. 应用层:关注包的BPS,网络延迟,每秒请求数

相关工具表

avator

avator

制定优化策略

应用程序

  • 在IO模型上采用epool,取代pool或者select;或者AIO
  • 在进程工作模型上:采用master+worker机制

套接字

套接字可以屏蔽掉Linux内核中不同协议的差异,为应用程序提供统一的访问接口。每个套接字,都有一个读写缓冲区。

  • 读缓冲区:缓存了远端发过来的数据。如果读缓冲区已满,就不能再接收新的数据。
  • 写缓冲区:缓存了要发出去的数据。如果写缓冲区已满,应用程序的写操作就会被阻塞

    为了提高吞吐我们可以调整缓冲区大小:

  • 增大每个套接字的缓冲区大小 net.core.optmem_max;

  • 增大套接字接收缓冲区大小 net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max;
  • 增大 TCP 接收缓冲区大小 net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem

    具体参数如图:
    avator

    除此之外,套接字接口还提供了一些配置选项,用来修改网络连接的行为:

  • 为 TCP 连接设置 TCP_NODELAY 后,就可以禁用 Nagle 算法;

  • 为 TCP 连接开启 TCP_CORK 后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送);
  • 使用 SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小。

传输层

我第一类,在请求数比较大的场景下,你可能会看到大量处于 TIME_WAIT 状态的连接,它们会占用大量内存和端口资源。这时,我们可以优化与 TIME_WAIT 状态相关的内核选项,比如采取下面几种措施。

  1. 增大处于 TIME_WAIT 状态的连接数量 net.ipv4.tcp_max_tw_buckets ,并增大连接跟踪表的大小 net.netfilter.nf_conntrack_max。
  2. 减小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait ,让系统尽快释放它们所占用的资源。
  3. 开启端口复用 net.ipv4.tcp_tw_reuse。这样,被 TIME_WAIT 状态占用的端口,还能用到新建的连接中。
  4. 增大本地端口的范围 net.ipv4.ip_local_port_range 。这样就可以支持更多连接,提高整体的并发能力。
  5. 增加最大文件描述符的数量。你可以使用 fs.nr_open 和 fs.file-max ,分别增大进程和系统的最大文件描述符数;或在应用程序的 systemd 配置文件中,配置 LimitNOFILE ,设置应用程序的最大文件描述符数。

    第二类,为了缓解 SYN FLOOD 等,利用 TCP 协议特点进行攻击而引发的性能问题,你可以考虑优化与 SYN 状态相关的内核选项,比如采取下面几种措施。

  6. 增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog

  7. 开启 TCP SYN Cookies net.ipv4.tcp_syncookies ,来绕开半连接数量限制的问题(注意,这两个选项不可同时使用)。
  8. 减少 SYN_RECV 状态的连接重传 SYN+ACK 包的次数 net.ipv4.tcp_synack_retries。

    第三类,在长连接的场景中,通常使用 Keepalive 来检测 TCP 连接的状态,以便对端连接断开后,可以自动回收。但是,系统默认的 Keepalive 探测间隔和重试次数,一般都无法满足应用程序的性能要求。所以,这时候你需要优化与 Keepalive 相关的内核选项,比如:

  9. 缩短最后一次数据包到 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_time;

  10. 缩短发送 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_intvl;
  11. 减少 Keepalive 探测失败后,一直到通知应用程序前的重试次数 net.ipv4.tcp_keepalive_probes。

    具体的参数如下:
    avator

网络层

网络层,负责网络包的封装、寻址和路由,包括 IP、ICMP 等常见协议。在网络层,最主要的优化,其实就是对路由、 IP 分片以及 ICMP 等进行调优。

第一种,从路由和转发的角度出发,你可以调整下面的内核选项。在需要转发的服务器中,

  1. 比如用作 NAT 网关的服务器或者使用 Docker 容器时,开启 IP 转发,即设置 net.ipv4.ip_forward = 1。
  2. 调整数据包的生存周期 TTL,比如设置 net.ipv4.ip_default_ttl = 64。注意,增大该值会降低系统性能。
  3. 开启数据包的反向地址校验,比如设置 net.ipv4.conf.eth0.rp_filter = 1。这样可以防止 IP 欺骗,并减少伪造 IP 带来的 DDoS 问题。

    第二种,从分片的角度出发,最主要的是调整 MTU(Maximum Transmission Unit)的大小。通常,MTU 的大小应该根据以太网的标准来设置。

    以太网标准规定,一个网络帧最大为 1518B,那么去掉以太网头部的 18B 后,剩余的 1500 就是以太网 MTU 的大小。

    在使用 VXLAN、GRE 等叠加网络技术时,要注意,网络叠加会使原来的网络包变大,导致 MTU 也需要调整。比如,就以 VXLAN 为例,它在原来报文的基础上,增加了 14B 的以太网头部、 8B 的 VXLAN 头部、8B 的 UDP 头部以及 20B 的 IP 头部。换句话说,每个包比原来增大了 50B。所以,我们就需要把交换机、路由器等的 MTU,增大到 1550, 或者把 VXLAN 封包前(比如虚拟化环境中的虚拟网卡)的 MTU 减小为 1450。

    另外,现在很多网络设备都支持巨帧,如果是这种环境,你还可以把 MTU 调大为 9000,以提高网络吞吐量。

    第三种,从 ICMP 的角度出发,为了避免 ICMP 主机探测、ICMP Flood 等各种网络问题,你可以通过内核选项,来限制 ICMP 的行为。比如,

  4. 你可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1。这样,外部主机就无法通过 ICMP 来探测主机。

  5. 你还可以禁止广播 ICMP,即设置 net.ipv4.icmp_echo_ignore_broadcasts = 1。

数据链路层

网络层的下面是链路层,所以最后,我们再来看链路层的优化方法。链路层负责网络包在物理网络中的传输,比如 MAC 寻址、错误侦测以及通过网卡传输网络帧等。自然,链路层的优化,也是围绕这些基本功能进行的。接下来,我们从不同的几个方面分别来看。

  1. 由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的 CPU。所以,将这些中断处理程序调度到不同的 CPU 上执行,就可以显著提高网络吞吐量。这通常可以采用下面两种方法。
  2. 可以为网卡硬中断配置 CPU 亲和性(smp_affinity),或者开启 irqbalance 服务。再如,你可以开启 RPS(Receive Packet Steering)和 RFS(Receive Flow Steering),将应用程序和软中断的处理,调度到相同 CPU 上,这样就可以增加 CPU 缓存命中率,减少网络延迟。

    另外,现在的网卡都有很丰富的功能,原来在内核中通过软件处理的功能,可以卸载到网卡中,通过硬件来执行。

  • TSO(TCP Segmentation Offload)和 UFO(UDP Fragmentation Offload):在 TCP/UDP 协议中直接发送大包;而 TCP 包的分段(按照 MSS 分段)和 UDP 的分片(按照 MTU 分片)功能,由网卡来完成 。
  • GSO(Generic Segmentation Offload):在网卡不支持 TSO/UFO 时,将 TCP/UDP 包的分段,延迟到进入网卡前再执行。这样,不仅可以减少 CPU 的消耗,还可以在发生丢包时只重传分段后的包。
  • LRO(Large Receive Offload):在接收 TCP 分段包时,由网卡将其组装合并后,再交给上层网络处理。不过要注意,在需要 IP 转发的情况下,不能开启 LRO,因为如果多个包的头部信息不一致,LRO 合并会导致网络包的校验错误。
  • GRO(Generic Receive Offload):GRO 修复了 LRO 的缺陷,并且更为通用,同时支持 TCP 和 UDP。
  • RSS(Receive Side Scaling):也称为多队列接收,它基于硬件的多个接收队列,来分配网络接收进程,这样可以让多个 CPU 来处理接收到的网络包。
  • VXLAN 卸载:也就是让网卡来完成 VXLAN 的组包功能。

DKDP and XDP

  1. 使用 DPDK 技术,跳过内核协议栈,直接由用户态进程用轮询的方式,来处理网络请求。同时,再结合大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率。
  2. 使用内核自带的 XDP 技术,在网络包进入内核协议栈前,就对其进行处理,这样也可以实现很好的性能。

系统优化-网络-NAT相关的知识以及优化

Posted on 2020-05-30 | Edited on 2022-09-21 | In 性能优化 , 网络

NAT概念

NAT技术可以重写IP数据包的源IP或者目的IP,被普遍地用来解决公网IP地址短缺的问题。

原理:网络中的多台主机,通过共享同一个公网IP地址,来访问外网资源。同时,由于 NAT 屏蔽了内网网络,自然也就为局域网中的机器提供了安全隔离。你既可以在支持网络地址转换的路由器(称为 NAT网关)中配置NAT,也可以在Linux服务器中配置 NAT。如果采用第二种方式,Linux服务器实际上充当的是“软”路由器的角色。

根据实现方式的不同,NAT 可以分为三类:

  • 静态NAT,即内网IP与公网IP是一对一的永久映射关系;
  • 动态NAT,即内网IP从公网IP池中,动态选择一个进行映射;网络地址端口转换
  • NAPT(Network Address and Port Translation),即把内网IP映射到公网IP的不同端口上,让多个内网IP可以共享同一个公网IP地址。

    我们基本上都会采用NAPT。

    根据转换方式不同,我们又可以把NAPT分为三类。

  • SNAT,即目的地址不变,只替换源IP或源端口。SNAT主要用于,多个内网IP共享同一个公网IP ,来访问外网资源的场景。

  • DNAT,即源IP保持不变,只替换目的IP或者目的端口。DNAT 主要通过公网IP的不同端口号,来访问内网的多种服务,同时会隐藏后端服务器的真实IP地址。
  • 双向地址转换,即同时使用SNAT和DNAT。当接收到网络包时,执行DNAT,把目的IP转换为内网IP;而在发送网络包时,执行SNAT,把源IP替换为外部 IP。

    这3种方式如图
    avator

iptables和NAT

我们可以通过设置iptables来做NAT的映射,其中:

POSTROUTING:在经过路由之后,用来设置SNAT
PREROUTING:在经过路由之前,用来设置DNAT

1
2
3
4
# POSTROUTING 经过路由 设置-s realServer内网IP  -j SNAT  --to-source  外网IP
iptables -t nat -A POSTROUTING -s 192.168.0.2 -j SNAT --to-source 100.100.100.100
# PREROUTING 未进过路由 设置-d 外网IP -j DNAT --to-destination realServer内网IP
iptables -t nat -A PREROUTING -d 100.100.100.100 -j DNAT --to-destination 192.168.0.2

NAT影响性能的原因

测试步骤

  1. 在服务器上运行nginx-docker,使用HOST网络,不使用NAT,用ab测试观察每秒请求书,请求平均延迟,建立连接访问延迟
  2. 在服务器上运行nginx-docker,使用NAT,用ab测试观察每秒请求书,请求平均延迟,建立连接访问延迟。
1
2
docker run --name nginx-hostnet --privileged --network=host -itd feisky/nginx:80
docker run --name nginx --privileged -p 8080:8080 -itd feisky/nginx:nat

NAT的工作原理:网络包的流向以及NAT的原理,你会发现,要保证NAT正常工作,就至少需要两个步骤:

  1. 利用 Netfilter 中的钩子函数(Hook),修改源地址或者目的地址。
  2. 利用连接跟踪模块 conntrack ,关联同一个连接的请求和响应。

通过SystemTap来追踪内核

我们编写如下脚本,之后执行 stap –all-modules dropwatch.stp 将脚本编译进内核状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#! /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 points we find
global locations

# Note when we turn the monitor on and off
probe begin { printf("Monitoring for dropped packets\n") }
probe end { printf("Stopping dropped packet monitor\n") }

# increment a drop counter for every location we drop at
probe kernel.trace("kfree_skb") { locations[$location] <<< 1 }

# Every 5 seconds report our drop locations
probe timer.sec(5)
{
printf("\n")
foreach (l in locations-) {
printf("%d packets dropped at %s\n",
@count(locations[l]), symname(l))
}
delete locations
}

再进行ab压测发现丢包都发生在nf_hook_slow

整个请求的原理如下,我们知道了原理,可以有针对的进行性优化,调整内核参数。

  1. 接收网络包时,在连接跟踪表中查找连接,并为新的连接分配跟踪对象(Bucket)。
  2. 在 Linux 网桥中转发包。这是因为案例 Nginx 是一个 Docker 容器,而容器的网络通过网桥来实现;
  3. 接收网络包时,执行 DNAT,即把 8080 端口收到的包转发给容器。
1
2
3
4
5
6
7
8
9

$ sysctl -a | grep conntrack
net.netfilter.nf_conntrack_count = 180 ## 表示当前连接跟踪数;
net.netfilter.nf_conntrack_max = 1000 ## 表示最大连接跟踪数;可以改大为nf_conntrack_buckets俩倍
net.netfilter.nf_conntrack_buckets = 65536 ## 表示连接跟踪表的大小。
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
...

附录

conntrack工具查看链接表内容

1
2
3
4
# -L表示列表,-o表示以扩展格式显示
$ conntrack -L -o extended | head
ipv4 2 tcp 6 7 TIME_WAIT src=192.168.0.2 dst=192.168.0.96 sport=51744 dport=8080 src=172.17.0.2 dst=192.168.0.2 sport=8080 dport=51744 [ASSURED] mark=0 use=1
ipv4 2 tcp 6 6 TIME_WAIT src=192.168.0.2 dst=192.168.0.96 sport=51524 dport=8080 src=172.17.0.2 dst=192.168.0.2 sport=8080 dport=51524 [ASSURED] mark=0 use=1

dmsg查看系统错误日志

系统优化-网络-延迟ACK和Nagle算法对网络延迟的影响

Posted on 2020-05-30 | Edited on 2022-09-21 | In 性能优化 , 网络

Nagle算法

开启方式,设置标记为: TCP_NODELAY

说明:我们在开启这个选项时候就启用了Nagle算法,Negle算法的初衷本质是想充分利用带宽资源,将小包汇总到一起发送出去,它规定了网络中只能存在一个为ack的包。

延迟ACK

开启方式,默认为开启,关闭设置 TCP_QUICKACK

说明:为了减少网络交互客户端不用每个包都Ack。会等一段时间(40ms)之后一起发送出去

问题

当延迟ACK遇上TCP_NODELAY,就会有可能出现明显的网络延迟。如下图:

avator

系统优化-网络-如何环境DDoS攻击

Posted on 2020-05-29 | Edited on 2022-09-21 | In 性能优化 , 网络

DDoS 简介

DDoS 的前身是 DoS(Denail of Service),即拒绝服务攻击,指利用大量的合理请求,来占用过多的目标资源,从而使目标服务无法响应正常请求。

DDoS(Distributed Denial of Service) 则是在 DoS 的基础上,采用了分布式架构,利用多台主机同时攻击目标主机。这样,即使目标服务部署了网络防御设备,面对大量网络请求时,还是无力应对。

DDos攻击以及缓解方法

常见的有半连接攻击,攻击者发送大量的SYNC包给服务器,大量的链接处于SYN_REV状态,很快链接表被占满,比如用这个命令

1
2
3
4
-S 发sync包
-i 间隔
-p 目标端口
hping3 -S -p 80 -i u10 192.168.1.1

现象:端口响应变慢,甚至中断所有操作都慢。

排查思路:
用sar -n DEV 1看到如下结果,rxpack很大,但是rxkB很小,是典型的小包问题。

1
2
3
07:11:19 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
07:11:20 PM eth0 37279.00 18651.00 1966.75 1057.48 0.00 0.00 0.00
07:11:20 PM lo 0.00 0.00 0.00 0.00 0.00 0.00 0.00

如何排查,我们可以用netstat看统计有多少SYN_RECV的链接发现大量的SYN_RECV,大概有190+,因为默认的链接表只有256个,所以我们的正常请求会变慢。

1
netstat -np|grep SYN_RECV|wc -l

解决思路

优化系统参数

1
2
3
4
$ cat /etc/sysctl.conf
net.ipv4.tcp_syncookies = 1 #通过cookie,来取代半连接
net.ipv4.tcp_synack_retries = 1 #重试次数,可通过减少次数
net.ipv4.tcp_max_syn_backlog = 1024 #半链接容量

优化系统:通过XDP、DKDP来缓解

借用外部工具:通过WAF、CDN等来缓解

彻底解决:通过专业的流量清洗工具

附录

avator

系统优化-网络-常用工具

Posted on 2020-05-26 | Edited on 2022-09-21 | In 计算机基础 , 网络

DNS相关

DNS:即域名系统,是互联网中最基础的一项服务,主要提供域名和 IP 地址之间映射关系的查询服务。

我们常见的DNS记录有以下几种:

  • A:用来把域名转换成 IP 地址;
  • CNAME:用来创建别名
  • NS:则表示该域名对应的域名服务器地址。

    DNS的域名查找从跟(.)开始进行递归查找,我们可以通过nslookup后者dig这俩个工具查看DNS解析的全过程

1
2
3
4
5
6
7
8
9
$ nslookup time.geekbang.org
# 域名服务器及端口信息
Server: 114.114.114.114
Address: 114.114.114.114#53

# 非权威查询结果
Non-authoritative answer:
Name: time.geekbang.org
Address: 39.106.233.17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# +trace表示开启跟踪查询
# +nodnssec表示禁止DNS安全扩展
$ dig +trace +nodnssec time.geekbang.org

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> +trace +nodnssec time.geekbang.org
;; global options: +cmd
. 322086 IN NS m.root-servers.net.
. 322086 IN NS a.root-servers.net.
. 322086 IN NS i.root-servers.net.
. 322086 IN NS d.root-servers.net.
. 322086 IN NS g.root-servers.net.
. 322086 IN NS l.root-servers.net.
. 322086 IN NS c.root-servers.net.
. 322086 IN NS b.root-servers.net.
. 322086 IN NS h.root-servers.net.
. 322086 IN NS e.root-servers.net.
. 322086 IN NS k.root-servers.net.
. 322086 IN NS j.root-servers.net.
. 322086 IN NS f.root-servers.net.
;; Received 239 bytes from 114.114.114.114#53(114.114.114.114) in 1340 ms

org. 172800 IN NS a0.org.afilias-nst.info.
org. 172800 IN NS a2.org.afilias-nst.info.
org. 172800 IN NS b0.org.afilias-nst.org.
org. 172800 IN NS b2.org.afilias-nst.org.
org. 172800 IN NS c0.org.afilias-nst.info.
org. 172800 IN NS d0.org.afilias-nst.org.
;; Received 448 bytes from 198.97.190.53#53(h.root-servers.net) in 708 ms

geekbang.org. 86400 IN NS dns9.hichina.com.
geekbang.org. 86400 IN NS dns10.hichina.com.
;; Received 96 bytes from 199.19.54.1#53(b0.org.afilias-nst.org) in 1833 ms

time.geekbang.org. 600 IN A 39.106.233.176
;; Received 62 bytes from 140.205.41.16#53(dns10.hichina.com) in 4 ms

设置DNS的方法

1
2
$ cat /etc/resolv.conf
nameserver 114.114.114.114

如果DNS的解析不稳定我们可以开启dnsmasq dns缓存来加速

1
2
/etc/init.d/dnsmasq start
* Starting DNS forwarder and DHCP server dnsmasq [ OK ]

抓包工具

常用的抓包工具有tcpdump和wireshark,其中tcpdump用于服务器抓包,wireshark由于有图形界面适合我们对tcpdump导出的文件在本地分析

tcpdump的常用参数

avator

tcpdump的常用表达式

avator

tcpdump的输出格式

1
时间戳 协议 源地址.源端口 > 目的地址.目的端口 网络包详细信息

wireshark

avator

你可以看到

  • IP 层(Internet Protocol)的源地址和目的地址
  • 传输层的 UDP 协议(Uder Datagram Protocol)
  • 应用层的 DNS 协议(Domain Name System)的概要信息。

继续点击每层左边的箭头,就可以看到该层协议头的所有信息。比如点击 DNS 后,就可以看到 Transaction ID、Flags、Queries 等 DNS 协议各个字段的数值以及含义。当然,Wireshark 的功能远不止如此。接下来我再带你一起,看一个 HTTP 的例子,并理解 TCP 三次握手和四次挥手的工作原理。

系统优化-网络-C10K和C1000K回顾-系统调优方案

Posted on 2020-05-23 | Edited on 2022-09-21 | In 性能优化 , 网络

C10K的C只的是client客户端的意思,意思是支持1W的客户端连接。C1000K则是指100W的客户端连接。为了达到C10K,我们就要对系统进行优化,一般会从俩方面考虑。

IO方面的优化

因为传统的BIO一个线程只能同时处理一个IO请求效率比较低。为了增大并发处理的能力,我们往往会采用IO多路复用的思路去解决问题。I/O多路复用是什么意思呢?

我们先来普及俩个概念

  • 水平触发:只要文件描述符可以非阻塞地执行 I/O ,就会触发通知。也就是说,应用程序可以随时检查文件描述符的状态,然后再根据状态,进行 I/O 操作。
  • 边缘触发:只有在文件描述符的状态发生改变(也就是 I/O 请求达到)时,才发送一次通知。这时候,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止。如果 I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了。

select或者pool的水平触发

select 和 poll 需要从文件描述符列表中,找出哪些可以执行I/O ,然后进行真正的网络I/O读写。由于I/O是非阻塞的,一个线程中就可以同时监控一批套接字的文件描述符,这样就达到了单线程处理多请求的目的。【多路复用指的就是粗体部分】

缺点:

  • select采用标量位,32位系统默认只能支持1024个描述符,且select的fd的状态位是也是用标量位来表示的,获取每个fd的状态需要轮询这个标量位。时间复杂度是o(n^2)
  • pool采用了个无界数组,没有了1024的限制,时间复杂度是o(n),但是他俩都需要将能读写的fd,从用户态转移到内核态修改状态每次都要进行切换会造成损耗

epool的边缘触发

epool采用红黑树管理fd,时间复杂度o(log2n),epool在内核态管理fd没有切换的损耗。

系统并发度方面的优化

master+worker的方案,nginx或者我们的netty架构几乎都是这种方案master和worker可以是进程也可以是线程。这种情况要考虑避免惊群效应,即多个进程被同时唤醒,但实际上只有一个进程来响应这个事件,其他被唤醒的进程都会重新休眠、

  • 其中,accept() 的惊群问题,已经在 Linux 2.6 中解决了;
  • 而epoll的问题,到了 Linux 4.5 ,才通过EPOLLEXCLUSIVE解决。

    Nginx是如何避免惊群问题, Nginx在每个worker进程中,都增加一个了全局锁(accept_mutex)。这些worker进程需要首先竞争到锁,只有竞争到锁的进程,才会加入到epoll中,这样就确保只有一个worker子进程被唤醒。

    监听到相同端口的多进程模型。在这种方式下,所有的进程都监听相同的接口,并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去。这一过程如下图所示

    avator

    这由于内核确保了只有一个进程被唤醒,就不会出现惊群问题了。比如,Nginx在1.9.1中就已经支持了这种模式。

    avator

    我们从C10K-C100K都可以用这种方案+不断升级我们的硬件来解决。但是到了更高C1000K因为中断、网络包链路过长等等原因采取一些新的手段比如

DKDP

允许我们在用户态直接处理请求包并且调用网卡发送出去,需要支持DKDP网卡

avator

XDP

允许我们在进入网络协议栈之前先处理网络包

avator

但是:其实我们C10K就几乎够用了因为我们还要考虑业务等因素,我们还是推荐拆分到不同的服务器中。

系统优化-网络-基础概念以及指标

Posted on 2020-05-19 | Edited on 2022-09-21 | In 性能优化 , 网络

前面我们讲了CPU、内存、磁盘IO,下面我们来看网络这部分,网络处理的流程最复杂,跟我们前面讲到的进程调度、中断处理、内存管理以及 I/O 等都密不可分。

OSI的网络七层模型,以及TCP/IP的四层or五层模型,如下:

avator

TCP/IP网络栈

有了linux根据tcp/ip模型,我们的网络栈的收发数据实际上就是按照这4层模型对数据层层进行处理,对上层发送发送来的数据进行分析,然后将本层的数据进行封装发给下层

  • 应用层–>数据
  • 传输层–>TCP头+数据
  • 网络层–>IP头+TCP头+数据
  • 网络接口层–>根据MTU切分数据包 and 帧头+IP头+TCP头+数据+栈帧

    下图是整个网络栈
    avator

TCP/IP的手法

接收网络包

  1. 网卡通过DMA获取网络数据,放到待接收队列
  2. 系统的硬中断程序通知系统接收网络数据,并且申请sk_buff,并且将数据放入到sk_buff中
  3. 通过系统的软中断开始处理网络数据
  4. 网络接口层校验包完整性,去掉帧头和帧尾,数据传递给网络层
  5. 网络层根据IP信息确定吓一跳是本机还是转发,如果是本机,数据传递给传输层
  6. 传输层解析TCP头,找到4元祖通过套接字发送给程序

发送网络包

和接收相反

  1. 程序通过系统内核调用套接字的sendMsg发送网络包
  2. 传输层封装TCP头信息,发送给IP层
  3. IP层根据IP找到吓一跳的地址封装IP头,传递给网络接口层
  4. 网络接口层根据MTU对包进行切分,添加帧头、帧尾等信息
  5. 拆分好的包进行物理寻址找到对应的MAC地址,之后将自己添加到待发送队列
  6. 系统的软中断通知系统有网络包待发送
  7. 之后网卡驱动会通过DMA,将待发送队列的数据通过网卡发送出去

如图:
avator

查看网络情况的基础指标和工具

查看网卡信息

ifconfig or ip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.240.0.30 netmask 255.240.0.0 broadcast 10.255.255.255
inet6 fe80::20d:3aff:fe07:cf2a prefixlen 64 scopeid 0x20<link>
ether 78:0d:3a:07:cf:3a txqueuelen 1000 (Ethernet)
RX packets 40809142 bytes 9542369803 (9.5 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 32637401 bytes 4815573306 (4.8 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
​
$ ip -s addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 78:0d:3a:07:cf:3a brd ff:ff:ff:ff:ff:ff
inet 10.240.0.30/12 brd 10.255.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::20d:3aff:fe07:cf2a/64 scope link
valid_lft forever preferred_lft forever
RX: bytes packets errors dropped overrun mcast
9542432350 40809397 0 0 0 193
TX: bytes packets errors dropped carrier collsns
4815625265 32637658 0 0 0 0
  1. 网络接口的状态标志。ifconfig 输出中的 RUNNING ,或 ip 输出中的 LOWER_UP ,都表示物理网络是连通的,即网卡已经连接到了交换机或者路由器中。如果你看不到它们,通常表示网线被拔掉了。
  2. MTU 的大小。MTU 默认大小是 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值。
  3. 网络接口的 IP 地址、子网以及 MAC 地址。这些都是保障网络功能正常工作所必需的,你需要确保配置正确。
  4. 网络收发的字节数、包数、错误数以及丢包情况,特别是 TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指标不为 0 时,通常表示出现了网络 I/O 问题。其中:
    • errors 表示发生错误的数据包数,比如校验错误、帧同步错误等;
    • dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包;
    • overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;
    • carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等;
    • collisions 表示碰撞数据包数。

查看套接字信息

netstat or ss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 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 表示全连接队列的最大长度。

统计协议栈信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

$ netstat -s
...
Tcp:
3244906 active connection openings
23143 passive connection openings
115732 failed connection attempts
2964 connection resets received
1 connections established
13025010 segments received
17606946 segments sent out
44438 segments retransmitted
42 bad segments received
5315 resets sent
InCsumErrors: 42
...

$ ss -s
Total: 186 (kernel 1446)
TCP: 4 (estab 1, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 0

Transport Total IP IPv6
* 1446 - -
RAW 2 1 1
UDP 2 2 0
TCP 4 3 1
...

查看网络的吞吐等指标

sar

1
2
3
4
5
6
7
8
# 数字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.00

连通性和RTT

ping

1
2
3
4
5
6
7
8
9
10
11

# -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/mdev = 244.023/244.070/244.105/0.034 ms

系统优化-IO-优化套路

Posted on 2020-05-17 | Edited on 2022-09-21 | In 性能优化 , I/O

基准测试

用fio+blktrace

fio用于基准测试,blktrace可以进行io的录制通过fio的回放功能得到系统真实场景的io情况

优化套路

代码优化

  • 追加写替代随机写
  • 采用有缓存的io
  • 采用标准库缓存或者外部缓存介绍io
  • 如果对大文件写考虑用mmap(zero copy)
  • 用fsync替代O_SYNC
  • 做好进程隔离防止某个进程占有所有的io资源,比如采用cgroup对进程的io做限制
  • 如果io的调度采用CFQ,考虑对ionice进行设置
    • ionice 支持三个优先级类:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 还分别支持 0-7 的级别,数值越小,则表示优先级别越高。

文件系统优化

  • 采用适合的文件系统
  • 对文件系统参数调优
  • 优化文件系统的缓存
    • 可以优化 pdflush 脏页的刷新频率(比如设置 dirty_expire_centisecs 和 dirty_writeback_centisecs)以及脏页的限额(比如调整 dirty_background_ratio 和 dirty_ratio 等)。

磁盘优化

  • 硬件优化用SDD取代HDD等
  • 采用RAID方案提高读写能力
  • 采用适合的线程调度算法
  • 如果磁盘读写负担较大,可以单独配置磁盘
  • 对顺序读取增加磁盘的预读能力
    • 调整内核选项 /sys/block/sdb/queue/read_ahead_kb,默认大小是 128 KB,单位为 KB。
    • 使用 blockdev 工具设置,比如 blockdev –setra 8192 /dev/sdb,注意这里的单位是 512B(0.5KB),所以它的数值总是 read_ahead_kb 的两倍。
  • 通过调整请求队列增加磁盘吞吐
    • 可以调整磁盘队列的长度 /sys/block/sdb/queue/nr_requests,适当增大队列长度,可以提升磁盘的吞吐量(当然也会导致 I/O 延迟增大)。

系统优化-IO-总结

Posted on 2020-05-17 | Edited on 2022-09-21 | In 性能优化 , I/O

IO的原理

磁盘

磁盘是块状设备,用来持久化数据,磁盘的性能明显劣于内存,系统用了缓存(cache)和缓冲(buffer)来弥补这部分的性能差异。

磁盘有许多类型有:IDE(hd),SATA(sd)还有网络存储等,序号一般从a-z。我们在这些设备上还可以进行分区,分区用0-9表示。

我们将磁盘分区挂载到文件系统的某个目录中使用

文件系统

linux通过文件系统操作磁盘中的数据。文件系统由目录项、索引块、超级块、逻辑块组成。

  • 逻辑块:数据的最小单位是扇出一般是512b,磁盘为了加速访问将8个相邻的扇区组成了一个逻辑块进行操作,一个逻辑块4kb。逻辑块用来存储文件数据
  • 索引块:存储索引节点(inode),索引节点存储了文件的原信息。比如大小、创建时间、权限、地址
  • 目录项:存储文件和目录项之间的关系。
  • 超级块:用来表示索引块、逻辑快使用情况。

    缓存:

    页缓存:加速磁盘IO访问
    索引节点缓存:加速索引节点的访问
    目录项缓存:加速目录项查找和访问

    vfs:隐藏各种磁盘的实现提供统一的接口
    通用区:在vfs下层隐藏多种磁盘系统的实现,对上提供统一接口;对下提供多种io请求合并优化的策略。

重点指标

如图

avator

工具

下面的表格是提供I/O的性能指标的工具:

avator

以指标的维度看:

avator

按照常用的排查思路我们看各工具:

avator

系统优化-IO-案例redis引起的io问题

Posted on 2020-05-16 | Edited on 2022-09-21 | In 性能优化 , CPU

测试方案

容器准备

1
2
3
4
5
6
7
docker run --name=redis -itd -p 10000:80 feisky/redis-server
docker run --name=app --network=container:redis -itd feisky/redis-app

docker ps #确定启动了俩个容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e8aa78bb2a42 feisky/redis-app "python /app.py" 15 minutes ago Up 15 minutes app
5567d2ba22c7 feisky/redis-server "docker-entrypoint..." 21 minutes ago Up 21 minutes 6379/tcp, 0.0.0.0:10000->80/tcp redis

数据准备

1
2
curl http://127.0.0.1:10000/init/5000
# 返回 {"elapsed_seconds":23.771671295166016,"keys_initialized":5000}

测试

1
while true; do curl http://192.168.0.10:10000/get_cache; done

现象

接口响应要7s,用top查看cpu0的us是11.1,wa是35.6,初步判断是io的问题,cpu占用较高的进程是Pyhton和redis-server

1
2
3
4
5
6
7
8
9
top - 12:37:23 up 4 days, 13:13,  2 users,  load average: 0.51, 0.25, 0.20
Tasks: 102 total, 2 running, 100 sleeping, 0 stopped, 0 zombie
%Cpu0 : 11.1 us, 8.8 sy, 0.0 ni, 40.6 id, 35.6 wa, 0.0 hi, 1.1 si, 2.7 st
KiB Mem : 1014884 total, 106288 free, 229516 used, 679080 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 524716 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2335 root 20 0 192992 23796 5148 S 17.6 2.3 0:06.97 python
394 100 20 0 28340 3020 1128 R 8.6 0.3 0:06.05 redis-server

我们用iostat -d -x进一步定位io的问题,发现很奇怪的发现一个查询接口居然有大量的write

1
2
3
4
5
6
7
8
9
10
11
iostat -d -x 1
Linux 3.10.0-957.27.2.el7.x86_64 (10-254-192-16) 05/16/2020 _x86_64_ (1 CPU)

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.26 0.53 5.36 105.01 107.24 72.00 0.13 35.83 57.35 33.69 0.75 0.44

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 1.08 504.30 8.60 1259.68 5.02 0.43 1.62 54.00 1.51 0.74 37.53

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
vda 0.00 0.00 0.00 576.14 0.00 1459.09 5.07 0.36 1.41 0.00 1.41 0.63 36.36

用pidstat -d 定位发现是redis-server在写,接着我们用strace定位,发现有大量的write和fdatasync,经过排查发现我们的redis-server用的是AOF策略是alwalys,每条指令都需要write和fsync,会产生大量的write。我们改变策略改为everysec,问题解决大部分,为什么是大部分,我们看到西面的命令存在大量的SADD。这些是没必要的,我们可以通过修改代码优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
strace -f -T -tt -p 394
[pid 394] 12:49:57.864317 fdatasync(7) = 0 <0.003447>
[pid 394] 12:49:57.867921 write(8, ":1\r\n", 4) = 4 <0.000285>
[pid 394] 12:49:57.868293 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 55, NULL, 8) = 1 <0.000078>
[pid 394] 12:49:57.868481 read(8, "*2\r\n$3\r\nGET\r\n$41\r\nuuid:510df338-"..., 16384) = 61 <0.000040>
[pid 394] 12:49:57.868609 read(3, 0x7fffaef0dce7, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000047>
[pid 394] 12:49:57.868754 write(8, "$3\r\nbad\r\n", 9) = 9 <0.000249>
[pid 394] 12:49:57.869108 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 54, NULL, 8) = 1 <0.000037>
[pid 394] 12:49:57.869226 read(8, "*2\r\n$3\r\nGET\r\n$41\r\nuuid:52aa4944-"..., 16384) = 61 <0.000062>
[pid 394] 12:49:57.869403 read(3, 0x7fffaef0dce7, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000036>
[pid 394] 12:49:57.869505 write(8, "$3\r\nbad\r\n", 9) = 9 <0.000205>
[pid 394] 12:49:57.869833 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 54, NULL, 8) = 1 <0.000038>
[pid 394] 12:49:57.869939 read(8, "*4\r\n$4\r\nSCAN\r\n$4\r\n1590\r\n$5\r\nMATC"..., 16384) = 47 <0.000038>
[pid 394] 12:49:57.870071 read(3, 0x7fffaef0dce7, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000036>
[pid 394] 12:49:57.870172 write(8, "*2\r\n$4\r\n5942\r\n*10\r\n$41\r\nuuid:4bd"..., 499) = 499 <0.000352>
[pid 394] 12:49:57.870600 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 53, NULL, 8) = 1 <0.000037>
[pid 394] 12:49:57.870753 read(8, "*2\r\n$3\r\nGET\r\n$41\r\nuuid:4bdfcf62-"..., 16384) = 61 <0.000057>
[pid 394] 12:49:57.870887 read(3, 0x7fffaef0dce7, 1) = -1 EAGAIN (Resource temporarily unavailable) <0.000036>
[pid 394] 12:49:57.870987 write(8, "$4\r\ngood\r\n", 10) = 10 <0.000196>
[pid 394] 12:49:57.871297 epoll_pwait(5, [{EPOLLIN, {u32=8, u64=8}}], 10128, 52, NULL, 8) = 1 <0.000105>

经过修改AOF的策略和优化掉SADD命令相关代码接口响应市场降为0.16034674644470215ms

1…567…23

Liu hao

励志当好厨子的程序员

229 posts
54 categories
81 tags
RSS
GitHub E-Mail
© 2018 – 2023 Liu hao
Powered by Hexo v3.9.0
|
Theme – NexT.Pisces v7.0.0