redis调优:服务器numa架构和网卡中断处理的分配

随着业务发展,请求量越来越大,web可以水平扩。单线程的redis缓存一般最先成为业务的性能瓶颈,32核心的物理服务器不进行优化会有资源抢占和cpu资源分配不合理的问题。将导致大量的慢查询、甚至web到redis连接数耗尽,形成阻塞影响整个系统。
下面针对一台物理机上运行多个redis实例的情况做优化.


基础系统调优

调整内核内存

在高性能条件下,我们注意到由于内存分配导致的性能偶尔会出现问题。为避免Redis操作出现问题(官方文档),您应该禁用Transparent HugePages内核函数。在终端执行:

echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled

执行此命令后,需要重启redis服务。
将以下行添加到/etc/rc.local文件中,以便在服务器重新启动后禁用此功能:

if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
   echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
   echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag
fi

tcp连接队列的最大值

永久生效:

vim /etc/sysctl.conf
net.core.somaxconn=65535
sysctl -p

相关参数:

net.core.netdev_max_backlog = 1000

number of unprocessed input packets before kernel starts dropping them, default 1000

每个网络接口接收数据包的速率比内核处理这些包的速率快时,内核开始删除未处理的输入数据包的数量。

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

减少SWAP使用率

有关SWAP对绩效影响的详细信息,请参阅官方文档

vim /etc/sysctl.conf
vm.swappiness=1
vm.overcommit_memory=1
sysctl -p

文件描述符限制修改

sysctl -w fs.file-max=1024000
vim /etc/security/limits.conf
*       hard nofile 65535
*       soft nofile 65535

配置Redis服务器
如果可能,仅使用UNIX套接字。如果您的Redis服务器和客户端位于本地计算机中。这将至少减少30%的TCP连接的成本,因为Unix套接字工作而无需建立特别的连接,的开销getaddrinfo,bind,ntohs。可以从Redis源代码服务器(此处此处)获取有关这两种连接类型之间差异的信息


Redis绑定cpu实践

检查cpu的关系

  • 每一行是一个物理核心。一个物理核心超线程为两个逻辑核心。
# cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list|sort  |uniq|sort -t ',' -k2
0,16
1,17
2,18
3,19
4,20
5,21
6,22
7,23
8,24
9,25
10,26
11,27
12,28
13,29
14,30
15,31
  • 可以看到一共有两块cpu。偶数是cpu0,奇数是cpu1。
# lscpu
NUMA node0 CPU(s):     0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30
NUMA node1 CPU(s):     1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31

如何规划绑定

由于网卡对cpu0比较亲和,以及numa特性。 绑定主节点到cpu 0 , 从节点到cpu1.
已237服务器为例, 一共3主三从。
从上面找到:
cpu0的8组物理cpu核心为 0,16 2,18 4,20 6,22 8,24 10,26 12,28 14,30
cpu1的8组物理cpu核心为 1,17 3,19 5,21 7,23 9,25 11,27 13,29 15,31
redis bgsave也会占用1颗cpu,所以每个进程分配两颗cpu。

  • 分配
主1: 0,16,2,18
主2: 4,20,6,22
主3: 8,24,10,26
从1: 1,17,3,19
从2: 5,21,7,23
从3: 9,25,11,27

绑定命令 taskset使用说明

  • 指定进程运行在cpu0,1上
# taskset -pc 0,1 2726
pid 2726's current affinity list: 0,1
pid 2726's new affinity list: 1
  • 查看当前的绑定
# taskset -p 2726
pid 2726's current affinity mask: 0,1
  • 启动程序时绑定cpu
# taskset -c 0,1 ./redis_servr 

开始操作

  • 查看redis对应pid
# ps -eo cmd,pid|grep redis[0-9]
/opt/app/redis7000/src/redi  30927
/opt/app/redis7001/src/redi  35223
/opt/app/redis7002/src/redi  39438
/opt/app/redis7003/src/redi  43689
/opt/app/redis7004/src/redi  48007
/opt/app/redis7005/src/redi  52214
  • 查看redis主从信息
