信创数据库服务器的 NUMA 与网卡中断绑核实践:以海光、鲲鹏平台为例

信创数据库服务器上,NUMA 绑核和网卡中断隔离是降低延迟抖动的关键。

原文标题:数据库信创服务器NUMA绑核策略

原文作者:牧羊人的方向

冷月清谈:

文章围绕信创数据库服务器在 NUMA 架构下的性能调优展开,重点分析海光、鲲鹏等平台在 NUMA 节点划分、跨节点访问延迟、超线程特性上的差异,并说明数据库进程和网卡中断为什么需要进行绑核。核心思路是让数据库进程、共享内存、网卡中断尽量保持在同一 Socket 或同一 NUMA 亲和范围内,减少跨节点内存访问、缓存失效、上下文切换和软中断抢占。

文中进一步拆解了 Linux 网卡收包流程,包括硬件队列、RSS、多队列、MSI-X、硬中断、软中断、NAPI 轮询和协议栈处理,解释了为什么高并发 OLTP 小包场景下,软中断会成为数据库延迟抖动的重要来源。

实践部分以双路鲲鹏 920 服务器为例,给出数据库实例按 Socket 隔离、网卡中断绑定到专用核、关闭 irqbalance、调整网卡队列数、扩大 Ring Buffer、关闭 NUMA 自动平衡等操作建议。文章也提醒,绑核会带来 CPU 使用上限,若主库算力不足,不建议简单跨 Socket 扩核,应优先优化 SQL、索引、并发模型或进行横向拆分。

怜星夜思:

1、数据库绑核以后,如果 CPU 打满了,是该放开绑核,还是优先拆库、优化 SQL?
2、网卡中断一定要和数据库业务核隔离吗?小流量场景有没有必要这么细?
3、网卡队列数是不是越多越好?为什么文章建议队列数匹配预留中断核?
4、关闭 irqbalance 会不会带来副作用?生产环境应该怎么控制风险?

原文内容

在信创基础设施规模化落地的背景下,海光、鲲鹏等架构服务器已成为信创数据库部署的主流硬件底座。这两类CPU均采用NUMA(非一致性内存访问)架构,CPU访问本地节点内存的延迟与带宽远优于远端节点,如果沿用默认的系统调度策略,数据库进程与网卡中断会在不同NUMA节点间漂移,引发缓存失效、跨节点内存开销、资源争抢等问题,最终导致数据库性能下降、延迟抖动加剧。本文将介绍在数据库信创服务器下,数据库进程该如何绑定NUMA节点、网卡中断是否需要绑核又该如何绑核

1、NUMA绑核分析
1.1 NUMA基础架构

NUMA架构将整台服务器划分为多个独立的NUMA节点,每个节点拥有专属的CPU核、内存控制器与本地内存,节点间通过片间互联总线通信。CPU访问同节点本地内存的延迟最低、带宽最高;访问跨节点内存需经过互联总线,延迟显著升高、带宽下降。

海光与鲲鹏平台的NUMA架构存在明显差异,绑核策略需针对性适配:

  • 海光平台:单颗CPU集成4个DIE,每个DIE对应1个NUMA节点,双路服务器共8个NUMA节点,单节点通常包含8~16个CPU核。同Socket内跨DIE访问开销较低,跨Socket访问开销显著升高,NUMA颗粒度更细。
  • 鲲鹏平台:单颗CPU包含24个NUMA节点,双路服务器共48个节点,全系无超线程技术,所有核均为物理核。鲲鹏平台跨NUMA节点的内存访问延迟高于x86平台,跨Socket访问延迟可达同节点的2.5~3倍,NUMA亲和性优先级更高。

关于NUMA相关知识,参考“”。

1.2 数据库进程绑核

