RPS和RFS网卡多队列性能调优实践

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RPS和RFS网卡多队列性能调优实践相关的知识,希望对你有一定的参考价值。

参考技术A

为了解决LVS ksoftirqd CPU使用率100%导致网卡软中断丢包,我和同事们一起搜索了大量的资料去分析问题,特别是感谢美团技术团队的分享帮助我们快速梳理优化思路,最后明确了如何重构RPS和RFS网卡多队列的优化脚本。个人认为这是一个大家可能普遍会遇到的问题,文章内的分析思路和解决方案未必是最优解,也欢迎各位分享自己的解决方法。

2019年07月03日 - 初稿

阅读原文 - https://wsgzao.github.io/post/rps/

扩展阅读

Redis 高负载下的中断优化 - https://tech.meituan.com/2018/03/16/redis-high-concurrency-optimization.html

我们遇到的问题属于计划外的incident,现象是某产品用户在线率突然降低,LVS Master同时收到CPU High Load告警,检查发现该节点出现网卡大量断开重连和丢包情况,应急切换到LVS Slave也出现上述问题,在排除掉流量异常和外部攻击后选择切换DNS到背后的nginx Real Servers后服务逐步恢复。

复盘核心原因在于系统初始化时rps优化脚本没有成功执行,这个脚本起初是因为早期DBA团队遇到过CPU负载较高导致网卡异常,这个优化脚本也一直传承至今,却已经没有人知道为什么添加。现在大多数服务器没有执行成功而被大家一直所忽视显然也是post check没有做到位。在早期大家都停留在Bash Shell运维的阶段,没有专职的团队来管理确实容易失控,好在现在可以基于Ansible来做初始化和检查,运维的压力也减轻了一部分。

通过Google搜索相关知识的过程中,我们也发现在不少人都会遇到这样类似的问题。比如这篇文章提到 lvs/irq

lvs 的性能问题,软中断耗尽 CPU 单核后到达处理极限

和华为的工程师们在交换经验的时候对方分享了一个关于RSS和RPS关系图,之后的内容还会引用美团技术团队的分析

我们遇到的情况是缺少可用服务器资源选择把用户外部请求流量和Codis Cache Cluster内部流量临时混在了同一个LVS上,虽然看上去CPU和traffic的整体压力都不算高,但是CPU的处理压力可能恰好集中在了和外网Bond1网卡相同的Core上最后引起了ksoftirqd软中断,而内网Bond0网卡就没有监控到任何丢包。虽然我们也有正常开启 irqbalance ,但不清楚是不是因为受到 cpupower performance 和 NUMA 的影响最后也没能阻止事故的发生,最终的优化方案主要是手动开启RPS和RFS,大致步骤如下:

This document describes a set of complementary techniques in the Linux
networking stack to increase parallelism and improve performance for
multi-processor systems.

The following technologies are described:

https://www.kernel.org/doc/Documentation/networking/scaling.txt

RECEIVE PACKET STEERING (RPS)

Receive Packet Steering (RPS) is similar to RSS in that it is used to direct packets to specific CPUs for processing. However, RPS is implemented at the software level, and helps to prevent the hardware queue of a single network interface card from becoming a bottleneck in network traffic.

RPS has several advantages over hardware-based RSS:

RPS is configured per network device and receive queue, in the /sys/class/net/*device*/queues/*rx-queue*/rps_cpus file, where device is the name of the network device (such as eth0 ) and rx-queue is the name of the appropriate receive queue (such as rx-0 ).

The default value of the rps_cpus file is zero. This disables RPS, so the CPU that handles the network interrupt also processes the packet.

To enable RPS, configure the appropriate rps_cpus file with the CPUs that should process packets from the specified network device and receive queue.

The rps_cpus files use comma-delimited CPU bitmaps. Therefore, to allow a CPU to handle interrupts for the receive queue on an interface, set the value of their positions in the bitmap to 1. For example, to handle interrupts with CPUs 0, 1, 2, and 3, set the value of rps_cpus to 00001111 (1+2+4+8), or f (the hexadecimal value for 15).

For network devices with single transmit queues, best performance can be achieved by configuring RPS to use CPUs in the same memory domain. On non-NUMA systems, this means that all available CPUs can be used. If the network interrupt rate is extremely high, excluding the CPU that handles network interrupts may also improve performance.

For network devices with multiple queues, there is typically no benefit to configuring both RPS and RSS, as RSS is configured to map a CPU to each receive queue by default. However, RPS may still be beneficial if there are fewer hardware queues than CPUs, and RPS is configured to use CPUs in the same memory domain.

