服务器性能优化之网络性能优化

发布日期:2022-08-06 19:25    点击次数:157

本文转载自微信群众号「极客重生」,作者极客重生。转载本文请联络极客重生群众号。

hi ,巨匠好,来日诰日分享一篇后援服务器性能优化之网络性能优化,停留巨匠对Linux网络有更深的理解。

曾几什么时光,通通都是那末俭朴。网卡很慢,只要一个行列。当数据包抵达时,网卡经由过程DMA复制数据包并发送中缀,Linux内核采集这些数据包并实现中缀处置惩罚。随着网卡越来越快,基于中缀的模型兴许会因大量传入数据包而导致 IRQ 风暴。这将斲丧大部份 CPU 功率并冻结体系。

为相识决这个成就,NAPI(中缀+轮询)被创议。当内核收到来自网卡的中缀时,它起头轮询动作举措并尽快采集行列中的数据包。NAPI 可以或许很好地与今朝罕见的 1 Gbps 网卡共同应用。然则,关于10Gbps、20Gbps以至40Gbps的网卡,NAPI兴许还不敷。假设我们仍然应用一个 CPU 和一个行列来领受数据包,这些卡将需要更快的 CPU。

幸运的是,今朝多核 CPU 很流行,那末为何不并行处置惩罚数据包呢?

RSS:领受端缩放

Receive Side Scaling(RSS)是所述机构具有多个RX / TX行列进程的数据包。当带有RSS 的网卡领受到数据包时,它会对数据包应用过滤器并将数据包散发到RX 行列。过滤器平日是一个哈希函数,可以或许经由过程“ethtool -X”举行设置。假设你想在前 3 个行列中均匀漫衍流量:

# ethtool -X eth0 equal 3 

或许,假设你缔造一个特殊有效的魔法哈希键:

 

# ethtool -X eth0 hkey <magic hash key> 

关于低耽误网络,除了过滤器之外,CPU 亲和性也很首要。最佳设置是分派一个 CPU 公用于一个行列。首先经由过程查抄/proc/interrupt找出IRQ号,尔后将CPU位掩码设置为/proc/irq//smp_affinity来分派公用CPU。为防止设置被笼盖,必须禁用守卫过程irqbalance。请留心,痛处内核文档,超线程对中缀处置惩罚没有任何益处,因而最佳将行列数与物理 CPU 内核数相成家。

RPS:领受数据包掌握

RSS供应硬件行列,一个称为软件行列机制Receive Packet Steering (RPS)在Linux内核实现。

当驱动顺序领受到数据包时,它会将数据包包装在套接字缓冲区 ( sk_buff ) 中,个中包孕数据包的u32哈希值。散列是所谓的第 4 层散列(l4 散列),它基于源 IP、源端口、目标 IP 和目标端口,由网卡或__skb_set_sw_hash() 计算。因为沟通 TCP/UDP 跟尾(流)的每个数据包同享沟通的哈希值,因而应用沟通的 CPU 处置惩罚它们是公正的。

RPS 的根抵思想是痛处每个行列的 rps_map 将同一流的数据包发送到特定的 CPU。这是 rps_map 的布局:晖映痛处 CPU 位掩码静态改观为/sys/class/net//queues/rx-/rps_cpus。比喻我们要让行列应用前3个CPU,在8个CPU的体系中,我们先布局位掩码,0 0 0 0 0 1 1 1,到0x7,尔后

#echo 7 > /sys/class/net /eth0/queues/rx-0/rps_cpus 

这将担保从 eth0 中行列 0 领受的数据包进入 CPU 1~3。驱动顺序在 sk_buff 中包装一个数据包后,它将抵达netif_rx_internal()或netif_receive_skb_internal(),尔后抵达 get_rps_cpu()

struct rps_map { unsigned int len; struct rcu_head rcu;     u16 cpus[0]; }; 

将被调用以将哈希晖映到 rps_map 中的条目,即 CPU id。失去CPU id后,enqueue_to_backlog()将sk_buff放到特定的CPU行列及第进步一步处置惩罚。每个 CPU 的行列在 per-cpu 变量softnet_data 等分派。

