|
|
51CTO旗下网站
|
|
移步端
创造专栏

副STGW年产量下降探秘内核收包机制

在STGW现网运营中,出现了共计流量突然下降的Case,此刻我们的正常化拨测机制探测到失败,并且用户侧重试次数增加、呼吁延迟增大。

笔者:dalektan| 2020-01-16 09:55

 题材现象

在STGW现网运营中,出现了共计流量突然下降的Case,此刻我们的正常化拨测机制探测到失败,并且用户侧重试次数增加、呼吁延迟增大。但通过已部分各类监控进行定点,只发现整体CPU、内存、经过状态、QPS(每秒请求数)等主要指标虽然出现动荡,但均未超过告警水位。

如图,年产量出现了跌幅,并且出现健康检查拨测失败。

但是,完全CPU在总量出现缺口的期间,并未超过阈值,反而有部分降低,之后因为恢复健康含量冲高才出现一个小毛刺。

另外,内存和应用层监控,都没有意识明显异常。

最初探索

众目睽睽,仅凭这些常规监控,无法稳定问题至关重要原因,尽量拿到更多的题材信息,成为了当务之急。侥幸的是,副STGW自研的秒级监控体系中,咱们查到了部分关键的消息。

在STGW自研的监察体系里,咱们增加了骨干资源细粒度监控,针对CPU、内存、基础网络协议栈这些核心指标支持秒级监控、监督指标更年轻化,如下图就是出题目时间段,cpu各国核心的秒级消耗情况。

穿过STGW CPU细粒度监控展示的消息,可以看出在出现问题的时间段内,局部CPU核被跑满,并且是出于软中断消耗造成,想起整个问题时间段,咱们还发现,在一段长时间内,这种软中断热点偏高都会在几个固定的核上出现,不会转移给任何核。

另外,STGW的监察模块支持在出现系统核心资源异常时,抓取当时的函数调用栈信息,有了函数调用信息,咱们能更准确的了解是什么造成了系统核心资源异常,而不是继承猜想。如图展示了STGW监督抓到的函数调用及cpu占比信息:

穿过函数栈监控信息,咱们发现了inet_lookup_listener函数是这次CPU软中断热点的首要消耗者。出现问题时,其它函数调用在没有发生多少变化情况下,inet_lookup_listener由原始很微小的cpu消耗占比,刹那间冲到了TOP1。

穿过这里,咱们可以初步确定,inet_lookup_listener消耗过高跟软中断热点强相关,顶热点将cpu单核跑满后就可能引发出流量有损的题材。出于软中断热点持续在产生,点上平稳隐患很大。基于这个紧迫的祥和问题,咱们从为什么产生热点、为什么热点只在有的cpu core上出现两个样子,拓展了问题分析、固定和消灭。

为什么产生了热点

1. 探秘 inet_lookup_listener

出于perf已经给咱们提供了热点所在,第一从热点函数入手开展分析,重组本代码得知,__inet_lookup铺天盖地函数是用于将接收的数据包定位到一个具体的socket上,但只有握手包会进入到找__inet_lookup_listener的逻辑,大多数数据包是通过__inet_lookup_established追寻socket。

现实分析lookup_listener的编码我们发现,出于listen socket不具备四元组特征,故此内核只能用监听端口计算一下哈希值,并利用了 listening_hash 哈希桶存起来,握手包发赶到的时节,就下该哈希桶中寻找对应的listen socket。

      
  1. struct sock *__inet_lookup_listener(struct net *net, 
  2.             struct inet_hashinfo *hashinfo, 
  3.             const __be32 saddr, __be16 sport, 
  4.             const __be32 daddr, const unsigned short hnum, 
  5.             const int dif) 
  6. // 简言之了一部分代码 
  7. // 获取listen fd 哈希桶 
  8.   struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; 
  9.         result = NULL
  10.   hiscore = 0; 
  11. // 遍历桶中的节点 
  12.         sk_nulls_for_each_rcu(sk, node, &ilb->head) { 
  13.     score = compute_score(sk, net, hnum, daddr, dif); 
  14.     if (score > hiscore) { 
  15.       result = sk; 
  16.       hiscore = score; 
  17.       reuseport = sk->sk_reuseport; 
  18.       if (reuseport) { 
  19.         phash = inet_ehashfn(net, daddr, hnum, 
  20.                  saddr, sport); 
  21.         matches = 1; 
  22.       } 
  23.     } else if (score == hiscore && reuseport) { 
  24.       matches++; 
  25.       if (((u64)phash * matches) >> 32 == 0) 
  26.         result = sk; 
  27.       phash = next_pseudo_random32(phash); 
  28.     } 
  29.   } 
  30. }  