数据库属于典型的内存密集型+计算密集型负载,绑核的核心作用是从硬件层面消除NUMA架构带来的性能损耗。

  • 消除跨NUMA内存访问开销:数据库的共享内存(缓冲池、共享池、日志缓冲区等)容量通常达到几十GB甚至上百GB,如果进程在不同NUMA节点间漂移,会频繁触发远端内存访问。绑核后进程与内存固定在对应NUMA节点内,可将内存访问延迟降低50%以上。
  • 降低CPU调度与缓存失效开销:默认内核调度会将线程在不同CPU核间迁移,导致CPU的L1、L2、L3级缓存数据频繁失效,需要重新从内存加载。数据库线程固定在指定CPU核运行后,可充分利用缓存预热结果,指令与数据命中率大幅提升。
  • 减少高并发下的上下文切换:高并发场景下数据库线程数量众多,无约束的调度会导致大量上下文切换,占用有效算力。绑核后线程调度范围被限定在指定CPU核内,可减少60%以上的上下文切换,提升CPU有效利用率。

对于不同服务器类型,海光支持SMT(超线程),两个逻辑线程共享执行单元,如果不绑核,两个数据库线程可能挤在同一物理核上,互相阻塞;鲲鹏无超线程,所有核均为物理核,但单颗CPU内包含多个DIE(呈现为多个NUMA节点),不绑核同样面临跨DIE访问。

1.3 网卡中断绑核

服务器的PCIe网卡物理挂载在特定CPU的PCIe控制器上,对应归属一个固定的NUMA节点,网卡的DMA内存、硬件中断默认优先分配在所属节点。默认系统调度下,中断处理可以调度到任意CPU核,可能会引发一系列性能问题。

  • 避免中断漂移,提升CPU缓存命中率:中断频繁在不同CPU核间迁移,会导致网络协议栈、驱动程序的缓存数据频繁失效,处理延迟升高。将中断固定在指定CPU后,缓存可持续预热,网络包处理效率提升20%以上。
  • 保障NUMA亲和性,降低内存访问延迟:若中断调度到远端NUMA节点的CPU核处理,数据包读取、协议栈解析都会涉及跨节点内存访问,延迟大幅升高。将中断绑定到网卡所属NUMA节点的CPU核,可实现“网卡DMA写入→中断处理→协议栈解析”全流程本地内存访问,性能最优。
  • 多队列并行扩展,突破单核中断瓶颈:单个CPU核的中断处理能力存在上限,万兆以上网卡需开启多队列技术,每个队列对应独立中断,绑定不同核实现并行处理,吞吐量随核数近似线性提升。
  • 业务与中断资源隔离,消除性能抖动:高流量场景下,网卡软中断会占用大量CPU算力。将中断固定在专用CPU核,与数据库业务CPU核完全隔离,可避免中断处理抢占数据库算力,消除业务延迟抖动,提升性能稳定性。
2、网卡中断处理流程
2.1 网卡核心概念

1)网卡硬件队列(RX/TX Queue)

网卡硬件队列是网卡芯片上的硬件缓存队列,与内存中的环形缓冲区(Ring Buffer)一一对应,是数据在硬件与内核之间的中转站。

  • 接收队列(RX Queue):负责存放网卡收到的、等待内核处理的数据包;
  • 发送队列(TX Queue):负责存放内核提交的、等待网卡发送的数据包。

每个队列都对应一块预先分配好的内存(Ring Buffer),通过DMA(直接内存访问) 技术映射给网卡:网卡可以不经过CPU,直接把数据包写入内存的Ring Buffer,也可以直接从Ring Buffer读取数据发送,极大降低CPU占用。

早期网卡只有1个RX队列,所有流量都由这一个队列处理,对应1个中断,只能由单个CPU处理。随着网卡带宽从千兆升级到万兆以上,单核的中断处理能力已经成为瓶颈(单核极限约100万pps小包)。因此诞生了多队列网卡(配合MSI-X中断技术),多队列网卡支持多个独立的 RX/TX 队列,每个队列对应一个独立的硬件中断号;通过RSS(接收端缩放)技术,网卡基于数据包的源IP、目的IP、源端口、目的端口、协议号(五元组)做哈希计算,将同一条流的数据包固定分发到同一个RX队列,实现流量的并行分发;另外不同队列的中断可以绑定到不同CPU,实现多核并行处理,吞吐量随CPU核数近似线性提升。

2)中断的两个层级:硬中断vs软中断

Linux将中断处理拆分为硬中断和软中断,以平衡 “快速响应硬件” 和 “充分处理数据” 的需求。硬中断是网卡硬件主动发送信号给CPU、软中断则是内核软件触发。