应用RPS的益处是可以或许在 CPU 之间分担数据包处置惩罚的负载。然则,假设RSS 可用,则兴许没有须要,因为网卡已经对每个行列/CPU 的数据包举行了排序。然则,假设行列中的CPU数更多,RPS 仍然可以或许发挥浸染。在这类环境下,每个行列可以或许与多个 CPU相联络纠葛并在它们之间散发数据包。

RFS: Receive Flow Steering

尽管 RPS 基于飘泊发数据包,但它没有推敲用户空间应用顺序。应用顺序兴许在 CPU A 上运行,而内核将数据包放入 CPU B 的行列中。因为 CPU A 只能应用自身的缓存,因而 CPU B 中缓存的数据包变得无用。Receive Flow Steering(RFS)进一步延伸为RPS的应用顺序。

接替每个行列的哈希至CPU地图,RFS回护全局flow-to-CPU的表,rps_sock_flow_table:该掩模用于将散列值晖映成所述表的索引。因为表大小将四舍五入到 2 的幂,因而掩码设置为table_size - 1。

struct rps_sock_flow_table {     u32 mask;     u32 ents[0]; }; 

并且很苟且找到索引:a sk_buff与hash & scok_table->mask。

该条目由 rps_cpu_mask分手为流 id 和 CPU id。低位用于CPU id,而高位用于流id。当应用顺序对套接字举行操作时(inet_recvmsg()、inet_sendmsg()、inet_sendpage()、tcp_splice_read()),将调用sock_rps_record_flow()来更新sock 流表。

当数据包到来时,将调用get_rps_cpu()来抉择应用哪一个 CPU 行列。上面是get_rps_cpu()怎么样抉择数据包的 CPU

ident = sock_flow_table->ents[hash & sock_flow_table->mask]; if ((ident ^ hash) & ~rps_cpu_mask)      goto try_rps; next_cpu = ident & rps_cpu_mask; 

应用流表掩码找到条目的索引,并查抄散列的高位是否与条目成家。假设是,它会从条目中检索 CPU id 并为数据包分派该 CPU。假设散列不成家任何条目,它会回退到应用 RPS 晖映。

可以或许经由过程rps_sock_flow_entries调整 sock 流表的大小。譬如,假设我们要将表大小设置为 32768:

#echo 32768 > /proc/sys/net/core/rps_sock_flow_entries 

sock流表诚然进步了应用的部份性,但也带来了一个成就。当调理器将应用顺序迁移到新 CPU 时,旧 CPU 行列中残剩的数据包变得未实现,应用顺序兴许会失去乱序的数据包。为相识决这个成就,RFS 应用每个行列的rps_dev_flow_table来跟踪未实现的数据包。

上面是该布局rps_dev_flow_table:到袜子流表中,近似的rps_dev_flow_table也应用table_size - 1作为掩模而表的大小也必须被向上舍入到2的幂当流量分组被入队,last_qtail被更新

struct rps_dev_flow {     u16 cpu;     u16 filter; /* For aRFS */ unsigned int last_qtail; }; struct rps_dev_flow_table { unsigned int mask; struct rcu_head rcu; struct rps_dev_flow flows[0]; }; 

到 CPU 行列的尾部。假设应用顺序迁移到新 CPU,则 sock 流表将回响反映改观,并且get_rps_cpu()将为流设置新 CPU。在设置新 CPU 从前,get_rps_cpu() 会查抄今后行列的头部是否已经经由过程 last_qtail。假设是这样,这意味着行列中没有更多未实现的数据包,并且可以或许安好地改观 CPU。否则,get_rps_cpu()仍将应用rps_dev_flow->cpu 中记载的旧 CPU 。

每个行列的流表(rps_dev_flow_table)的大小可以或许经由过程 sysfs 接口举行设置:

/sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt 

倡导将rps_flow_cnt设置为 ( rps_sock_flow_entries / N) 而 N 是 RX 行列的数量(假设流在行列中均匀漫衍)。

ARFS:加速领受流量转向

Accelerated Receive Flow Steering(aRFS)进一步延伸RFS为RX行列硬件过滤。要启用 aRFS,它需要具有可编程元组过滤器和驱动顺序支持的网卡。要启用ntuple 过滤器。

# ethtool -K eth0 ntuple on 