RECEIVE FLOW STEERING (RFS)

Receive Flow Steering (RFS) extends RPS behavior to increase the CPU cache hit rate and thereby reduce network latency. Where RPS forwards packets based solely on queue length, RFS uses the RPS backend to calculate the most appropriate CPU, then forwards packets based on the location of the application consuming the packet. This increases CPU cache efficiency.

RFS is disabled by default. To enable RFS, you must edit two files:

/proc/sys/net/core/rps_sock_flow_entries

Set the value of this file to the maximum expected number of concurrently active connections. We recommend a value of 32768 for moderate server loads. All values entered are rounded up to the nearest power of 2 in practice.

/sys/class/net/*device*/queues/*rx-queue*/rps_flow_cnt

Replace device with the name of the network device you wish to configure (for example, eth0 ), and rx-queue with the receive queue you wish to configure (for example, rx-0 ).

Set the value of this file to the value of rps_sock_flow_entries divided by N , where N is the number of receive queues on a device. For example, if rps_flow_entries is set to 32768 and there are 16 configured receive queues, rps_flow_cnt should be set to 2048 . For single-queue devices, the value of rps_flow_cnt is the same as the value of rps_sock_flow_entries .

Data received from a single sender is not sent to more than one CPU. If the amount of data received from a single sender is greater than a single CPU can handle, configure a larger frame size to reduce the number of interrupts and therefore the amount of processing work for the CPU. Alternatively, consider NIC offload options or faster CPUs.

Consider using numactl or taskset in conjunction with RFS to pin applications to specific cores, sockets, or NUMA nodes. This can help prevent packets from being processed out of order.

接收数据包是一个复杂的过程,涉及很多底层的技术细节,但大致需要以下几个步骤:

NIC 在接收到数据包之后,首先需要将数据同步到内核中,这中间的桥梁是 rx ring buffer 。它是由 NIC 和驱动程序共享的一片区域,事实上, rx ring buffer 存储的并不是实际的 packet 数据,而是一个描述符,这个描述符指向了它真正的存储地址,具体流程如下:

当驱动处理速度跟不上网卡收包速度时,驱动来不及分配缓冲区,NIC 接收到的数据包无法及时写到 sk_buffer ,就会产生堆积,当 NIC 内部缓冲区写满后,就会丢弃部分数据,引起丢包。这部分丢包为 rx_fifo_errors ,在 /proc/net/dev 中体现为 fifo 字段增长,在 ifconfig 中体现为 overruns 指标增长。

这个时候,数据包已经被转移到了 sk_buffer 中。前文提到,这是驱动程序在内存中分配的一片缓冲区,并且是通过 DMA 写入的,这种方式不依赖 CPU 直接将数据写到了内存中,意味着对内核来说,其实并不知道已经有新数据到了内存中。那么如何让内核知道有新数据进来了呢?答案就是中断,通过中断告诉内核有新数据进来了,并需要进行后续处理。

提到中断,就涉及到硬中断和软中断,首先需要简单了解一下它们的区别:

当 NIC 把数据包通过 DMA 复制到内核缓冲区 sk_buffer 后,NIC 立即发起一个硬件中断。CPU 接收后,首先进入上半部分,网卡中断对应的中断处理程序是网卡驱动程序的一部分,之后由它发起软中断,进入下半部分,开始消费 sk_buffer 中的数据,交给内核协议栈处理。

通过中断,能够快速及时地响应网卡数据请求,但如果数据量大,那么会产生大量中断请求,CPU 大部分时间都忙于处理中断,效率很低。为了解决这个问题,现在的内核及驱动都采用一种叫 NAPI(new API)的方式进行数据处理,其原理可以简单理解为 中断 + 轮询,在数据量大时,一次中断后通过轮询接收一定数量包再返回,避免产生多次中断。

由于接收来自外围硬件 (相对于 CPU 和内存) 的异步信号或者来自软件的同步信号,而进行相应的硬件、软件处理;发出这样的信号称为进行中断请求 (interrupt request, IRQ)

1.top 按下数字键 1

2.mpstat -P ALL 2

mpstat使用介绍和输出参数详解 - https://wsgzao.github.io/post/mpstat/

线上Redis高并发性能调优实践

点击上方关注 “终端研发部

设为“星标”,和你一起掌握更多数据库知识

  作者 |  丶谦信

来源 |  urlify.cn/YBJryu

66套java从入门到精通实战课程分享