3)NAPI机制

NAPI是Linux内核针对高流量网络场景的优化机制,核心思想是“中断触发+轮询处理”,解决高并发下“中断风暴”导致的CPU开销过高问题。

  • 低流量时:正常走中断模式,每个数据包触发一次中断,响应及时;
  • 高流量时:硬中断触发后,暂时关闭该队列的硬件中断,内核通过轮询(poll)的方式批量处理队列里的数据包,处理完或达到处理上限后,再重新开启中断;

这种机制在高流量下可以大幅减少中断次数,降低上下文切换开销,提升吞吐量。

2.2 网卡收包处理流程

网卡接收流程是中断、队列交互最复杂的链路,按数据包流向分为5个阶段。

2.2.1 阶段1:物理层接收+DMA写入硬件队列
  1. 数据包通过光纤/网线到达网卡物理接口,PHY芯片完成光电转换、物理层解码;MAC层完成帧校验、MAC地址过滤,丢弃错误帧、非本机帧。
  2. RSS队列分发:网卡硬件根据数据包的五元组(源IP、目的IP、源端口、目的端口、协议号)计算哈希值,选择对应的RX硬件队列(保证同一条TCP流始终进入同一个队列,避免乱序)。
  3. DMA直接内存写入:网卡不经过CPU,直接通过DMA总线,将完整的数据包写入该RX队列对应的内存Ring Buffer中(Ring Buffer是内核预先分配、与网卡映射的连续内存块,以描述符链表形式组织)。
  4. 中断触发准备:数据包写入完成后,网卡向CPU发送该队列对应的MSI-X硬件中断信号,通知CPU“有新数据包到达,请处理”。
2.2.2 阶段2:硬中断响应
  1. CPU收到中断信号后,立即暂停当前正在执行的任务,保存寄存器上下文,跳转到该中断号对应的中断处理函数(ISR)。
  2. 硬中断处理函数仅执行3个简单的操作:
    • 向网卡寄存器写入应答,告知硬件“中断已收到”;
    • 禁用该RX队列的硬件中断(防止后续数据包持续触发中断,引发中断风暴,为NAPI轮询做准备);
    • 标记该队列的NAPI状态为“待调度”,触发内核NET_RX_SOFTIRQ(网络接收软中断)。
  3. 硬中断执行完毕,CPU恢复之前的上下文,继续执行被打断的任务。
2.2.3 阶段3:软中断处理(NAPI 轮询)

软中断处理会占用大量CPU算力。

  1. 软中断调度触发:硬中断返回后,内核会检查软中断待处理标记;如果当前CPU有未处理的网络软中断,会立即进入软中断处理流程;如果当前CPU繁忙,则由对应核的ksoftirqd/n内核线程(n为CPU号)调度处理。
  2. NAPI poll 轮询:调用该RX队列驱动的poll函数,开始批量从Ring Buffer中读取数据包:从Ring Buffer的头部开始,逐个取出数据包描述符,构建内核网络结构体sk_buff(简称skb,Linux内核中网络数据包的通用载体);每处理完一个描述符,就释放Ring Buffer中对应的内存位置,更新队列指针,供网卡后续写入新数据包;
  3. 轮询退出条件(满足任一即退出本次poll):Ring Buffer中的数据包全部处理完毕,队列为空;达到单次软中断的处理预算(默认netdev_budget=64个包,或2ms时间上限),避免长时间占用 CPU。
  4. 退出后处理:如果队列已空:重新开启该RX队列的硬件中断,退出NAPI模式,等待下一次硬中断触发;如果还有未处理数据包:保持中断关闭,继续标记软中断待处理,下一次调度时继续轮询。

数据库OLTP场景多为小包(SQL请求通常几百字节),软中断处理的占比会非常高。将网卡中断绑定到固定CPU,本质是固定软中断的处理CPU,提升CPU缓存命中率,同时避免抢占数据库业务CPU。

2.2.4 阶段4:内核协议栈处理