要使驱动顺序支持aRFS,它必须实现ndo_rx_flow_steer以协助set_rps_cpu()设置硬件过滤器。当get_rps_cpu()抉择为流分派一个新 CPU 时,它会调用set_rps_cpu()。set_rps_cpu()首先查抄网卡是否支持 ntuple 过滤器。假设是,它将查询rx_cpu_rmap为流找到相宜的 RX 行列。

rx_cpu_rmap是驱动回护的不凡晖映。该晖映用于查找哪一个 RX 行列得当 CPU。它可以是与给定 CPU 间接联络纠葛的行列,也可以是处置惩罚 CPU 在缓存职位地方最激情亲切的行列。获取 RX 行列索引后,set_rps_cpu()调用ndo_rx_flow_steer()以看护驱动顺序为给定的流创立新过滤器。ndo_rx_flow_steer()将前去过滤器 id,过滤器 id 将存储在每个行列的流表中。

除了实现ndo_rx_flow_steer() 外,驱动顺序还必须调用rps_may_expire_flow() 定期查抄过滤器是否仍然有效并删除过期的过滤器。

SO_REUSEPORT

linux man文档中一段文字形貌其浸染:

The new socket option allows multiple sockets on the same host to bind to the same port, and is intended to improve the performance of multithreaded network server applications running on top of multicore systems.

俭朴说,SO_REUSEPORT支持多个过程或许线程绑定到同一端口,用从进步服务器顺序的性能。我们想相识为何这个特点这么火(常常被大厂笔试官问到),究竟是经管什么成就。

Linux体系上后援应用顺序,为了行使多核的劣势,普通应用下列相比典范的多过程/多线程服务器模型:

复线程listen/accept,解决方案多个事变线程领受使命散发,虽CPU的事变负载再也不是成就,但会存在:

1. 复线程listener,在处置惩罚高速率海量跟尾时,同样会成为瓶颈;

2. CPU缓存行失效(遗失套接字布局socket structure)景象重大;

全体事变线程都accept()在同一个服务器套接字上呢,同样存在成就:

1. 多线程拜访server socket锁竞争重大;

2. 高负载下,线程之间处置惩罚不服衡,偶尔高达3:1不服衡比例;

3. 导致CPU缓存行跳跃(cache line bouncing);

4. 在劳碌CPU上存在较大耽误;

上面模型诚然可以或许做到线程和CPU核绑定,但都市存在下列成就:

繁多listener事变线程在高速的跟尾接入处置惩罚时会成为瓶颈 缓存行跳跃 很难做到CPU之间的负载平衡 随着核数的扩张,性能并无随着提升

SO_REUSEPORT支持多个过程或许线程绑定到同一端口:

允不少个套接字 bind()/listen() 同一个TCP/UDP端口

1.每个线程拥有自身的服务器套接字。

2.在服务器套接字上没有了锁的竞争。

内核层面实现负载平衡。 安好层面,监听同一个端口的套接字只能位于同一个用户上面。

其焦点的实现首要有三点:

扩张socket option,添加

SO_REUSEPORT选项,用来设置 reuseport。

编削 bind 体系调用实现,以便支持可以或许绑定到沟通的 IP 和端口。 编削处置惩罚新建跟尾的实现,查找 listener 的岁月,兴许支持在监听沟通 IP 和端口的多个 sock 之间平衡抉择 带来意思 CPU之间平衡处置惩罚,水平扩张,模型俭朴,回护方便了,过程的打点和应用逻辑解耦,过程的打点水平扩张权限下放给顺序员/打点员,可以或许痛处置论举行掌握过程启动/敞开,添加了灵巧性。这带来了一个较为宏观的水平扩张思路,线程几多是否相宜,形态是否存在同享,升高单个过程的资源寄托,针对无形态的服务器架构最为得当。 针对对客户端而言,详情上感想感染不到其更动,因为这些事变齐全在服务器端举行。 服务器无缝重启/切换,热更新,供应新的兴许性。我们迭代了一版本,需要陈列到线上,为之启动一个新的过程后,稍后敞开旧版本过程顺序,服务一贯在运行中不陆续,需要平衡适度。这就像Erlang言语层面所供应的热更新同样。 SO_REUSEPORT已知成就 SO_REUSEPORT分为两种情势,即热备份情势和负载平衡情势,在晚期的内核版本中,即便是插手对reuseport选项的支持,也仅仅为热备份情势,而在3.9内核今后,则整个改成了负载平衡情势,两种情势没有共存,诚然我一贯都停留它们可以或许共存。 SO_REUSEPORT痛处数据包的四元组{src ip, src port, dst ip, dst port}和今后绑定同一个端口的服务器套接字数量举行数据包散发。若服务器套接字数量孕育发生变换,内核会把本该上一个服务器套接字所处置惩罚的客户端跟尾所发送的数据包(比喻三次握手时期的半跟尾,以及已经实现握手但在行列中列队的跟尾)散发到此外的服务器套接字上面,兴许会导致客户端要求失利。