项目背景

  最近,做一个按优先级和时间先后排队的需求。用 Redis 的 sorted set 做排队队列。

  主要使用的 Redis 命令有, zadd, zcount, zscore, zrange 等。

  测试完毕后,发到线上,发现有大量接口请求返回超时熔断(超时时间为3s)。

  Error日志打印的异常堆栈为:

    redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool

    Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.ConnectException: Connection timed out (Connection timed out)

    Caused by: java.net.ConnectException: Connection timed out (Connection timed out)

  且有一个怪异的现象,只有写库的逻辑报错,即 zadd 操作。像 zadd, zcount, zscore 这些操作全部能正常执行。

  还有就是报错和正常执行交错持续。即假设每分钟有1000个 Redis 操作,其中900个正常,100个报错。而不是报错后,Redis 就不能正常使用了。

问题排查

1.连接池泄露?

  从上面的现象基本可以排除连接池泄露的可能,如果连接未被释放,那么一旦开始报错,后面的 Redis 请求基本上都会失败。而不是有90%都可正常执行。

  但 Jedis 客户端据说有高并发下连接池泄露的问题,所以为了排除一切可能,还是升级了 Jedis 版本,发布上线,发现没什么用。

2.硬件原因?

  排查 Redis 客户端服务器性能指标,CPU利用率10%,内存利用率75%,磁盘利用率10%,网络I/O上行 1.12M/s,下行 2.07M/s。接口单实例QPS均值300左右,峰值600左右。

  Redis 服务端连接总数徘徊在2000+,CPU利用率5.8%,内存使用率49%,QPS1500-2500。

  硬件指标似乎也没什么问题。

3.Redis参数配置问题?

JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal (200);        // 最大连接数
config.setMinIdle (5);           // 最小空闲连接数
config.setMaxIdle (50);          // 最大空闲连接数
config.setMaxWaitMillis (1000 * 1);    // 最长等待时间
config.setTestOnReturn (false);
config.setTestOnBorrow (false);
config.setTestWhileIdle (true);
config.setTimeBetweenEvictionRunsMillis (30 * 1000);
config.setNumTestsPerEvictionRun (50);

  基本上大部分公司的配置包括网上博客提供的配置其实都和上面差不多,看不出有什么问题。

  这里我尝试把最大连接数调整到500,发布到线上,并没什么卵用,报错数反而变多了。

4.连接数统计

  在 Redis Master 库上执行命令:client list。打印出当前所有连接到服务器的客户端IP,并过滤出当前服务的IP地址的连接。

  发现均未达到最大连接数,确实排除了连接泄露的可能。

5.最大连接数调优和压测

  既然连接远未打满,说明不需要设置那么大的连接数。而 Redis 服务端又是单线程读写。客户端创建过多连接,只会耗费资源,反而拖累性能。

     使用以上代码,在本机使用 JMeter 压测300个线程,连续请求30秒。

  首先把最大连接数设为500,成功率:99.61%

  请求成功:82004次,TP90耗时目测在50-80ms左右。

  请求失败322次,全部为请求服务器超时:socket read timeout,耗时2s后,由 Jedis 自行熔断。

  (这种情况造成数据不一致,实际上服务端已执行了命令,只是客户端读取返回结果超时)。

  再把最大连接数设为20,成功率:98.62%(有一定几率100%成功)

  请求成功:85788次,TP90耗时在10ms左右。

    请求失败:1200次,全部为等待客户端连接超时:Caused by: java.util.NoSuchElementException: Timeout waiting for idle object,熔断时间为1秒。

   再将最大连接数调整为50,成功率:100%

   请求成功:85788次, TP90耗时10ms。

   请求失败:0次。

  综上,Redis 服务端单线程读写,连接数太多并没卵用,反而会消耗更多资源。最大连接数配置太小,不能满足并发需求,线程会因为拿不到空闲连接而超时退出。

  在满足并发的前提下,maxTotal连接数越小越好。在300线程并发下,最大连接数设为50,可以稳定运行。

  基于以上结论,尝试调整 Redis 参数配置并发布上线,但以上实验只执行了 zadd 命令,仍未解决一个问题:为什么只有写库报错?

  果然,发布上线后,接口超时次数有所减少,响应时间有所提升,但仍有报错,没能解决此问题。

6.插曲 - Redis锁

  在优化此服务的同时,把同事使用的另一个 Redis 客户端一起优化了,结果同事的接口过了一天开始大面积报错,接口响应时间达到8个小时。

  排查发现,同事的接口仅使用 Redis 作为分布式锁。而这个 RedisLock 类是从其他服务拿过来直接用的,自旋时间设置过长,这个接口又是超高并发。

  最大连接数设为50后,锁资源竞争激烈,直接导致大部分线程自旋把连接池耗尽了。于是又紧急把最大连接池恢复到200,问题得以解决。

  由此可见,在分布式锁的场景下,配置不能完全参考读写 Redis 操作的配置。