sk_buff离开驱动层后,向上进入内核协议栈,逐层解析处理:

  1. 链路层:校验以太网头,处理VLAN标签,判断帧类型(IP/ARP等),对应转发到上层;
  2. 网络层(IP层):解析IP头,校验IP地址,经过iptables/netfilter钩子,判断数据包是本机接收还是转发;
  3. 传输层(TCP/UDP):解析端口号,校验TCP/UDP头,处理序列号、滑动窗口、拥塞控制等TCP逻辑;
  4. socket匹配:根据“源IP+目的IP+源端口+目的端口+协议”五元组,查找对应的内核socket对象。
2.2.5 阶段5:交付用户态应用
  1. 数据包被放入对应socket的接收缓冲区(Receive Buffer)中,排队等待应用读取;
  2. 内核唤醒阻塞在该socket上的用户态进程(如数据库的监听进程、工作线程);
  3. 数据库进程通过read()/recv()等系统调用,将数据从内核态socket缓冲区拷贝到用户态内存中,完成一次完整的数据包接收。
2.3 网卡相关调优

1)关闭irqbalance

在软中断(SoftIRQ)阶段,CPU会消耗大量算力处理协议栈。irqbalance会周期性地把硬件中断(Hard IRQ)迁移到不同的CPU核。中断迁移导致网卡驱动上下文、RSS表、软中断状态在不同CPU的L1/L2 Cache之间频繁失效。因此关闭 irqbalance,手动将MSI-X中断(smp_affinity)绑定在特定的NUMA节点上。

2)中断处理核和数据库业务核分开

在高并发访问下,软中断(si%)会占用多个CPU。如果和业务同核,软中断会频繁抢占数据库线程的CPU时间片,导致数据库P99延迟剧烈抖动。此时需要将中断绑定到专用CPU,数据库绑定到其他CPU。

3)调整Ring Buffer大小

如果RX Ring Buffer太小(如默认256),在突发流量(Microburst)时,网卡DMA来不及写入,或者CPU软中断poll来不及取走,就会导致Ring Buffer溢出(Drop),网卡直接丢包。

4)网卡队列数量与NUMA节点核数不匹配

网卡开启的RX队列总数,超过了网卡所属NUMA节点的总核数。网卡默认配置将队列数设为整机CPU总核数,但网卡仅挂载在单个NUMA节点上。比如海光双路服务器共8个NUMA节点、64核,网卡默认开64个队列,但网卡仅属于NUMA0节点(仅8核),数量严重不匹配。这种配置上的不匹配有可能会导致数据库性能上TPS/QPS上不去但网络延迟大、偶发重传或丢包连接超时、CPU占用不均等问题。

  • 单核多队列,处理效率骤降:多个队列的中断只能挤在少数核上,单个核要处理多个队列的NAPI轮询,上下文切换、缓存失效大幅增加,软中断处理不及时,严重时导致Ring Buffer溢出丢包。
  • RSS哈希效率下降:过多队列会导致五元组哈希碰撞概率升高,同一条流的数据包可能被分发到不同队列,引发TCP乱序,进一步降低业务性能。

优化建议是设置合理的队列数:队列数=所属NUMA节点预留的中断核数,建议占单节点核数的10%~25%。

3、信创服务器绑核策略

以鲲鹏双路鲲鹏920共128C为例,共4个NUMA节点(Node 0 ~ Node 3),单节点32核。单台服务器部署1主1备两个实例。Socket归属:

  • Socket 0(CPU0):包含Node 0(CPU 0~31)、Node 1(CPU 32~63),共64核
  • Socket 1(CPU1):包含Node 2(CPU 64~95)、Node 3(CPU 96~127),共64核

由于鲲鹏跨Socket内存访问延迟是同节点的2.5~3倍,远高于x86平台,因此所有进程、中断、内存分配必须严格控制在所属Socket内,严禁跨Socket绑定;1主1备两个数据库实例分属不同Socket,完全独占CPU、缓存、内存控制器、互联总线资源;网卡中断与数据库业务CPU无重叠,避免中断处理抢占数据库算力,消除业务延迟抖动;网卡队列数=预留中断CPU核数,且全部属于网卡所在NUMA节点,杜绝队列和CPU不匹配问题。

基于以上原则,单网卡预留4核做中断处理,每NUMA节点预留1核做系统基础服务(内核线程、监控、SSH 等),CPU核划分如下:

3.1 绑核实施策略

1)查看NUMA节点分布

#numactl --hardware