相对来说并不复杂的lookup_listener函数为什么会造成这么大的cpu付出?历经进一步稳定后,意识问题四方:listen哈希桶开的太小了,只有32个。

      
  1. /* This is for listening sockets, thus all sockets which possess wildcards. */ 
  2. #define INET_LHTABLE_SIZE  32  /* Yes, really, this is all you need. */ 

为什么内核这里会以为listen哈希桶大小32就满足需要了呢?

在IETF(互联网工程任务组)关于端口的设计中,0-1023是System port,系统保留使用,1024-49151为Registered port,为IANA(互联网数字分配机构)可分配给一些固定应用,49152-65535是Dynamic port,是可以真正自由使用的。当然了,这只是IETF的一个计划,在Linux官方,除了System port,另两个端口段并未真的做了鲜明区分,除非端口已经被占用,他家可以自由使用,此地提一个Linux官方跟端口划分有关系的本参数:ip_local_port_range,他表示系统在成立TCP/UDP联网时,系统送它们分配的端口范围,默认的ip_local_port_range值是32768-60999,拓展这个设置后好处是,61000~65535端口是可以更安全的用来做为服务器监听,而不用担心一些TCP联网将他占用。

故此,在例行的情况下,传感器的listen port多少,简言之就是几w个这样的量级。这种量级下,一度port对应一个socket,哈希桶大小为32是可以吸收的。然而在基础支持了reuseport并且把广大采用后,气象就不一样了,在多进程架构里,listen port对应的socket多少,是会把几十倍的加大的。以应用层监听了5000个端口,reuseport 采用了50个cpu基本为例,5000*50/32约等于7812,意味着每次握手包到来时,光是查找listen socket,就要求遍历7800多次。随着机器硬件性能越来越强,应用层使用的cpu多少增加,其一题目还会持续强化。

正因为上述原因,并且我们现网机器开启了reuseport,在端口数量较多的机械里,inet_lookup_listener的哈希桶大小太小,遍历过程消耗了cpu,导致出现了函数热点。

2. 如何解决__inet_lookup_listener题材

Linux镇区难道没有注意到开启reuseport此后,原本的哈希桶大小不够用这个题目吗?

其实社区是注意到了这个题目的,并且有修复这个题目。

副Linux 4.17起来,Linux镇区就修复了由于reuseport带来的socket多少过多,导致inet_lookup_listener追寻缓慢的题材,修复方案分两步:

1. 引入了两次查找,第一还是根据目的端口进行哈希,接着会利用握手包中拿到的四元组信息,按照四元组进行重大次查找,如果四元组获取不到结果,则采取之前那种对于任意IP地点查找。

      
  1. struct sock *__inet_lookup_listener(struct net *net, 
  2.             struct inet_hashinfo *hashinfo, 
  3.             struct sk_buff *skb, int doff, 
  4.             const __be32 saddr, __be16 sport, 
  5.             const __be32 daddr, const unsigned short hnum, 
  6.             const int dif, const int sdif) 
  7.   struct inet_listen_hashbucket *ilb2; 
  8.   struct sock *result = NULL
  9.   unsigned int hash2; 
  10.  
  11. // 根据目的端口进行重大次哈希 
  12.   hash2 = ipv4_portaddr_hash(net, daddr, hnum); 
  13.   ilb2 = inet_lhash2_bucket(hashinfo, hash2); 
  14. // 根据四元组信息再做一次查找 
  15.   result = inet_lhash2_lookup(net, ilb2, skb, doff, 
  16.             saddr, sport, daddr, hnum, 
  17.             dif, sdif); 
  18.   if (result) 
  19.     goto done; 
  20.  
  21.   /* Lookup lhash2 with INADDR_ANY */ 
  22. // 四元组没查到,尝试在0.0.0.0监听范围查找 
  23.   hash2 = ipv4_portaddr_hash(net, htonl(INADDR_ANY), hnum); 
  24.   ilb2 = inet_lhash2_bucket(hashinfo, hash2); 
  25.  
  26.   result = inet_lhash2_lookup(net, ilb2, skb, doff, 
  27.             saddr, sport, htonl(INADDR_ANY), hnum, 
  28.             dif, sdif); 
  29. done: 
  30.   if (IS_ERR(result)) 
  31.     return NULL
  32.   return result; 