7.排查服务端持久化

  在把客户端研究了好几遍之后,发现并没有什么可以优化的了,于是开始怀疑是服务端的问题。

  持久化是一直没研究过的问题。在查阅了网上的一些博客,发现持久化确实有可能阻塞读写IO的。

 

  “1) 对于没有持久化的方式,读写都在数据量达到800万的时候,性能下降几倍,此时正好是达到内存10G,Redis开始换出到磁盘的时候。并且从那以后再也没办法重新振作起来,性能比Mongodb还要差很多。

  2) 对于AOF持久化的方式,总体性能并不会比不带持久化方式差太多,都是在到了千万数据量,内存占满之后读的性能只有几百。

  3) 对于Dump持久化方式,读写性能波动都比较大,可能在那段时候正在Dump也有关系,并且在达到了1400万数据量之后,读写性能贴底了。在Dump的时候,不会进行换出,而且所有修改的数据还是创建的新页,内存占用比平时高不少,超过了15GB。而且Dump还会压缩,占用了大量的CPU。也就是说,在那个时候内存、磁盘和CPU的压力都接近极限,性能不差才怪。”  ---- 引用自lovecindywang 的博客园博客

  “内存越大,触发持久化的操作阻塞主线程的时间越长

  Redis是单线程的内存数据库,在redis需要执行耗时的操作时,会fork一个新进程来做,比如bgsave,bgrewriteaof。Fork新进程时,虽然可共享的数据内容不需要复制,但会复制之前进程空间的内存页表,这个复制是主线程来做的,会阻塞所有的读写操作,并且随着内存使用量越大耗时越长。例如:内存20G的redis,bgsave复制内存页表耗时约为750ms,redis主线程也会因为它阻塞750ms。”       ---- 引用自CSDN博客

  而我们的Redis实例总内存20G,内存使用了50%,keys数量达4000w。

  主从集群,从库不做持久化,主库使用RDB持久化。rdb的save参数是默认值。(这也恰好能解释通为什么写库报错,读库正常)

  且此 Redis 已使用了几年,里面可能存在大量的key已经不使用了,但未设置过期时间。  

  然而,像 Redis、MySQL 这种都是由数据中台负责,我们并无权查看服务端日志,这个事情也不好推动,中台会说客户端使用的有问题,建议调整参数。

  所以最佳解决方案可能是,重新申请 Redis 实例,逐步把项目中使用的 Redis 迁移到新实例,并注意设置过期时间。迁移完成后,把老的 Redis 实例废弃回收。

小结

  1)如果简单的在网上搜索,Could not get a resource from the pool , 基本都是些连接未释放的问题。

  然而很多原因可能导致 Jedis 报这个错,这条信息并不是异常堆栈的最顶层。

       2)Redis其实只适合作为缓存,而不是数据库或是存储。它的持久化方式适用于救救急啥的,不太适合当作一个普通功能来用。

  3)还是建议任何数据都设置过期时间,哪怕设1年呢。不然老的项目可能已经都废弃了,残留在 Redis 里的 key,其他人也不敢删。

       4)不要存放垃圾数据到 Redis 中,及时清理无用数据。业务下线了,就把相关数据清理掉。

BAT等大厂Java面试经验总结 想获取 Java大厂面试题学习资料扫下方二维码回复「BAT」就好了回复 【加群】获取github掘金交流群回复 【电子书】获取2020电子书教程回复 【C】获取全套C语言学习知识手册回复 【Java】获取java相关的视频教程和资料回复 【爬虫】获取SpringCloud相关多的学习资料回复 【Python】即可获得Python基础到进阶的学习教程回复 【idea破解】即可获得intellij idea相关的破解教程关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!



回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群
阅读更多
为什么HTTPS是安全的
因为BitMap,白白搭进去8台服务器...
《某厂内部SQL大全 》.PDF
字节跳动一面:i++ 是线程安全的吗?
大家好,欢迎加我微信,很高兴认识你!
在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!



如果喜欢就给个“在看”

以上是关于RPS和RFS网卡多队列性能调优实践的主要内容,如果未能解决你的问题,请参考以下文章

一文解决ethtool 原理介绍和解决网卡丢包排查思路

为什么使能RPS/RFS, 或者RSS/网卡多队列后,QPS反而下降?

技能篇:linux服务性能问题排查及jvm调优思路

ElasticSearch性能优化实践(JVM调优+ES调优)

线上Redis高并发性能调优实践

Java 应用性能调优实践