2)数据库进程绑核操作

#数据库实例 A(Socket 0,Node 0+Node 1)
numactl -C 5-31,33-63 --membind=0,1 /path/to/db1

#数据库实例 B(Socket 1,Node 2+Node 3)
numactl -C 69-95,97-127 –membind=2,3 /path/to/db2

#验证内存NUMA分布,查看numa_miss是否接近0(接近0表示本地内存命中率高) 
numastat -p <数据库PID>

3)网卡中断绑核

#关闭自动中断调度irqbalance服务
systemctl stop irqbalance
systemctl disable irqbalance

#获取网卡中断号
#查看eth0的RX队列中断号 
grep eth0 /proc/interrupts
#查看eth1的RX队列中断号 
grep eth1 /proc/interrupts

#执行脚本进行批量绑核

eth0的4个中断,绑Node0(CPU 1-4)

bash bind_irq.sh eth0 1 4   #绑定4个队列到CPU 1,2,3,4

eth1同理,绑定到Node2(CPU 65-68)

bash bind_irq.sh eth1 65 4  #绑定4个队列到CPU 65,66,67,68

查看中断对应的亲和性列表,确认CPU号是否正确 

cat /proc/irq/xx/smp_affinity_list

bind_irq.sh如下,建议将网卡中断绑核操作配置到开机自启动中,这样服务器重启后也能自动绑核。

#!/bin/bash
# 用法: bash bind_irq.sh <网卡名> <起始CPU> <核数>
NIC=$1; START_CPU=$2; NUM_CPUS=$3
IRQS=($(grep "${NIC}" /proc/interrupts | awk '{print $1}' | sed 's/://g'))