2. 统一处理reuseport推广出来的socket,在发现指定的端口开启了reuseport此后,不再是遍历式的去获取到相当的socket,而是将他看成一个整体,二次哈希下,租用 reuseport_select_sock,取到相当的socket。

      
  1. static struct sock *inet_lhash2_lookup(struct net *net, 
  2.         struct inet_listen_hashbucket *ilb2, 
  3.         struct sk_buff *skb, int doff, 
  4.         const __be32 saddr, __be16 sport, 
  5.         const __be32 daddr, const unsigned short hnum, 
  6.         const int dif, const int sdif) 
  7.   bool exact_dif = inet_exact_dif_match(net, skb); 
  8.   struct inet_connection_sock *icsk; 
  9.   struct sock *sk, *result = NULL
  10.   int score, hiscore = 0; 
  11.   u32 phash = 0; 
  12.  
  13.   inet_lhash2_for_each_icsk_rcu(icsk, &ilb2->head) { 
  14.     sk = (struct sock *)icsk; 
  15.     score = compute_score(sk, net, hnum, daddr, 
  16.               dif, sdif, exact_dif); 
  17.     if (score > hiscore) { 
  18.       if (sk->sk_reuseport) { 
  19.                 // 如果是reuseport,拓展二次哈希物色 
  20.         phash = inet_ehashfn(net, daddr, hnum, 
  21.                  saddr, sport); 
  22.         result = reuseport_select_sock(sk, phash, 
  23.                      skb, doff); 
  24.         if (result) 
  25.           return result; 
  26.       } 
  27.       result = sk; 
  28.       hiscore = score; 
  29.     } 
  30.   } 
  31.  
  32.   return result; 

总结来说,镇区通过引入两次查找+统一reuseport sockets的拍卖,消灭了reuseport带来的sockets多少放大效果。此地结合我们的探讨,此外提供两个可行的低成本解决方案:

1. 修改本哈希桶大小,根据reuseport增长socket的倍数,应当提高INET_LHTABLE_SIZE,或者直接改成例如2048

      
  1. #define INET_LHTABLE_SIZE  2048  

2. 关闭reuseport可以减少socket数据到32个哈希桶大小能负担的框框,故而降低该函数消耗。

增长郊区方案,此地的三个办法在本质上都是减少listen table哈希桶的遍历复杂度。镇区的提案一套比较系统之主意,事后随着基础版本升级,确认会将以此题目解决掉。但短期升级内核的资金较高,故此后面两个方案就足以用来短期解决问题。另外,关闭reuseport虽然不需要更改内核,但需要考虑应用层server对于reuseport的依赖情况。

为什么热点只在有的核心出现

消灭完哈希桶问题后,咱们并没有固定到任何之题材,眼前提到,软中断热点仅在有的cpu核上出现,如果仅仅是__inet_lookup_listener题材,按理所有cpu核的软中断消耗都会偏高。如果这里问题没有解释清楚,一旦出现热点函数,一部分单核就会把跑满,意味着整机容量强依赖部分单核之性质瓶颈,超出了单核能力就会有损,这是完整不能接受的。

1. 副CPU中断数入手

根据问题现象,咱们做了部分假设,在此间最直观的假想就是,咱们的数据包在各国核上并不是负载均衡的。

第一,穿过cat /proc/interrupts找到网卡在各国cpu核的间歇数,意识网卡在各国核的血性中断就已经不平衡衡了。这就是说会是坚强中断亲和性的题材吗?接着检查了网卡各个队列的smp_affiinity,意识每个队列与cpu核都是一一对应并且互相错开,顽强中断亲和性设置没有问题。

紧接着,咱们排查了网卡,咱们的网卡默认都打开了RSS(网卡多队),每个队列绑定到一个核心上,既然硬中断亲和性没有问题,这就是说会是网卡多队本身就不均衡吗?穿过ethtool -S eth0/eth1再过滤出每个rx_queue的收包数,咱们得到如下图:

原本网卡多队收包就已经严重不均衡了,以入包量升序排序,意识不同之rx_queue 收包数量相差达到了上万倍!

2. 探索网卡多队(RSS)

