Redis - CPU架构对Redis性能的影响

文章目录

引言

很多开发者对Redis和CPU的关系理解比较简单:Redis线程跑在CPU上,CPU越快Redis就越快。这种认知是片面的。现代服务器普遍采用多核CPU和NUMA架构,这些硬件特性会从底层深刻影响Redis的延迟表现和吞吐能力。如果在性能调优时忽略了CPU层面的因素,就可能错过一些关键的优化手段。

本文将从CPU多核架构和NUMA架构两个维度,分析它们对Redis性能的影响机制,并给出实际可操作的绑核优化方案。

主流CPU架构概览

物理核与缓存层次

一个CPU处理器包含多个物理核(Physical Core),每个物理核拥有:

  • 私有L1缓存:包括L1指令缓存和L1数据缓存,访问延迟不超过10纳秒
  • 私有L2缓存:同样为物理核私有,访问延迟在10纳秒级别
  • 共享L3缓存:同一个CPU Socket上的所有物理核共享,容量可达几MB到几十MB

L1和L2缓存虽然速度极快,但容量只有KB级别。当缓存未命中时,程序需要访问内存,延迟会跳到百纳秒级别------接近L1/L2访问延迟的10倍。L3缓存的存在就是为了在L1/L2未命中时提供一个中间层,尽量避免直接访问内存。

超线程与逻辑核

现代CPU的每个物理核通常运行两个超线程(Hyper-Threading),也叫逻辑核。同一物理核的两个逻辑核共享L1和L2缓存。主流服务器上,一个CPU处理器有10到20多个物理核。

多Socket与NUMA架构

为了提升处理能力,服务器通常配备多个CPU处理器(多CPU Socket)。每个Socket有自己的物理核、L1/L2/L3缓存,以及直连的本地内存。不同Socket之间通过总线互联。

这种架构下,应用程序访问本Socket直连的内存(本地内存访问)速度快,而访问其他Socket连接的内存(远端内存访问)则需要跨总线,延迟明显增加。由于不同Socket的内存访问延迟不一致,这种架构被称为非统一内存访问架构(NUMA,Non-Uniform Memory Access)

CPU多核对Redis性能的影响

上下文切换的代价

Redis主线程在某个CPU核上运行时,会把频繁访问的指令和数据缓存到该核的L1/L2中。一旦操作系统把Redis调度到另一个核上运行,就会发生context switch:

  1. 运行时信息(栈指针、寄存器值等)需要重新加载到新核
  2. 新核的L1/L2缓存中没有Redis之前的热点数据,需要从L3甚至内存重新加载
  3. Redis必须等待加载完成才能继续处理请求

如果Redis被频繁调度到不同核上,每次调度都会有一批请求受到缓存重加载的影响,导致这些请求的延迟明显高于正常水平。

实际案例:绑核降低尾延迟

一个真实的优化案例:在24核服务器上运行Redis实例,使用O(1)复杂度的String类型操作,关闭了RDB和AOF,没有bigkey------排除了所有常见的慢查询因素。但测试结果显示:

  • GET 99%尾延迟:504微秒(目标<300微秒)
  • PUT 99%尾延迟:1175微秒(目标<500微秒)

排查发现CPU的context switch次数异常偏高。使用taskset命令将Redis实例绑定到固定CPU核后:

bash 复制代码
taskset -c 0 ./redis-server

绑核后的测试结果:

  • GET 99%尾延迟:260微秒
  • PUT 99%尾延迟:482微秒

尾延迟直接降低了50%以上,达到了预期目标。绑核的本质是让Redis持续复用同一个核的L1/L2缓存,避免了缓存失效带来的性能抖动。

NUMA架构对Redis性能的影响

网络中断与Redis的跨Socket问题

在实际部署中,为了提升网络性能,运维人员经常会把网络中断处理程序绑定到特定CPU核上。网络中断程序从网卡读取数据后,写入内核维护的内存缓冲区,Redis再通过epoll机制从该缓冲区拷贝数据到自己的内存空间。

问题来了:如果网络中断程序绑在Socket 1的某个核上,而Redis实例绑在Socket 2上,那么网络数据存放在Socket 1的本地内存中。Redis读取网络数据时,就需要跨Socket访问------Socket 2通过总线向Socket 1发送内存访问命令,这属于远端内存访问。

实测数据表明,跨Socket的内存访问延迟比本地访问增加了约18%。

NUMA下的CPU编号陷阱

NUMA架构下CPU核的编号规则容易让人踩坑。编号并不是先把一个Socket的所有逻辑核编完再编下一个Socket,而是:

  1. 先给每个Socket中每个物理核的第一个逻辑核依次编号
  2. 再给每个Socket中每个物理核的第二个逻辑核依次编号