for i in $(seq 0 $((${#IRQS[@]} - 1))); do
    IRQ=${IRQS[$i]}
    TARGET_CPU=$(( START_CPU + (i % NUM_CPUS) ))
    # 使用 python 计算 2 的 TARGET_CPU 次方的十六进制,避免 bc 大数溢出
    MASK=$(python3 -c “print(hex(1 << $TARGET_CPU)[2:])”)
    echo $MASK > /proc/irq/$IRQ/smp_affinity
    echo “IRQ $IRQ -> CPU $TARGET_CPU”
done

注:本方案无需开启 RPS/RFS。硬件多队列已足够承载数据库流量,且中断已固定CPU核,RPS 反而会增加跨核调度开销,保持默认关闭即可。

4)网卡队列配置

网卡队列数和网卡中断绑核数一一对应,避免多队列争抢单核。另外鲲鹏平台数据库场景不建议设置过多队列(超过8核收益会骤降),过多队列可能会导致RSS哈希碰撞率上升,TCP流乱序,反而会降低性能。

#查看网卡当前队列配置
ethtool -l eth0
ethtool -l eth1

#设置combined队列数(RX/TX共用队列,对应中断数)

eth0设置4个队列,匹配Node0的4个中断核

ethtool -L eth0 combined 4

eth1设置4个队列,匹配Node2的4个中断核

ethtool -L eth1 combined 4

#调大Ring Buffer(防止突发流量丢包,数据库OLTP小包场景推荐)
ethtool -G eth0 rx 4096 tx 4096
ethtool -G eth1 rx 4096 tx 4096

5)其它OS层测参数优化

# 关闭NUMA自动平衡,避免内核自动迁移进程/内存打乱手动绑定
kernel.numa_balancing = 0

开启NUMA节点内存回收,优先使用本地内存

vm.zone_reclaim_mode = 1

降低swap使用倾向,数据库场景尽量不使用交换分区

vm.swappiness = 0

TCP缓冲区优化,适配网卡大带宽

net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

基于以上绑核和OS层的配置调优,可提升数据库TPS、降低事务延迟、提升网卡带宽利用率,降低数据库性能抖动。实际效果以压测结果为准。

3.2 绑核策略下CPU上限

在上述物理绑核的情况下,数据库主进程在物理上无法使用超过其绑定范围(即Chip 1的64个核)的CPU资源。但如果业务高并发访问的时候数据库主进程所需要的CPU超过64C时,将会出现什么情况?

  • TPS/QPS达到上限:数据库处理能力不再随并发增加而提升,新增请求会进入等待队列;
  • 事务延迟增加:平均延迟、P99延迟显著上升,高并发下可能会出现大量连接超时、事务堆积,甚至引发数据库连接池打满、业务雪崩;
  • 运行队列堆积:系统load average持续高于物理核数(64),大量线程处于等待CPU调度的状态,上下文切换次数呈指数级上升,调度开销进一步挤占有效算力。

此场景下所有计算仍在Socket 0内完成,内存访问均为本地节点,NUMA亲和性保持完好,不会出现跨节点的内存延迟问题,性能瓶颈是因为CPU不足导致。

如果放开绑核限制,让主库同时使用Socket 0和Socket 1的CPU,有可能会出现加CPU核数反而性能下降的现象。在鲲鹏 920的NUMA架构中,跨Socket的内存访问延迟是同节点2.5~3倍,且片间互联总线带宽远低于本地内存控制器。

  • 内存访问延迟飙升:数据库的共享内存(缓冲池、共享池、日志缓冲区等)容量通常几十GB到上百GB,初始分配时集中在Socket 0的本地内存;当线程调度到Socket 1的CPU上时,所有对共享内存的访问都要跨片间总线,内存延迟从100ns级上升到300ns 以上,会拉低指令执行效率;
  • 内存带宽瓶颈:单Socket拥有独立的内存控制器和通道,本地内存带宽远高于片间总线带宽;大量跨节点内存访问会打满互联总线,不仅拖慢主库自身性能,还会挤占同服务器其他进程的跨节点访问带宽。

根据鲲鹏服务器的实测数据,对于计算密集型的业务,跨Socket扩展CPU,性能有小幅提升,但提升幅度远低于核数增长比例。对于内存密集型场景(如缓冲池访问、全表扫描),跨节点扩展CPU可能会出现性能不升反降,同时延迟抖动会大幅加剧。另外,在高并发下尤为明显,并发越高,跨节点线程调度越频繁,CPU各级缓存失效越严重,性能损失比例越大。其它信创服务器如海光跨Socket扩展CPU,同样会出现性能收益递减,在数据库场景不推荐作为长期方案。

当主库CPU出现算力不足时,如果服务器层面已经不能再进行扩展,只能通过降低业务并发访问、优化慢SQL和增加索引的方式进行优化,甚至需要进行横向的扩容,将业务库进行拆分到多台服务器,以分散单台服务器的CPU压力。

参考资料:

从运维角度说,关闭 irqbalance 本身不是问题,问题是有没有配套自动化。比如 systemd 启动脚本、网卡队列检查、smp_affinity 校验、异常回滚。如果靠人肉执行,迟早有一天重启后忘绑。

1 个赞

针对“网卡队列数是不是越多越好”:不是。队列多只是提供并行处理能力,但前提是有足够 CPU 核去接。队列数远大于中断核数时,多个队列挤在同一个核上,NAPI poll、缓存切换、软中断调度都会变重。

1 个赞

我回答下“CPU 打满后要不要放开绑核”这个问题:如果是鲲鹏这种跨 Socket 延迟很高的机器,我更倾向于先不放开。数据库很多时候不是单纯缺核,而是缺低延迟内存访问。跨 Socket 以后,看起来 CPU 多了,但缓存命中、内存访问、互联总线都可能拖后腿,P99 可能更难看。

2 个赞

针对“绑核后 CPU 不够用怎么办”:先看瓶颈是不是真在 CPU。慢 SQL、索引缺失、连接池过大、热点行锁,这些都会表现成 CPU 高或者 load 高。直接放开绑核有点像车胎漏气然后换发动机,不一定对症。

3 个赞

文章里说队列数要匹配预留中断核,这点我赞同。RSS 多队列的收益来自“队列到 CPU”的清晰映射,而不是把队列开满。尤其 NUMA 机器上,队列开到整机核数,但网卡只挂在一个 NUMA 节点,基本就是给自己挖坑。

2 个赞

回答“有没有必要这么细”:先监控再动手。看 /proc/interrupts、mpstat 里的 si%、网卡 drop、重传、P99 延迟。如果这些指标都很平稳,那就别急着关 irqbalance、改队列。调优不是做法事。

2 个赞