热点资讯
物联网软件开发价格 管事器性能优化之相聚性能优化
发布日期:2024-09-19 15:29 点击次数:147
[[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_REUSEPORTlinux 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核上
冠军奖金高达40万美元的应氏杯每四年举行一次,且在奥运会举办年举办,所以被称为“围棋奥运”。本届应氏杯在赛制上做了重大改革,其中第二阶段赛程涵盖了从上海开始的十六强赛至在应昌期故乡慈城的半决赛。中国棋手整体表现出色,尤其在十六强将韩国前三棋手如数淘汰出局,是称大捷。而八强之战柯洁惊险逆转上海名将王星昊,被视为“第一人”状态回猛的标志。
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