[转帖]CPU架构对redis的性能影响

cpu,架构,redis,性能,影响 · 浏览次数 : 0

小编点评

**Redis尾延迟优化方法** **1. 绑核** * 降低 Redis 的尾延迟可以通过绑核来实现。 * 绑核是指将 Redis 实例绑定到特定 CPU 核上。 * 这可以避免网络中断处理程序在不同核上来回调度执行,从而提升 Redis 的网络处理性能。 **2. NUMA架构的风险** * NUMA 架构可以避免网络中断处理程序在不同核上来回调度执行,但它也存在一些风险,例如: * 绑定错误:如果网络中断处理程序和 Redis 实例绑定到不同的 CPU核上,则会引发异常。 **3. 绑核的风险和解决方案** **方法 1:使用单 CPU 核绑定** * 每个 Redis 实例只能绑定到一个 CPU 核。 * 在给 Redis 实例绑定时,要确保并行绑定到同一个物理核的 2 个逻辑核上。 **方法 2:修改 Redis 的代码** * 修改 Redis 的代码,将子进程和后台线程绑定到不同的 CPU 核上。 * 这可以通过在 fork 创建子进程时设置 CPU 索引来实现。 **4. 代码示例** **方法 1:使用单 CPU 核绑定** ```c++ // 创建位图变量 cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(bind_cpu, &cpuset); // 使用 sched_setaffinity 将程序绑定到指定逻辑核上 sched_setaffinity(0, sizeof(cpuset), &cpuset); ``` **方法 2:修改 Redis 的代码** ```c++ // 创建位图变量 cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(3, &cpuset); // 使用 sched_setaffinity 将程序绑定到指定逻辑核上 sched_setaffinity(0, sizeof(cpuset), &cpuset); ``` **总结** 生成 RDB 和 AOF 日志重写的子进程时,可以使用不同的方法降低 Redis 的尾延迟,包括使用单 CPU 核绑定或修改 Redis 的代码。

正文


目录

    绑核的风险
    解决方案
作者:@dwtfukgv
本文为作者原创,转载请注明出处:https://www.cnblogs.com/dwtfukgv/p/15203960.html

CPU架构对redis的性能影响

主流CPU架构

一个CPU处理器中通常有多个运行核心,每一个运行核心称为一个物理核,每个物理核都可以运行应用程序。每个物理核都拥有私有的一级缓存(Level 1 cache,简称L1 cache),包括一级指令缓存和一级数据缓存,私有的二级缓存(Level 2 cache,简称L2 cache),以及不同物理核共享的三级缓存(Level 3 cache,简称L3 cache)。

现在主流的CPU处理器,每个物理核通常会运行两个超线程,也叫作逻辑核,同一个物理核的逻辑核共享使用L1和L2缓存。

在现在的主流服务器上,一个CPU处理器会有10到20多个物理核。一个服务器上通常会有多个CPU处理器(也叫作多CPU Socket),每个处理器都有自己的物理核,L1、L2和L3以及连接的内存,不同处理器之间通过总线进行连接。

整体如下图,图中有两个CPU处理器,每个处理器拥有两个物理核,每个物理都拥有两个逻辑核。不同的处理器之间通过总结进行通信。

image

可以看出L1缓存和L2缓存是每个物理核私有的,所以当数据或者指令保存在L1和L2缓存时,物理核访问它们的延迟不超过10ns,是纳秒级别,速度非常快。但是L1和L2缓存的大小受限于处理器的制造技术,一般来说只有KB级别的。如果L1和L2中没有缓存需要的数据,就需要访问内存,访问内存的延迟一般在百纳秒级别,这就是L1和L2延迟的10倍。所以不同的物理核还会共享一个三级缓存。L3缓存比较大,能达到几MB到几十MB,这样就能够缓存更多的数据。

从图中还可以看到,我把内存分成了CPU Socket1内存和CPU Socket1内存,内存应该是共享的,为什么这么分呢?是因为如果一个程序先在一个Socket上运行,并且数据都保存到了内存,然后被调度到另一个Socket上接着运行。此时程序再进行内存访问时就需要访问之前Socket上连接的内存,这种属于远端内存访问。和直接内存访问相比,远端内存访问会增加程序的延迟。虽然内存是共享的,但是访问也有差别,所以这里分成了两个部分内存,从物理上来看是一个内存。