# /opt/app/redis7000/src/redis-cli -c  -h 172.16.66.237 -p 7000  cluster nodes|grep  66.237
264f18653f08e32bbd7ff2f5b20299909b110544 172.16.66.237:7001@17001 master - 0 1581402111000 2 connected 5461-7281
e099411bc06839e95a01fc52003ea67edb9e8ead 172.16.66.237:7003@17003 slave 833400af8c53f48cf989468c7a1874e392ff5cb6 0 1581402112000 15 connected
44c7ec07bc6417fa26a860c3f97a211b48f5c242 172.16.66.237:7002@17002 master - 0 1581402112273 3 connected 10923-12742
e87dcb1a862c52fdb3aee4205183222d7b0bda8f 172.16.66.237:7000@17000 myself,master - 0 1581402107000 1 connected 0-1819
1be51d1c98d88e717c73ee3539c73ce6b6a91e4a 172.16.66.237:7005@17005 slave d53e7d4eb39935aa667d7d23cd522affd08d762f 0 1581402110000 14 connected
bd75e9a1ea1968cb79ea27b9c4af59a5851bd6b8 172.16.66.237:7004@17004 slave 520cb889ea2c477021c4e3fac94b6092f6c9f327 0 1581402111000 13 connected
  • 绑定
# taskset -pc 0,16,2,18 30927
# taskset -pc 4,20,6,22 35223
# taskset -pc 8,24,10,26 39438
# taskset -pc 1,17,3,19 43689
# taskset -pc 5,21,7,23 48007
# taskset -pc 9,25,11,27 52214

架构和原理介绍

优化前先了解服务器硬件架构

SMP 架构

随着单核CPU的频率在制造工艺上的瓶颈,CPU制造商的发展方向也由纵向变为横向:从CPU频率转为每瓦性能。CPU也就从单核频率时代过渡到多核性能协调。

SMP(对称多处理结构):即CPU共享所有资源,例如总线、内存、IO等。

SMP 结构:一个物理CPU可以有多个物理Core,每个Core又可以有多个硬件线程。即:每个HT有一个独立的L1 cache,同一个Core下的HT共享L2 cache,同一个物理CPU下的多个core共享L3 cache。

NUMA 架构

在前面的FSB(前端系统总线)结构中,当CPU不断增长的情况下,共享的系统总线就会因为资源竞争(多核争抢总线资源以访问北桥上的内存)而出现扩展和性能问题。

在这样的背景下,基于SMP架构上的优化,设计出了NUMA(Non-Uniform Memory Access)—— 非均匀内存访问。

内存控制器芯片被集成到处理器内部,多个处理器通过QPI链路相连,DRAM也就有了远近之分。

CPU 多层Cache的性能差异是很巨大的,比如:L1的访问时长1ns,L2的时长3ns...跨node的访问会有几十甚至上百倍的性能损耗。

cpu中断问题

NUMA 架构下的中断问题

当两个NUMA节点处理中断时,CPU实例化的softnet_data以及驱动分配的sk_buffer都可能是跨node的,数据接收后对上层应用Redis来说,跨node访问的几率也大大提高,并且无法充分利用L2、L3 cache,增加了延时。

同时,由于Linux wake affinity 特性,如果两个进程频繁互动,调度系统会觉得它们很有可能共享同样的数据,把它们放到同一CPU核心或NUMA Node有助于提高缓存和内存的访问性能,所以当一个进程唤醒另一个的时候,被唤醒的进程可能会被放到相同的CPU core或者相同的NUMA节点上。此特性对中断唤醒进程时也起作用,在上一节所述的现象中,所有的网络中断都分配给CPU 0去处理,当中断处理完成时,由于wakeup affinity特性的作用,所唤醒的用户进程也被安排给CPU 0或其所在的numa节点上其他core。而当两个NUMA node处理中断时,这种调度特性有可能导致Redis进程在CPU core之间频繁迁移,造成性能损失。

综合上述,将中断都分配在同一NUMA Node中,中断处理函数和应用程序充分利用同NUMA下的L2、L3缓存、以及同node下的内存,结合调度系统的wake affinity特性,能够更进一步降低延迟。

cpu单核中断瓶颈导致的丢包问题:

cat /proc/net/softnet_stat