此地我们重点检查了几个网卡多队的底数

      
  1. // 检查网卡的队数 
  2. ethtool -l eth0 
  3. Current hardware settings: 
  4. RX:             0 
  5. TX:             0 
  6. Other:          1 
  7. Combined:       48 
  8.  
  9. // 检查软件哈希开关 
  10. ethtool -k eth0 
  11. receive-hashing: on 
  12.  
  13. // 检查软件哈希之底数,此地显示以TCP是以四元组信息进行哈希 
  14. ethtool -n eth0 rx-flow-hash tcp4 
  15. TCP over IPV4 flows use these fields for computing Hash flow key
  16. IP SA 
  17. IP DA 
  18. L4 bytes 0 & 1 [TCP/UDP src port] 
  19. L4 bytes 2 & 3 [TCP/UDP dst port] 

该署参数都是符合预期的,数据包会根据TCP包的四元组哈希到不同之队上。咱们继续采用假设论证法,会是数据包本身就是比如长连接,导致不均衡吗?穿过检查我们服务端的日记,意识请求的ip和端口都是比较分散的,传输的多寡也都是较小文件,并没有集中化。

历经了一个折腾,咱们有了新的假设,出于我们现网大部分流量是IPIP短道及GRE封装的数据包,在一般数据包的IP header上多了一层header,外层IP与我们在server观看的并不一样,外层IP是异样集中的。此地是否会让网卡多队的平衡策略失效呢?

来源网图,以GRE包为例,IP数据包其实是分了正面IP头部、gre层、内层IP头部,以及再往上的TCP/UDP层,如果只获取了正面IP头部,则较为集中,难以进行分散。

历经同事帮忙牵线,咱们从网卡厂商处获得了重在的消息,不同之网卡对于多队哈希书法是不一样的!

副网卡厂商处进一步肯定得知,咱们在采取的这款网卡,是不支持解析封装后的数据包的,只会以正面IP表现哈希根据。销售商提供了一款新型号的网卡,是帮腔解析IPIP及GRE内层IP PORT的。咱们经过实测这两种网卡,意识确实如此。

观看这里,网卡多队不均衡问题原因已经固定清楚,出于现网使用了IPIP或GRE这类封装协议,局部网卡不支持解析内层IP PORT拓展哈希,故而导致多队不均衡,进一步导致cpu顽强中断不均衡,下一场不均的软中断热点便出现了。

3. 如何解决网卡多队不均衡

对于STGW来说,咱们已经确定了不均的网卡型号,都是型号较老的网卡,咱们正在逐步使用新的网卡型号,新网卡型号已验证支持IPIP及GRE分立式的数据包负载均衡。

为什么RPS没有起作用

Receive Packet Steering (RPS),是根本的一种负载均衡机制,即便硬件层面收到的数据包不均的,RPS会对数据包再次进行哈希与分流,合同她进入网络协议栈是均衡的。

历经确认,出题目机器上都开始了RPS。故此问题还是没有解释清楚,即便旧型号的网卡RSS不均衡,但经过内核RPS此后,数据包才会送给网络协议栈,下一场调用_inet_lookup_listener,此刻依然出现热点不均衡,表明RPS并未生效。

1. 刺探硬件及内核收包流程

出于引入了RPS其一概念,在稳定该问题前,我梳理了一份简明收包流程,穿过了解数据包是如何通过硬件、基础、再到本网络协议栈,可以更鲜明的询问RPS所处的岗位,以及我们相遇的题材。

如上图所示,数据包在进入内核IP/TCP协和栈之前,经验了那些步骤:

  1. 网口(NIC)接受packets
  2. 网口通过DMA(Direct memeory access)名将数据写入到内存(RAM)官方。
  3. 网口通过RSS(网卡多队)名将接收的数据包分发给某个rx列,并触发该队列所绑定核上的CPU中断。
  4. 接受中断的核,租用该核所在的本软中断线程(softirqd)拓展继续处理。
  5. softirqd承担将数据包从RAM官方取到本中。
  6. 如果开启了RPS,RPS会选择一个目标cpu核来处理该包,如果目标核非当前正在运行的核,则会触发目标核的IPI(计算机之间中断),并将数据包放在目标核的backlog列中。
  7. 软中断线程将数据包(数据包可能来源于第5地、或第6地),穿过gro(generic receive offload,如果开启的话)等处理后,送往IP协和栈,及随后的TCP/UDP等协议栈。

