关于作者,目前在蚂蚁金服搬砖任职,在支付宝营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我
物理核:CPU中一般有多个运行核心,一个运行核心称作物理核,每个物理核都可以独立的运行程序
一级缓存(Level 1 cache,简称L1 cache):为每个物理核所私有,包括一级指令缓存和一级数据缓存
二级缓存(Level 2 cache,简称L2 cache):为每个物理核所私有
物理核的私有缓存:指缓存空间只能被当前物理核所使用,其他物理核无法对该缓存空间的数据进行读取。一级、二级都是私有缓存,并且物理核访问L1和L2非常快,一般为10纳秒,但是L1和L2的空间非常小,一般为KB级别,如果L1或L2中找不到数据那么就只能从访存查找,速度大约为百纳秒。
物理核具体架构如下:
三级缓存(Level 3 cache,简称L3 cache):不同的物理核可以共享L3缓存,一般为几十MB,能够缓存更多数据,当L1、L2无法查询到数据时,可以从L3查找
逻辑核:每个物理核中会运行两个超线程,也叫逻辑核。同一个物理核的逻辑核会共享L1、L2
具体关系如下:
处理器:主流服务器上通常会有多个处理器(CPU Socket),而一个处理器上可能会有10-20个物理核(L1、L2缓存),L3缓存,以及连接的内存(不同处理器会连接不同的内存?),不同处理器通过主线连接
多CPU架构上,redis可以在不同的处理器上运行。例如Redis可以先在Socket 1上运行一段时间,然后再被调度到Socket 2上运行。
远端内存访问:redis在CPU Socket 1上运行并将数据存储到了其所连接的内存,这个时候被调度到Socket 2运行,此时如果读取数据需要连接到Socket 1的内存中取读取,这就叫远端内存访问。
非同一内存访问架构(Non-Uniform Memory Access,NUMA架构):多CPU架构下,应用程序访问直连内存和远端内存的延迟并不会一致,远端内存访问会增加应用程序的延迟,这个架构就叫非同一内存访问架构。不同Socket通过总线进行通信,不同物理核之间通过QPI(Quick Path Interconnect)进行通信
运行时信息:在CPU核上运行时,应用程序需要记录自身用到的软硬件资源(例如栈指针,用到的寄存器的值等),这些信息称为运行时信息。同时应用程序访问最频繁的指令和数据会缓存在L1、L2缓存
多核场景下:应用程序可能会需要到一个新的核上运行,此时就需要重新加载运行时信息(context switch,上下文切换),并且重新加载L1、L2缓存的数据,这个过程会导致程序的运行时间增加
context switch :运行数据及L1、L2的重新加载就是上下文切换,频繁的context switch会影响应用程序,因为切换是需要耗费时间用于L1、L2或者L3缓存及运行时信息的重新加载,并且redis实例需要等待加载完成才能够处理请求。
如何避免上下文切换呢?
使用taskset
命令将redis实例绑定在一个核上运行, -c 设置绑定的编号
taskset -c 0 ./redis-server
由于采用NUMA架构的原因,所有实例会优先使用这一个节点的内存,当这个节点内存不足时,再经过总线去申请另一个CPU Socket下的内存,此时也会增加延迟。
如果网络中断处理程序和Redis实例各自所绑的CPU核不在同一个CPU Socket上,那么,Redis实例读取网络数据时,就需要跨CPU Socket访问内存,这个过程会花费较多时间
在CPU多核的场景下,用taskset命令把Redis实例和一个核绑定,可以减少Redis实例在不同核上被来回调度执行的开销,避免较高的尾延迟;在多CPU的NUMA架构下,如果你对网络中断程序做了绑核操作,建议你同时把Redis实例和网络中断程序绑在同一个CPU Socket的不同核上,这样可以避免Redis跨Socket访问内存中的网络数据的时间开销
为了提升Redis的网络性能,我们有时还会把网络中断处理程序和CPU核绑定。在这种情况下,如果服务器使用的是NUMA架构,Redis实例一旦被调度到和中断处理程序不在同一个CPU Socket,就要跨CPU Socket访问网络数据,这就会降低Redis的性能。所以,我建议你把Redis实例和网络中断处理程序绑在同一个CPU Socket下的不同核上,这样可以提升Redis的运行性能
当我们把Redis实例绑到一个CPU逻辑核上时,就会导致子进程、后台线程和Redis主线程竞争CPU资源,一旦子进程或后台线程占用CPU时,主线程就会被阻塞,导致Redis请求延迟增加
方案一:一个Redis实例对应绑一个物理核
在给Redis实例绑核时,我们不要把一个实例和一个逻辑核绑定,而要和一个物理核绑定,也就是说,把一个物理核的2个逻辑核都用上,这样,Redis的主线程、子进程和后台线程可以共享使用一个物理核上的两个逻辑核
方案二:优化Redis源码
如果你很熟悉Redis的源码,就可以在源码中增加绑核操作,把子进程和后台线程绑到不同的核上,这样可以避免对主线程的CPU资源竞争。不过,如果你不熟悉Redis源码,也不用太担心,Redis 6.0出来后,可以支持CPU核绑定的配置操作了,我将在第38讲中向你介绍Redis 6.0的最新特性。