CPU结构对Redis性能的影响
CPU架构
CPU多核架构
一个CPU处理器一般包含多个运行核心,每个运行核心被称为一个物理核,而根据现在主流的CPU架构一般一个物理核心运行两个超线程也称为逻辑核,两个逻辑核会共享一级缓存(L1 cache)包括一级指令缓存以及一级数据缓存,二级缓存(L2 cache),但需要注意的是一级缓存和二级缓存是物理核私有,那么只有在同一个物理核的逻辑核才会共享,不同物理核是不共享的。
一级缓存和二级缓存的速度是非常快的,物理核访问它们的延迟不会超过10纳秒,但是由于一级缓存和二级缓存的制造技术有限,存储的数据只是kb级别的,如果存储的数据不在缓存中,那么只能去内存中查找而程序访问内存的速度为100多纳秒,速度差距近10倍,为了平衡内存和一级缓存,二级缓存的速度差异,CPU还会有三级缓存,三级缓存容量能达到几MB到几十MB,而且不同的物理核之间还会共享三级缓存。
NUMA架构
目前为了提升服务器性能一个服务器上会存在多个CPU,也会有多个CPU插槽(也称为CPU socket),每个CPU存在多个物理核,每个CPU之间通过总线连接,结构图如下。
在多CPU架构中,一个应用程序可能因为系统调用将程序从CPU Socket1切换到CPU Socket2,如果这时程序再次访需要请求CPU Socket1的内存,这种远程访问会增加应用程序的延迟。
这种架构统一称为非统一内存访问架构(Non-Uniform Memory Access,NUMA 架构),用专业术语描述就是多架构下,一个应用程序访问所在的Socket的本地内存和访问远端内存的延迟不一致的情况,这种架构统称为NUMA架构。
CPU多核对Redis性能的影响
一个程序在CPU其中一个运行核上运行,应用程序需要加载软硬件资源信息(栈指针、CPU寄存器的值等),统称为运行时信息,同时应用程序访问最频繁的值还会放入一级缓存和二级缓存中来提高响应速度。
如果程序运行过程中发生了上下文切换(context switch,当一个线程在一个CPU核上运行后切换到另外一个CPU核上这样的执行过程称为上下文切换),那么Redis的运行时信息将需要重新加载到另外一个CPU核,并且当前CPU核上如果没有指令或者数据,那么需要从三级缓存甚至内存中加载,这样会增加程序的处理时间。
如果程序频繁的进行上下文切换,那么上述场景将经常发生,如何避免频繁的上下文切换呢?可以将Redis的处理程序绑定在某个CPU核上,这样CPU的上下文切换自然少了很多,操作系统提供了指令taskset
可以绑定CPU核与Redis进程之间的关系,同时还能重复利用一级缓存和二级缓存。
### 将Redis进程绑定到0号核上,绑定多个核用逗号分隔
taskset -c 0 ./redis-server
多CPU对Redis性能的影响
多CPU架构又被称为NUMA架构,不同的CPU间通信依靠总线,当不同的处理程序位于不同的CPU时,花费在通信上的时间较多,以Redis的网络中断程序为例。
Redis网络中断处理程序会从网卡中读取中断信息,放入到Redis的内核缓存区中,Redis通过epoll机制触发事件,通知Redis实例,Redis实例从内核缓存区中读取事件,写入到实例内存中,如下所示。
在上述示例中,如果网络中断处理程序和Redis实例不在一个CPU中,那么网络中断处理程序和Redis实例的访问需要跨CPU访问,带来的后果就是需要消耗过多的时间,拖慢网络中断处理速度,示意图如下
解决办法就是将Redis实例和网络中断处理程序绑定在一个CPU上,这样Redis实例访问中断数据就是本地访问,节省传输开销,如下所示。
在绑定运行核时,需要注意的是CPU逻辑核编号规则是先给每一个CPU socket的物理核的第一个逻辑核依次编号,再给物理核的第二个逻辑核编号,示意图如下所示。
另外需要注意的是单独绑定一个逻辑核,那么对于一个Redis实例而言,并不是只有主线程,还有用于持久化的RDB子进程、AOF文件重写进程、以及4.0版本后用于惰性删除的进程等等,这些子进程会和主进程竞争CPU资源,所以在绑定的时候可以绑定一个物理核(通常一个物理核包含两个逻辑核),如上图那么可以执行如下命令绑定,这样可以减少其它子线程对于主线程的CPU资源竞争。
### 0和12号逻辑核都是0号物理核
taskset -c 0,12 ./redis-server