在多CPU架构下,一个程序访问所在Socket的本地内存和访问远端内存的延迟并不一致,这个架构称为非统一内存访问架构(Non-Uniform Memory Access,NUMA架构)。

CPU多核对redis性能的影响

在一个CPU核上运行时,程序需要记录自身使用的软硬件资源信息,比如栈指针、CPU核的寄存器的内容等,这些都是运行时信息。为了提高执行速度,程序还会将访问最频繁的指令和数据缓存到L1和L2缓存上。但是在多核CPU场景下,一旦程序需要在一个新的CPU核上运行时,那么需要将运行时信息重新加载到新的CPU核上。而且,新的CPU核的L1、L2缓存也需要重新加载数据和指令,这会导致程序的运行时间增加。

对于redis来说,当上下文切换时,Redis主线程的运行时信息需要被重新加载到另一个CPU核上,而且,此时,另一个CPU核上的L1、L2缓存中,并没有Redis实例之前运行时频繁访问的指令和数据,所以,这些指令和数据都需要重新从L3缓存,甚至是内存中加载。这个重新加载的过程是需要花费一定时间的。而且,Redis实例需要等待这个重新加载的过程完成后,才能开始处理请求,所以,这也会导致一些请求的处理时间增加。

在CPU多核的情况下,redis实例如果被频繁调度到不同的CPU核上运行的话,那么请求的处理时间影响就更大了。每调度一次,一些请求就会受到运行时信息、指令和数据重新加载过程的影响,这就会导致某些请求的延迟明显高于其他请求。

为了避免Redis总是在不同CPU核上来回调度执行,可以把redis实例与CPU核进行绑定,让redis固定运行在一个CPU核上。可以使用taskset命令把一个程序绑定在一个核上运行。

  taskset -c 0 ./redis-server

其中参数-c后面需要加绑定的核编号。上面命令的意思就是将redis实例绑定在0号核上面。

在CPU多核的环境下,通过绑定Redis实例和CPU核,可以有效降低Redis的尾延迟。当然,绑核不仅对降低尾延迟有好处,同样也能降低平均延迟、提升吞吐率,进而提升Redis性能。

NUMA架构对redis性能的影响

为了提高redis的网络性能,把操作系统的网络中断处理程序和CPU核绑定。这个做法可以避免网络中断处理程序在不同核上来回调度执行,的确能有效提升Redis的网络处理性能。网络中断处理程序是要与redis进行数据交互的,所以需要知道redis实例是绑定在哪个核上了,因为这会关系到redis访问网络数据效率的高低。

下面的图介绍了redis和网络中断程序的数据交互过程:网络中断处理程序会先从网上硬件中读取数据,然后交数据写到操作系统内核缓冲区中,内核会通过epoll机制触发事件来通过redis,redis再把数据从内核缓存区拷贝到自己的内存缓冲区。

image

所以在CPU的NUMA架构下,当网络中断处理程序、redis实例分别和CPU核绑定后,就会有一个潜在的风险:如果网络中断处理程序和redis各自绑定的核不在同一个CPU Socket上,那么redis读取网络数据时就需要跨CPU Socket访问远端内存。所以最好把网络中断程处理程序和redis实例绑定同一个CPU Socket上,这样redis就能够直接从本地内存中读取网络数据了。

需要注意的是,在CPU的NUMA架构下,对CPU核的编号规则,并不是先把一个CPU Socket中的所有的逻辑核编完,再对下一个CPU Socket的逻辑核进行编码,而是先给每个CPU Socket中每个物理核的第一个逻辑核依次编号,再给每个物理核的第二个逻辑核依次编号。

假设有2个CPU Socket,每个Socket上有6个物理核,每个物理核又有2个逻辑核,总共24个逻辑核。可以执行lscpu命令,查看到这些核的编号:

  lscpu
   
  Architecture: x86_64
  ...
  NUMA node0 CPU(s): 0-5,12-17
  NUMA node1 CPU(s): 6-11,18-23
  ...

可以看到,NUMA node0的CPU核编号是0到5、12到17。其中,0到5是node0上的6个物理核中的第一个逻辑核的编号,12到17是相应物理核中的第二个逻辑核编号。NUMA node1的CPU核编号规则和node0一样。所以一定要注意编号方法,不要绑错了。

