[[437200]]
本文转载自微信公众号「极客更生」,作家极客更生。转载本文请计议极客更生公众号。
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
|