回首我们面前定位的题材,__inet_lookup_listener热点对应的是IP协和栈的题材,网卡多队不均衡是步骤3,RSS阶段出现的题材。RPS则是在步骤6官方。

2. 探秘RPS负载不均衡问题

穿过cat /proc/net/softnet_stat,可以获取到每个核接收的RPS次数。拿到这个数目后,咱们发现,不同之核在收到RPS次数上相差达到上百倍,并且RPS次数最多的核,相当就是软中断消耗出现热点的核。

至此我们发现,虽然网卡RSS生活不均,但RPS却依然将过多之数据包给了一部分cpu core,没有成功负载均衡,这才是导致我们软中断热点不均的直接原因。

穿过在基础代码库中找到RPS相关代码并开展分析,咱们再次发现了部分可疑的线

      
  1. static int get_rps_cpu(struct net_device *dev, struct sk_buff *skb, 
  2.            struct rps_dev_flow **rflowp) 
  3. // 简言之部分代码 
  4.   struct netdev_rx_queue *rxqueue; 
  5.   struct rps_map *map; 
  6.   struct rps_dev_flow_table *flow_table; 
  7.   struct rps_sock_flow_table *sock_flow_table; 
  8.   int cpu = -1; 
  9.   u16 tcpu; 
  10.  
  11.   skb_reset_network_header(skb); 
  12.   if (rps_ignore_l4_rxhash) { 
  13. // 计算哈希值 
  14.     __skb_get_rxhash(skb); 
  15.     if (!skb->rxhash) 
  16.       goto done; 
  17.   } 
  18.   else if(!skb_get_rxhash(skb)) 
  19.     goto done; 
  20.  
  21. // 穿过哈希值计算出目标CPU 
  22.         if (map) { 
  23.     tcpu = map->cpus[((u64) skb->rxhash * map->len) >> 32]; 
  24.  
  25.     if (cpu_online(tcpu)) { 
  26.       cpu = tcpu; 
  27.       goto done; 
  28.     } 
  29.   } 
  30.  
  31. done: 
  32.   return cpu; 
  33.  
      
  1. /* 
  2.  * __skb_get_rxhash: calculate a flow hash based on src/dst addresses 
  3.  * and src/dst port numbers.  Sets rxhash in skb to non-zero hash value 
  4.  * on success, zero indicates no valid hash.  Also, sets l4_rxhash in skb 
  5.  * if hash is a canonical 4-tuple hash over transport ports. 
  6.  */ 
  7. void __skb_get_rxhash(struct sk_buff *skb) 
  8.   struct flow_keys keys; 
  9.   u32 hash; 
  10.  
  11.   if (!skb_flow_dissect(skb, &keys)) 
  12.     return
  13.  
  14.   if (keys.ports) 
  15.     skb->l4_rxhash = 1; 
  16. // 采用TCP/UDP四元组进行计算 
  17.   /* get a consistent hash (same value on both flow directions) */ 
  18.   if (((__force u32)keys.dst < (__force u32)keys.src) || 
  19.       (((__force u32)keys.dst == (__force u32)keys.src) && 
  20.        ((__force u16)keys.port16[1] < (__force u16)keys.port16[0]))) { 
  21.     swap(keys.dst, keys.src); 
  22.     swap(keys.port16[0], keys.port16[1]); 
  23.   } 
  24. // 采用jenkins哈希书法 
  25.   hash = jhash_3words((__force u32)keys.dst, 
  26.           (__force u32)keys.src, 
  27.           (__force u32)keys.ports, hashrnd); 
  28.   if (!hash) 
  29.     hash = 1; 
  30.  
  31.   skb->rxhash = hash; 

猜想一:rps_ignore_l4_rxhash未打开,导致不均衡?

穿过代码发现 rps_ignore_l4_rxhash 会影响当前是否计算哈希值,眼前机器未设置ignore_l4_rxhash,则根本会直接行使网卡RSS计算出的哈希值,根据上面定位的网卡RSS不均的总结,RSS哈希值可能是不准的,此地会导致问题吗?

咱们将ignore_l4_rxhash开关进行打开

      
  1. sysctl -w kernel.rps_ignore_l4_rxhash=1 

意识并没有对不均衡问题产生任何改善,排除这个假设。

猜想二:RPS所采取的哈希书法有缺陷,导致不均衡?