绑核的风险和解决方案

绑核的风险

redis除了主线程以外,还有用于RDB生成和AOF重写的子进程,还有一些异步线程来执行一些不需要实时的任务。当把redis绑到一个CPU逻辑核上时,就会导致子进程、后台线程和redis主线程竞争CPU资源,一旦子进程或后台线程占用CPU时,主线程就会被阻塞,导致Redis请求延迟增加。

解决方案

第一种解决方案是一个Redis实例对应绑一个物理核。在给Redis实例绑核时,不要把一个实例和一个逻辑核绑定,而要和一个物理核绑定,也就是说,把一个物理核的2个逻辑核都用上。还是以刚才的NUMA架构为例,NUMA node0的CPU核编号是0到5、12到17。其中,编号0和12、1和13、2和14等都是表示一个物理核的2个逻辑核。所以,在绑核时,使用属于同一个物理核的2个逻辑核进行绑核操作。使用以下命令将redis绑定到第一个物理核上面:

  taskset -c 0,12 ./redis-server

和只绑一个逻辑核相比,把Redis实例和物理核绑定,可以让主线程、子进程、后台线程共享使用2个逻辑核,可以在一定程度上缓解CPU资源竞争。但是,因为只用了2个逻辑核,它们相互之间的CPU竞争仍然还会存在。

第二种方案是修改redis的源码,把子进程和后台线程绑定到不同的CPU核上。首先先介绍一下通用的编程绑核方法。需要使用操作系统提供的一个数据结构cpu_set_t和3个函数CPU_ZERO、CPU_SET和sched_setaffinity:

  • cpu_set_t数据结构:是一个位图,每一位用来表示服务器上的一个CPU逻辑核。
  • 以cpu_set_t结构的位图为输入参数,把位图中所有的位设置为0。
  • CPU_SET函数:以CPU逻辑核编号和cpu_set_t位图为参数,把位图中和输入的逻辑核编号对应的位设置为1。
  • sched_setaffinity函数:以进程/线程ID号和cpu_set_t为参数。检查cpu_set_t中哪一位为1,就把输入的ID号所代表的进程/线程绑定在对应的逻辑核上。

知道了这些东西就容易实现绑核操作了:

  1. 创建一个cpu_set_t结构的位图变量。
  2. 使用CPU_ZEOR函数,把cpu_set_t结构的位图所有的位都置为0。
  3. 使用CPU_SET函数,把需要绑定的逻辑号编号在cpu_set_t结构的位图设置为1.
  4. 使用sched_setaffinity函数,把程序绑定在cpu_set_t结构位图中为1的逻辑核上。

代码如下:

  //线程函数
  void worker(int bind_cpu){
  cpu_set_t cpuset; //创建位图变量
  CPU_ZERO(&cpu_set); //位图变量所有位设置0
  CPU_SET(bind_cpu, &cpuset); //根据输入的bind_cpu编号,把位图对应为设置为1
  sched_setaffinity(0, sizeof(cpuset), &cpuset); //把程序绑定在cpu_set_t结构位图中为1的逻辑核
   
  //实际线程函数工作
  }
   
  int main(){
  pthread_t pthread1
  //把创建的pthread1绑在编号为3的逻辑核上
  pthread_create(&pthread1, NULL, (void *)worker, 3);
  }

对于redis来说,它是在bio.c文件中的bioProcessBackgroundJobs函数中创建了后台线程。bioProcessBackgroundJobs函数类似于刚刚的例子中的worker函数,在这个函数中实现绑核四步操作,就可以把后台线程绑到和主线程不同的核上了。

和给线程绑核类似,当使用fork创建子进程时,也可以把刚刚说的四步操作实现在fork后的子进程代码中,示例代码如下:

  int main(){
  //用fork创建一个子进程
  pid_t p = fork();
  if(p < 0){
  printf(" fork error");
  }
  //子进程代码部分
  else if(!p){
  cpu_set_t cpuset; //创建位图变量
  CPU_ZERO(&cpu_set); //位图变量所有位设置0
  CPU_SET(3, &cpuset); //把位图的第3位设置为1
  sched_setaffinity(0, sizeof(cpuset), &cpuset); //把程序绑定在3号逻辑核
  //实际子进程工作
  exit(0);
  }
  ...
  }