例如,2个Socket、每个Socket 6个物理核、每个物理核2个逻辑核(共24个逻辑核)的情况下,用lscpu查看:

复制代码
NUMA node0 CPU(s): 0-5, 12-17
NUMA node1 CPU(s): 6-11, 18-23

如果想当然地认为0-11都属于同一个Socket,把网络中断绑到核1、Redis绑到核7,实际上它们分属两个不同的Socket,仍然会产生跨Socket访问。

正确做法:把Redis实例和网络中断处理程序绑在同一个Socket的不同核上。

绑核的风险与解决方案

风险:子进程和后台线程的CPU竞争

Redis除了主线程,还有:

  • RDB生成和AOF重写的子进程
  • 4.0版本后的惰性删除后台线程

如果把Redis实例绑到单个逻辑核上,这些子进程和后台线程会与主线程竞争同一个核的CPU资源,反而导致请求延迟增加。

方案一:绑定物理核而非逻辑核

不要把Redis绑到单个逻辑核,而是绑到一个完整的物理核(即该物理核的两个逻辑核)。例如:

bash 复制代码
taskset -c 0,12 ./redis-server

这里核0和核12属于同一个物理核的两个逻辑核。这样主线程、子进程、后台线程可以共享使用两个逻辑核,在一定程度上缓解竞争。

方案二:修改源码实现精细绑核

通过编程方式,把子进程和后台线程绑到不同的核上。核心API:

c 复制代码
cpu_set_t cpuset;          // 位图,表示可用的逻辑核
CPU_ZERO(&cpuset);         // 清零位图
CPU_SET(bindCpu, &cpuset); // 设置目标核
sched_setaffinity(0, sizeof(cpuset), &cpuset); // 绑定当前进程/线程

对于Redis源码,具体的修改点:

  • 后台线程 :在bio.cbioProcessBackgroundJobs函数中加入绑核操作
  • RDB子进程 :在rdb.crdbSaveBackground函数的fork后子进程代码中加入绑核操作
  • AOF重写子进程 :在aof.crewriteAppendOnlyFileBackground函数的fork后子进程代码中加入绑核操作

这种方案可以让主线程独占一个核,子进程和后台线程使用其他核,彻底避免CPU竞争。

Redis 6.0已经原生支持了CPU核绑定的配置操作,可以通过配置文件直接指定主线程、后台线程、子进程分别使用哪些核。

总结

CPU架构对Redis性能的影响主要体现在两个层面:

  1. 多核场景下的context switch:Redis在不同核间频繁调度会导致L1/L2缓存失效,表现为尾延迟升高。解决方案是使用taskset绑核。

  2. NUMA架构下的远端内存访问:网络中断程序和Redis实例如果分属不同Socket,网络数据读取会产生跨Socket延迟。解决方案是确保它们绑在同一个Socket上。

绑核时需要注意CPU竞争问题,推荐绑定物理核而非单个逻辑核,或者通过源码修改实现主线程与子进程/后台线程的核隔离。在部署多实例Redis集群时,建议将实例均匀分布在不同Socket上,充分利用各Socket的L3缓存和本地内存资源。

相关推荐
杨_晨16 分钟前
LLM输出康熙部首冲突
数据库·python·mysql·ai
阿图灵19 分钟前
Agentic AI 架构入门(三):Agent 的七大组件与 PRAL 循环
人工智能·架构·llm·rag·ai agent·智能体·agentic ai
爱看报的猿21 分钟前
【金仓数据库征文】MySQL至金仓KES异构数据库平滑迁移与性能深度调优实战
数据库·数据仓库·mysql·金仓数据库征文
程序猿的学习35 分钟前
同一个 Key 写入两次,StarRocks 会留下什么?四种表模型讲明白了
数据库
YuePeng42 分钟前
Java 开发者的 Django Admin,终于来了
后端·架构·github
朱峥嵘(朱髯)1 小时前
数据库如何根据全表 NDV 估算子集的 NDV
数据库·算法
Nturmoils1 小时前
MongoDB迁移:从烟囱式部署走向 KES-AI 时代融合数据库架构
数据库
开开心心就好1 小时前
免费压缩软件WinRAR无弹窗、广告干扰
java·开发语言·前端·数据库·python·excel·winrar
深念Y1 小时前
hotspot-adb-research
linux·数据库·adb·emmc·随身wifi·移动终端
ERD Online1 小时前
我们怎么设计 good first issue:让第一个 PR 两小时内合入
数据库·git·后端·开源·issue