其中每行代表一个CPU,第一列是中断处理程序接收的帧数,第二列是由于超过 netdev_max_backlog 而丢弃的帧数。 第三列则是在 net_rx_action 函数中处理数据包超过 netdev_budget 指定数量或运行时间超过2个时间片的次数。在检查线上服务器之后,发现第一行CPU。硬中断的中断号及统计数据可以在/proc/interrupts中看到,对于多队列网卡,当系统启动并加载NIC设备驱动程序模块时,每个RXTX队列会被初始化分配一个唯一的中断向量号,它通知中断处理程序该中断来自哪个NIC队列。在默认情况下,所有队列的硬中断都由CPU 0处理,因此对应的软中断逻辑也会在CPU 0上处理,在服务器 TOP 的输出中,也可以观察到 %si 软中断部分,CPU 0的占比比其他core高出一截

临时调大netdev_max_backlog能否解决燃眉之急,事实证明,对于轻微丢包调大参数可以缓解丢包,但对于大量丢包则几乎不怎么管用,内核处理速度跟不上收包速度的问题还是客观存在,本质还是因为单核处理中断有瓶颈,即使不丢包,服务响应速度也会变慢。因此如果能同时使用多个CPU Core来处理中断,就能显著提高中断处理的效率,并且每个CPU都会实例化一个softnet_data对象,队列数也增加了。

网络中断处理亲缘性设置

分配方式

中断的亲缘性设置可以在 cat /proc/irq/${中断号}/smp_affinity 或 cat /proc/irq/${中断号}/smp_affinity_list 中确认,前者是16进制掩码形式,后者是以CPU Core序号形式。例如下图中,将16进制的400转换成2进制后,为 10000000000,“1”在第10位上,表示亲缘性是第10个CPU Core。

怎么分配合适

那为什么中断号只设置一个CPU Core呢?而不是为每一个中断号设置多个CPU Core平行处理。我们经过测试,发现当给中断设置了多个CPU Core后,它也仅能由设置的第一个CPU Core来处理,其他的CPU Core并不会参与中断处理,原因猜想是当CPU可以平行收包时,不同的核收取了同一个queue的数据包,但处理速度不一致,导致提交到IP层后的顺序也不一致,这就会产生乱序的问题,由同一个核来处理可以避免了乱序问题。

但是,当我们配置了多个Core处理中断后,发现Redis的慢查询数量有明显上升,甚至部分业务也受到了影响,慢查询增多直接导致可用性降低,因此方案仍需进一步优化(redis进程亲缘性设置)。

Redis进程亲缘性设置

测试(踩坑)过程:

如果某个CPU Core正在处理Redis的调用,执行到一半时产生了中断,那么CPU不得不停止当前的工作转而处理中断请求,中断期间Redis也无法转交给其他core继续运行,必须等处理完中断后才能继续运行。Redis本身定位就是高速缓存,线上的平均端到端响应时间小于1ms,如果频繁被中断,那么响应时间必然受到极大影响。容易想到,由最初的CPU 0单核处理中断,改进到多核处理中断,Redis进程被中断影响的几率增大了,因此我们需要对Redis进程也设置CPU亲缘性,使其与处理中断的Core互相错开,避免受到影响。

使用命令taskset可以为进程设置CPU亲缘性,操作十分简单,一句taskset -cp cpu-list pid即可完成绑定。经过一番压测,我们发现使用8个core处理中断时,流量直至打满双万兆网卡也不会出现丢包,因此决定将中断的亲缘性设置为物理机上前8个core,Redis进程的亲缘性设置为剩下的所有core。调整后,确实有明显的效果,慢查询数量大幅优化,但对比初始情况,仍然还是高了一些些,还有没有优化空间呢?



通过观察,我们发现一个有趣的现象,当只有CPU 0处理中断时,Redis进程更倾向于运行在CPU 0,以及CPU 0同一物理CPU下的其他核上。于是有了以下推测:我们设置的中断亲缘性,是直接选取了前8个核心,但这8个core却可能是来自两块物理CPU的,在/proc/cpuinfo中,通过字段processor和physical id 能确认这一点,那么响应慢是否和物理CPU有关呢?物理CPU又和NUMA架构关联,每个物理CPU对应一个NUMA node,那么接下来就要从NUMA角度进行分析。