对于Redis来说,生成RDB和AOF日志重写的子进程分别是下面两个文件的函数中实现的。

  • rdb.c文件:rdbSaveBackground函数;
  • aof.c文件:rewriteAppendOnlyFileBackground函数。

这两个函数中都调用了fork创建子进程,所以,可以在子进程代码部分加上绑核的四步操作。使用源码优化方案,既可以实现Redis实例绑核,避免切换核带来的性能影响,还可以让子进程、后台线程和主线程不在同一个核上运行,避免了它们之间的CPU资源竞争。相比使用taskset绑核来说,这个方案可以进一步降低绑核的风险。

与[转帖]CPU架构对redis的性能影响相似的内容:

[转帖]CPU架构对redis的性能影响

https://www.cnblogs.com/dwtfukgv/p/15203960.html 目录 主流CPU架构 CPU多核对redis性能的影响 NUMA架构对redis性能的影响 绑核的风险和解决方案 绑核的风险 解决方案 作者:@dwtfukgv本文为作者原创,转载请注明出处:https

[转帖]CPU架构对redis的性能影响

目录 主流CPU架构 CPU多核对redis性能的影响 NUMA架构对redis性能的影响 绑核的风险和解决方案 绑核的风险 解决方案 作者:@dwtfukgv本文为作者原创,转载请注明出处:https://www.cnblogs.com/dwtfukgv/p/15203960.html CPU架构

[转帖]CPU结构对Redis性能的影响

https://wangkai.blog.csdn.net/article/details/111571446 文章目录 CPU架构多核架构多CPU架构CPU架构的影响 CPU多核对Redis的影响需求条件结果原因优化 CPU 的 NUMA 架构对 Redis 性能的影响Redis 实例和网络中断程

[转帖]CPU结构对Redis性能的影响

文章系转载,便于分类和归纳,源文地址:https://wangkai.blog.csdn.net/article/details/111571446 CPU的多核架构和多CPU架构都会影响到Redis的性能 CPU架构 多核架构 一个CPU处理器一般有多个运行核心,如何在Linux查看物理CPU个数

[转帖]CPU结构对Redis性能的影响

文章系转载,便于分类和归纳,源文地址:https://wangkai.blog.csdn.net/article/details/111571446 CPU的多核架构和多CPU架构都会影响到Redis的性能 CPU架构 多核架构 一个CPU处理器一般有多个运行核心,如何在Linux查看物理CPU个数

[转帖]CPU性能监控之三-----非Numa架构的进程绑定

CPU性能监控之三 非Numa架构的进程绑定 https://blog.51cto.com/hl914/1557740 上一篇重点在Numa架构下如果绑定,这篇就说说在非numa架构下常用的绑定吧。 使用taskset这个命令进行绑定,这个命令无法对内存进行限制,所以,如果有特殊需要,也可以使用Nu

[转帖]国产数字芯片厂商详细信息

http://www.ichyang.com/post/36770.html 国产数字芯片厂商详细信息 下面我们将从核心技术、主要产品、目标市场和应用方案等方面对这30家公司逐一展示。 国产芯片 中科龙芯 核心技术:MIPS授权架构的CPU及生态圈、跨指令兼容的二进制翻译技术。 主要产品:面向行业应

[转帖]linux网络常见概念

Linux用户态和内核态 为了减少有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不同的操作赋予不同的执行等级,就是所谓特权的概念。简单说就是有多大能力做多大的事,与系统相关的一些特别关键的操作必须由最高特权的程序来完成。Intel的X86架构的CPU提供了0到3四个特权级,数字

[转帖]linux网络常见概念

Linux用户态和内核态 为了减少有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不同的操作赋予不同的执行等级,就是所谓特权的概念。简单说就是有多大能力做多大的事,与系统相关的一些特别关键的操作必须由最高特权的程序来完成。Intel的X86架构的CPU提供了0到3四个特权级,数字

[转帖]CPU性能监控之一------CPU架构

CPU性能监控之一 CPU架构 https://blog.51cto.com/hl914/1557231 先说下CPU的缓存吧,都知道CPU的缓存是分为L1,L2和L3的,L1又分为数据缓存和指令缓存,每颗CPU核心都有自己的L1和L2,但L3是各核心共享的,一但涉及共享的东西,当然就有竞争咯。 S