对于负载不均类的题材,应该会怀疑当前利用的平衡算法存在缺陷,RPS此地使用的是jenkins hash(jhash_3words)书法,是一番比较著名且把广大采用的作法,历经了众多环境的印证,出现缺陷的可能较小。但我们还是想办法进行了部分验证,出于是根本代码,并且没有提供替代性的作法,转移内核的平价相对较高。

故此这里我们采用的对待的一手快速确定,在同样的本版本,在现网找到了负载均衡的机械,检查两边的组成部分本开关和RPS安排都是一致的,表明同样的RPS哈希书法,只是有的机器不均衡,故此这里算法侧先不做进一步挖掘。

猜想三:和RSS题材一样,RPS也不支持对封装后的多寡进行四元组哈希?

skb_flow_dissect是负责解析出TCP/UDP四元组,历经初步分析,基础是帮腔IPIP、GRE等通用的包装协议,并从这些协议数据包中,取出需要的四元组信息进行哈希。

在各种假设与折腾都没有找到新的突破的时,咱们采用systemtap其一基础调试神器,hook了主要的几个函数和信息,历经论证和高考后,在现网进行了短暂的debug,募集到了所要求的严重性信息。

      
  1. #! /usr/bin/env stap 
  2. /* 
  3. Analyse problem that softirq not balance with RPS. 
  4.  
  5. Author: dalektan@tencent.com 
  6.  
  7. Usage: 
  8. stap -p4 analyse_rps.stp -m stap_analyse_rps 
  9. staprun -x cpuid stap_analyse_rps.ko 
  10. */ 
  11.  
  12.  
  13. // To record how cpu changed with rps execute 
  14.  
  15. private global target_cpu = 0 
  16. private global begin_cpu 
  17. private global end_cpu 
  18.  
  19. probe begin { 
  20.   target_cpu = target() 
  21.   begin_cpu = target_cpu - 2 
  22.   end_cpu = target_cpu + 2 
  23. // 指定需要分析的cpu规模,避免对性能产生影响 
  24.   printf("Prepare to analyse cpu is :%d-%d\n", begin_cpu, end_cpu) 
  25.  
  26.  
  27. // To record tsv ip addr, daddr and protocol(ipip, gre or tcp) 
  28. probe kernel.function("ip_rcv").call { 
  29.   if (cpu() >= begin_cpu && cpu() <= end_cpu) { 
  30.     ip_protocol = ipmib_get_proto($skb) 
  31.     // if not tcp, ipip, gre, then return 
  32.     if (ip_protocol == 4 || ip_protocol == 6 || ip_protocol == 47) { 
  33.       saddr = ip_ntop(htonl(ipmib_remote_addr($skb, 0))) 
  34.       daddr = ip_ntop(htonl(ipmib_local_addr($skb, 0))) 
  35.  
  36.     printf("IP %s -> %s proto:%d rx_queue:%d cpu:%d\n"
  37.            saddr, daddr, ip_protocol, $skb->queue_mapping-1, cpu()) 
  38.     } 
  39.   } 
  40.  
  41. // To record tcp states 
  42. probe tcp.receive.call { 
  43.   if (cpu() >= begin_cpu && cpu() <= end_cpu) { 
  44.      printf("TCP %s:%d -> %s:%d  syn:%d  rst:%d  fin:%d cpu:%d\n"
  45.             saddr, sport , daddr, dport, syn, rst, fin, cpu()) 
  46.   } 

穿过使用上述systemtap剧本进行分析之后,咱们得到了一番关键信息,汪洋GRE协和(希冀中proto:47)的数据包,不论是其四元组是什么,都把集中调度到了单个核心上,并且这个中心正好是软中断消耗热点核。并且其他协议数据包未出现这个题目。

走到这里,题材渐为开展,GRE数据包未按预期均衡到各个核心,但根据之前的剖析,RPS是帮腔GRE协和获取四元组的,为什么在此间,不同之四元组,依然被哈希算成了同一个目标核呢?

3. 探索GRE数据包不均衡的谜

带着这个题目进一步挖掘,穿过抓包以及代码比对分析,迅速有了打破,固定到了原因是:眼前基本仅识别GRE_VERSION=0的GRE协和包并获取其四元组信息,而我辈的数据包,是GRE_VERSION=1的。

      
  1. // skb_flow_dissect 获取四元组信息 
  2. switch (ip_proto) { 
  3.   case IPPROTO_GRE: { 
  4.     struct gre_hdr { 
  5.       __be16 flags; 
  6.       __be16 proto; 
  7.     } *hdr, _hdr; 
  8.  
  9.     hdr = skb_header_pointer(skb, nhoff, sizeof(_hdr), &_hdr); 
  10.     if (!hdr) 
  11.       return false
  12.     /* 
  13.      * Only look inside GRE if version zero and no 
  14.      * routing 
  15.      */ 
  16. // 只解析GRE_VERSION = 0的GRE协和数据包 
  17.     if (!(hdr->flags & (GRE_VERSION|GRE_ROUTING))) { 
  18.       proto = hdr->proto; 
  19.       nhoff += 4; 
  20.       if (hdr->flags & GRE_CSUM) 
  21.         nhoff += 4; 
  22.       if (hdr->flags & GRE_KEY) 
  23.         nhoff += 4; 
  24.       if (hdr->flags & GRE_SEQ) 
  25.         nhoff += 4; 
  26.       if (proto == htons(ETH_P_TEB)) { 
  27.         const struct ethhdr *eth; 
  28.         struct ethhdr _eth; 
  29.  
  30.         eth = skb_header_pointer(skb, nhoff, 
  31.                sizeof(_eth), &_eth); 
  32.         if (!eth) 
  33.           return false
  34.         proto = eth->h_proto; 
  35.         nhoff += sizeof(*eth); 
  36.       } 
  37.       goto again; 
  38.     } 
  39.     break; 
  40.   }  

第一,咱们先承认一下,GRE_VERSION=1是符合标准的吗,答案是符合的,如果去了解一下PPTP协和的话,可以了解RFC规定了PPTP协和就是运用的GRE协和封装并且GRE_VERSION=1。

这就是说为什么内核这里不支持这种标记呢?

穿过在Linux镇区进行检索,咱们发现Linux 4.10本子开始支持PPTP协和从的GRE包识别与四元组获取,也就是GRE_VERSION=1的状况。出于没有参加对PPTP协和的支持,故此出现不识别GRE_VERSION=1(PPTP协和)的状况,RPS不会扮演获取这种数据包的四元组信息,哈希下也是不均的,末了导致单核出现软中断热点。

4. 如何解决RPS不均衡问题

迄今,整整的题材都已经拨开云雾,说到底对于RPS不均衡问题,此地提供三种解决方案:

  1. 对于RSS网卡多队已经是均衡的机械,可以将修改kernel.rps_ignore_l4_rxhash = 0,让RPS直接行使网卡硬件哈希值,出于硬件哈希值足够分散,故此RPS功能也是均衡的。
  2. 对于RSS网卡多队均衡的机械,穿过ethtool -S/-L翻开或修改网卡多队数目,如果队列数不少于cpu核数,再将多队通过/proc/irq/装备id/smp_affinity分散绑定到不同之cpu核。这样可以从容运用网卡RSS的平衡效果,而无需打开内核RPS。
  3. 在眼前基本进行修改或热补丁,在RPS函数中新增对GRE_VERSION=1的GRE数据包的鉴别与四元组获取。
  4. 升级内核到Linux 4.10后,即可支持PPTP协和包的RPS负载均衡。

总结

说到底,总结一下整个问题和一定过程,咱们从一次流量下降,工作有损的题材出发,副最开始找不到思路,到找到软中断热点这个关键点,再到区分IP协和栈、网卡多队、内核收包这三个范畴进行问题入手,没有满足于已经解决之一部分问题,不断深挖了下来,终于各个方向击破,拨开了问题的难得面纱,并送出了解决方案。

借着对问题的一定和消灭,收缴良多,读书了内核收包流程,深谙了基础问题一定工具和手法。谢谢STGW组里同事,文中诸多成果都是组织的共同奋斗。

【本文为51CTO专栏作者“腾讯技术工程”原创稿件,转载请联系原作者(微信号:Tencent_TEG)】

戳这里,瞧该作者更多好文

【编纂推荐】

  1. 特色工程的增值流量安全检测
  2. What?老板让我开发一个洞级流量的特大型网站
  3. 96秒100京!如何抗住双11高并发流量?
【义务编辑: 武晓燕 TEL:(010)68476606】

点赞 0
  • STGW  年产量  基础
  • 分享:
    大家都在看
    猜你喜欢



  •