相关命令

  • 查看网卡中断号
    cat /proc/interrupts|grep eth0
  • 查看numa架构中node0分配的cpu列表和cpu核心关系
    cat /sys/devices/system/node/node0/cpulist
    cat /proc/cpuinfo |grep 'core id'|uniq
  • 查看进程调用过程
    perf top
  • 查看网卡中断
    cat /proc/interrupts|grep p2p2|cut -f1 -d:|xargs -I vv cat /proc/irq/vv/smp_affinity_list|sort |uniq -c |sort -nk2
    mpstat -P ALL 1 1 显示中断
  • 第一列中断数量,第二列超过 netdev_max_backlog 而丢弃的帧数, 第三列则是在 net_rx_action 函数中处理数据包超过 netdev_budget 指定数量或运行时间超过2个时间片的次数
    cat /proc/net/softnet_stat

在不支持多列队网卡启用rps(具体参考软中断设置

启用CPU RPS

Redis是单线程应用程序。为确保redis与处理任何网络流量的CPU不在同一CPU上运行,强烈建议启用RPS。以下设置允许您将Redis服务器分发到服务器上的指定处理器核心。 <br/>rps_cpus文件使用逗号分隔的CPU位图。因此,要允许CPU处理接口上接收队列的中断,请将位图中的位置值设置为1.例如,要处理CPU 0,1,2和3的中断,请设置值rps_cpus到00001111(1 + 2 + 4 + 8),或f(15的十六进制值)。
要在CPU 0-1上启用RPS:
echo '3' > /sys/class/net/eth0/queues/rx-0/rps_cpus

'3'我们记录了上述规则1 + 2 = 3的值,前两个核心eth0 * - 替换为您的网络接口,可能是eth1
要将redis的CPU亲和性设置为CPU 2-8:
# config is set to write pid to /var/run/redis.pid
$ taskset -pc 2-8 `cat /var/run/redis.pid`
pid 8946's current affinity list: 0-8
pid 8946's new affinity list: 2-8

如果您有双核处理器,则值如下:
echo '1' > /sys/class/net/eth0/queues/rx-0/rps_cpus

taskset -pc 1 `cat /var/run/redis.pid`

以上是临时主机上库存编译提升性能的示例:
RPS状态 获得运营/秒 设置操作/秒
离 761.15 777.22
上 834 .45 859 .87

irqbalance 简单介绍

irqbalance根据系统中断负载的情况,自动迁移中断保持中断的平衡,同时会考虑到省电因素等等。 但是在实时系统中会导致中断自动漂移,对性能造成不稳定因素,在高性能的场合建议关闭。
深度剖析irqbalance原理:
http://blog.yufeng.info/archives/2422

关于网卡列队的总结:

事实上在一个有大量小数据包的系统上,irqbalance优化几乎没有效果,而且还使得cpu消耗分配的不均衡,导致机器性能得不到充分的利用,这个时候需要把它给结束掉。不管怎么说,在同等条件下强烈大家选用多队列的网卡,常见的有Intel的82575、82576,Boardcom的57711等。
多队列网卡是一种技术,最初是用来解决网络IO QoS 问题的,随着网络IO的带宽的不断提升,单核CPU不能完全处满足网卡的需求,通过多队列网卡驱动的支持,将各个队列通过中断绑定到不同的核上。其实用bonding网卡绑定在一定程度也可以做中断负载均衡,两个网卡中断号可以绑定在不同的cpu核心上。
由于RPS只是单纯把数据包均衡到不同的cpu,这个时候如果应用程序所在的cpu和软中断处理的cpu不是同一个,此时对于cpu cache的影响会很大,那么RFS确保应用程序处理的cpu跟软中断处理的cpu是同一个,这样就充分利用cpu的cache.

RPS/RFS主要是针对单队列网卡多CPU环境。虽然有这些虚拟队列的支撑,但是毕竟是软件模拟的。 强烈推荐用支持多队列的网卡。
多队列多重中断的网卡在使用了smp affinity之后也可以再使用该RFS RPS的功能,在这里他更像是个接收方的调解员,最大程度的提高cpu cache。


参考文档:

https://www.kancloud.cn/abc0012383/test/804976
https://www.itcodemonkey.com/article/3055.html
https://viblo.asia/p/redis-for-true-high-loads-bWrZnBkpZxw
http://www.simlinux.com/2017/02/28/net-softirq.html