CPU架构对redis的性能影响

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处理器,每个处理器拥有两个物理核,每个物理都拥有两个逻辑核。不同的处理器之间通过总结进行通信。

CPU架构对redis的性能影响

可以看出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再把数据从内核缓存区拷贝到自己的内存缓冲区。

CPU架构对redis的性能影响

所以在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绑核来说,这个方案可以进一步降低绑核的风险。

Original: https://www.cnblogs.com/dwtfukgv/p/15203960.html
Author: dwtfukgv
Title: CPU架构对redis的性能影响

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/529344/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • PyTorch 介绍 | DATSETS & DATALOADERS

    用于处理数据样本的代码可能会变得凌乱且难以维护;理想情况下,我们希望数据集代码和模型训练代码解耦(分离),以获得更好的可读性和模块性。PyTorch提供了两个data primit…

    Linux 2023年6月16日
    0101
  • 同城双活-流量分流

    引言 现阶段,在同城带宽时延问题没有经过大规模的生产实践、验证的情况下,我们只导入”白名单或1%”的小比例请求流量,进入双活环境,确保环境有效的(活的),同…

    Linux 2023年6月14日
    0100
  • Redis和Memcache

    redis 和memcached都支持集群 Redis支持的数据类型要丰富得多,Redis不仅仅支持简单的k/v类型的数据,同时还提供String,List,Set,Hash,So…

    Linux 2023年5月28日
    093
  • MySQL的主从复制+双主模式

    部署环境: MySQL master 192.168.40.21 MySQL slave 192.168.40.22 思路: I/O线程是对主MySQL上二进制日志文件进行读取,读…

    Linux 2023年6月8日
    0108
  • 软件测试基础理论(2)

    一, 为什么要进行软件测试 &#x4E3A;&#x4E86;&#x901A;&#x8FC7;&#x8F6F;&#x4EF6;&amp…

    Linux 2023年6月7日
    0114
  • 学习一下 Spring Security

    一、Spring Security 1、什么是 Spring Security? (1)基本认识Spring Security 是基于 Spring 框架,用于解决 Web 应用安…

    Linux 2023年6月11日
    095
  • scp 远程安全复制文件

    scp是 secure copy 的缩写,相当于 cp命令 + SSH。它的底层是 SSH 协议,默认端口是22,相当于先使用 ssh命令登录远程主机,然后再执行拷贝操作。 scp…

    Linux 2023年6月7日
    098
  • ASP.NET Core 2.2 : 二十三. 深入聊一聊配置的内部处理机制

    上一章介绍了配置的多种数据源被注册、加载和获取的过程,本节看一下这个过程系统是如何实现的。(ASP.NET Core 系列目录) 一、数据源的注册 在上一节介绍的数据源设置中,ap…

    Linux 2023年6月7日
    0144
  • 保姆教程系列三、Nacos Config–服务配置

    前言: 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 上篇我们介绍到 保姆教程系列二、Nacos实现注册中心 配置中心原理 一、 服务配置中心介绍 首先我们来看一下,微…

    Linux 2023年6月14日
    098
  • 红警快捷键图示

    最近,实验室的同学们 周末偶尔会玩一玩红警,回忆一下童年,挺愉快的。下面记录一下快捷键,方便操作; 看到B站上红警08,还有对应的快捷键教学视频,也可以直接学习一下; https:…

    Linux 2023年6月14日
    0130
  • 实用!这17个运维技巧,收藏起来随时备用~

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    0108
  • [20220228]enq TX

    [20220228]enq TX – allocate ITL entry的测试3.txt –//上个星期的测试有点乱,重新规划测试. 1.环境:SCOTT…

    Linux 2023年6月13日
    091
  • [Git系列] Git 基本概念

    版本控制系统 版本控制系统是一种帮助软件开发者实现团队合作和历史版本维护的软件,一个版本控制系统应具备以下列出的这几个基本功能: 允许开发者并发工作; 不允许一个开发者覆写另一个开…

    Linux 2023年6月14日
    0105
  • 网卡的RX Ring和TX Ring

    1 简介 环形缓冲(ring buffer)是NIC处理数据包的一种通用数据结构,出现的原因是现代NIC基本使用DMA进行数据传输,作为一种高效简单[1]的数据结构,环形缓冲很 适…

    Linux 2023年6月7日
    0111
  • ShardingSphere-proxy-5.0.0建立mysql读写分离的连接(六)

    一、修改配置文件config-sharding.yaml,并重启服务 # Licensed to the Apache Software Foundation (ASF) unde…

    Linux 2023年6月14日
    0117
  • 剑指offer计划21( 位运算简单)—java

    1.1、题目1 剑指 Offer 15. 二进制中1的个数 1.2、解法 通过判断每一位的与来识别1的数量。 1.3、代码 public class Solution { // y…

    Linux 2023年6月11日
    0135
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球