怎么样防范以上已知成就,普通经管思路:

1.应用安稳的服务器套接字数量,不要在负载劳碌时期等闲变换。

2.允不少个服务器套接字同享TCP要求表(Tcp request table)。

3.不应用四元组作为Hash值举行抉择外埠套接字处置惩罚,比喻抉择 会话ID或许过程ID,遴选附属于同一个CPU的套接字。

4. 应用分歧性hash算法。

与别的特点纠葛 1. SO_REUSEADDR:主若是地点复用

1.1 让处于time_wait形态的socket可以或许倏地复用原ip+port

1.2 使得0.0.0.0(ipv4通配符地点)与别的地点(127.0.0.1和10.0.0.x)不抵触

1.3 SO_REUSEADDR 的弱点在于,没有安好限定,并且没法担顾全体跟尾均匀分派。

2.与RFS/RPS/XPS-mq协作,可以或许获取进一步的性能

2.1.服务器线程绑定到CPUs

2.2.RPS散发TCP SYN包到对应CPU核上

2.3.TCP跟尾被已绑定到CPU上的线程accept()

2.4. XPS-mq(Transmit Packet Steering for multiqueue),传输行列和CPU绑定,发送 数据

2.5. RFS/RPS担保同一个跟尾后续数据包都市被散发到同一个CPU上,网卡领受行列 已经绑定到CPU,则RFS/RPS则无须设置,需要留心硬件支持与否,目标是数据包的软硬中缀、领受、处置惩罚等在一个CPU核上,并行化处置惩罚,尽管即便做到资源行使最大化。

SO_REUSEPORT的演进 3.9从前内核,兴许让多个socket同时绑定齐全沟通的ip+port,但不克不迭实现负载平衡,实现是热备。 Linux 3.9今后,兴许让多个socket同时绑定齐全沟通的ip+port,可以或许实现负载平衡。 Linux4.5版本后,内核引入了reuseport groups,它将绑定到同一个IP和Port,并且设置了SO_REUSEPORT选项的socket构造到一个group外部。目标是加快socket查询。 总结

Linux网络仓库所存在成就

TCP处置惩罚&多核 一个完备的TCP跟尾,中缀发生在一个CPU核上,但应用数据处置惩罚兴许会在此外一个核上 差别CPU焦点处置惩罚,带来了锁竞争和CPU Cache Miss(颠簸不服衡) 多个过程监听一个TCP套接字,同享一个listen queue行列 用于跟尾打点全局哈希表格,存在资源竞争 epoll IO模型多过程的惊群景象 Linux VFS的同步斲丧重大 Socket被VFS打点 VFS对文件节点Inode和目录Dentry有同步需要 SOCKET只需要在内存中存在就可,非严厉意思上文件体系,不需要Inode和Dentry 代码层面略过无须须的通例锁,但又对立了足够的兼容性

RSS、RPS、RFS 和 aRFS,这些机制是在 Linux 3.0 从前引入的,SO_REUSEPORT选项在Linux 3.9被引入内核,因而大大都发行版已经包孕并启用了它们。深入相识它们,以便为我们的服务器体系找到最佳性能设置。

性能优化无极限,我们下期再延续分享!

扩张与参考

https://garycplin.blogspot.com/2017/06/linux-network-scaling-receives-packets.html?m=1

https://jamal-jiang.github.io/2017/02/03/TCP-SO-REUSEPORT/

 

http://www.blogjava.net/yongboy/archive/2015/02/05/422760.html

 



相关资讯



Powered by 森林舞会电玩城游戏大厅登录网址 @2013-2022 RSS地图 HTML地图