CPU 对 Redis 性能的影响

2021/01/27 posted in  性能

CPU架构对程序运行的影响

  1. L1、L2缓存中的指令和数据访问速度快,充分利用L1、L2缓存,可以有效缩短应用程序执行时间
  2. 在NUMA架构下,如果应用程序从一个socket上调用另一个socket上,很可能会出现远端内存访问情况,这回增加程序执行时间


CPU 多核对Redis性能影响


在 CPU 多核场景下,Redis 实例被频繁的调用到不同的CPU核上运行,Redis 实例处理请求的时间影响就会更大

可以通过绑定CPU可以有效的降低尾延迟

taskset -c 0 ./redis-server

CPU 的 NUMA 架构对Redis性能影响

网络中断程序从网卡硬件中读取数据,并把数据写入到操作系统内核维护的一个内存缓存区,内核通过epoll 机制触发事件,通知 Redis 实例,Redis 再从内核缓存拷贝到自己内存空间

在CPU 的 NUMA 架构下,当网络中断处理程序、Redis实例分别和CPU核绑定后,就会存在 若果网络中断处理程序和Redis 实例各自绑定CPU 核不在同一CPU socket中 ,Redis 读取网路数据时,就需要跨CPU Socket 访问内存

绑定风险和解决方案

Redsi 实例绑定到一个CPU 逻辑核上时,就会导致子进程、后台线程和Redis主线程竞争CPU资源。

  1. 一个Redis 绑定一个物理核,这样可以缓解CPU资源的竞争
  taskset -c 0,12 ./redis-server
  1. 优化Redis 源码把子进程和后台线程绑定到不同的CPU 核上

修改元绑定CPU核

实现绑定时要用到操作系统提供的1个数据结构cpu_set_t和三个函数CPU_ZERO、CPU_SET、sched_setaffinity

  • cpu_set_t : 是一个位图,每一位表示CPU一个逻辑核
  • CPU_ZERO : 以cpu_set_t为输入参数,把所有位图中的位设置为0
  • CPU_SET : 以CPU 逻辑核编号和cpu_set_t 为参数,把位图中和输入逻辑核编号对应位置设置为1
  • sched_setaffinity : 以进程/线程ID号 和cpu_set_t 为参数,检查cpu_set_t 哪一位为 1 ,就把输入的ID号所代表的进程/线程绑定对应得逻辑核

代码实现


//线程函数
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中的bioProcessBackgroupJobs 函数中创建后台线程,在这函数中实现绑定核


int main(){
   //用fork创建一个子进程
   pid_t p = fork();
   if(p < 0){
      printf(" fork error\n");
   }
   //子进程代码部分
   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 文件 : rdbSaveBackgroud 函数
  • aof.c 文件 : rewriteAppendOnlyFileBackgroud 函数

这两个函数都调用fork 创建子进程,在子进程diamante部分加上核绑定