16 阻塞式操作
影响 Redis 性能的 5 大方面的潜在因素:
- Redis 外部的阻塞式操作;
- CPU 核和 NUMA 架构的影响;
- Redis 要害系统配置;
- Redis 内存碎片;
- Redis 缓冲区。
实例阻塞点
- 客户端:网络 IO,键值对增删改查,数据库操作;
- 磁盘:生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
- 主从节点:主库生成、传输 RDB 文件,从库接管 RDB 文件、清空数据库、加载 RDB 文件;
- 切片集群实例:向其余实例传输哈希槽信息,数据迁徙
客户端阻塞点
- 网络 IO:Redis 采纳 IO 多路复用机制,网络 IO 不是阻塞点
-
键值对操作:复杂度高 O(N)的增删改查操作必定会阻塞 redis。
- 汇合全量查问和聚合操作
- 汇合本身删除操作(开释内存后零碎会插入闲暇内存链表用于治理,内存过大操作工夫会减少)
- 清空数据库:删除和开释所有键值对
磁盘交互阻塞点
AOF 日志回写:
Redis 间接记录 AOF 日志时,会依据不同的写回策略对数据做落盘保留。一个同步写磁盘的操作的耗时大概是 1~2ms,如果有大量的写操作须要记录在 AOF 日志中,并同步写回的话,就会阻塞主线程了。
主从节点阻塞点
在主从集群中,主库须要生成 RDB 文件,并传输给从库。主库在复制的过程中,创立和传输 RDB 文件都是由子过程来实现的,不会阻塞主线程。
- 对于从库来说,它在接管了 RDB 文件后,须要应用 FLUSHDB 命令清空以后数据库,造成阻塞
- 从库在清空以后数据库后,还须要把 RDB 文件加载到内存,这个过程的快慢和 RDB 文件的大小密切相关,RDB 文件越大,加载过程越慢,造成阻塞
集群交互阻塞
如果你应用了 Redis Cluster 计划,而且同时正好迁徙的是 bigkey 的话,就会造成主线程的阻塞,因为 Redis Cluster 应用了同步迁徙。
异步执行
Redis 主线程启动后,会应用操作系统提供的 pthread_create 函数创立 3 个子线程,别离由它们负责 AOF 日志写操作、键值对删除以及文件敞开的异步执行。
主线程通过一个链表模式的工作队列和子线程进行交互。
写入 AOF 日志
当 AOF 日志配置成 everysec 选项后,主线程会把 AOF 写日志操作封装成一个工作,也放到工作队列中。后台子线程读取工作后,开始自行写入 AOF 日志,这样主线程就不必始终期待 AOF 日志写完了。
惰性删除 lazy free
当收到键值对删除和清空数据库的操作时,主线程会把这个操作封装成一个工作,放入到工作队列中,而后给客户端返回一个实现信息,表明删除曾经实现。
但实际上,这个时候删除还没有执行,等到后台子线程从工作队列中读取工作后,才开始理论删除键值对,并开释相应的内存空间。
此时,删除或清空操作不会阻塞主线程,这就防止了对主线程的性能影响。
小结
会导致 Redis 性能受损的 5 大阻塞点,包含汇合全量查问和聚合操作、bigkey 删除、清空数据库、AOF 日志同步写,以及从库加载 RDB 文件。
要害门路操作:不能被异步执行的操作。
- 读操作是典型的要害门路操作,包含汇合全量查问和聚合操作
- 从库加载 RDB 操作
- 写操作是否在要害门路,须要看应用方是否须要确认写入曾经实现
汇合全量查问和聚合操作、从库加载 RDB 文件是在要害门路上,无奈应用异步操作来实现。对于这两个阻塞点,我也给你两个小倡议。
- 汇合全量查问和聚合操作:能够应用 SCAN 命令,分批读取数据,再在客户端进行聚合计算;
- 从库加载 RDB 文件:把主库的数据量大小管制在 2~4GB 左右,以保障 RDB 文件能以较快的速度加载。
17 CPU 构造
一个 CPU 处理器中个别有多个运行外围,咱们把一个运行外围称为一个物理核,每个物理核都能够运行应用程序。
支流架构
每个物理核都领有公有的一级缓存(Level 1 cache,简称 L1 cache),包含一级指令缓存和一级数据缓存,以及公有的二级缓存(Level 2 cache,简称 L2 cache)。
L1 和 L2 缓存的大小只有 KB 级别。
不同的物理核还会共享一个独特的三级缓存(Level 3 cache,简称为 L3 cache)。
L3 有几 MB 到几十 MB。
另外,当初支流的 CPU 处理器中,每个物理核通常都会运行两个超线程,也叫作逻辑核。同一个物理核的逻辑核会共享应用 L1、L2 缓存。
服务器上通常有多个 CPU 处理器(CPU Socket),每个处理器有本人的物理核(包含 L1、L2 缓存),L3 缓存,以及连贯的内存,同时,不同处理器间通过总线连贯。
远端内存拜访:
利用在一个 Socket 上运行并把数据存入内存,当被调度到另一个 Socket 上运行再进行内存拜访时,就须要拜访之前 Socket 上连贯的内存,称为远端内存拜访。
远端内存拜访会减少应用程序的提早。
在多 CPU 架构下,一个应用程序拜访所在 Socket 的本地内存和拜访远端内存的提早并不统一,所以,咱们也把这个架构称为非对立内存拜访架构(Non-Uniform Memory Access,NUMA 架构)。
多核影响
在 CPU 多核的环境下,通过绑定 Redis 实例和 CPU 核,能够无效升高 Redis 的尾提早。
在一个 CPU 核上运行时,应用程序须要记录本身应用的软硬件资源信息(例如栈指针、CPU 核的寄存器值等),咱们把这些信息称为运行时信息。
同时,应用程序拜访最频繁的指令和数据还会被缓存到 L1、L2 缓存上,以便晋升执行速度。
上下文切换 context switch:
在 CPU 多核的环境中,一个线程先在一个 CPU 核上运行,之后又切换到另一个 CPU 核上运行,这时就会产生 context switch。
Redis 主线程的运行时信息须要被从新加载到另一个 CPU 核上,而且,此时,另一个 CPU 核上的 L1、L2 缓存中,并没有 Redis 实例之前运行时频繁拜访的指令和数据,所以,这些指令和数据都须要从新从 L3 缓存,甚至是内存中加载。
咱们能够应用 taskset 命令把一个程序绑定在一个核上运行。
taskset -c 0 ./redis-server
把 Redis 实例绑在了 0 号核上,其中,“-c”选项用于设置要绑定的核编号。
咱们最好把网络中断程序和 Redis 实例绑在同一个 CPU Socket 上。
不过,须要留神的是,在 CPU 的 NUMA 架构下,对 CPU 核的编号规定,并不是先把一个 CPU Socket 中的所有逻辑核编完,再对下一个 CPU Socket 中的逻辑核编码,而是先给每个 CPU Socket 中每个物理核的第一个逻辑核顺次编号,再给每个 CPU Socket 中的物理核的第二个逻辑核顺次编号。
lscpu
Architecture: x86_64
...
NUMA node0 CPU(s): 0-5,12-17
NUMA node1 CPU(s): 6-11,18-23
...
绑核危险:
当咱们把 Redis 实例绑到一个 CPU 逻辑核上时,就会导致子过程、后盾线程和 Redis 主线程竞争 CPU 资源,一旦子过程或后盾线程占用 CPU 时,主线程就会被阻塞,导致 Redis 申请提早减少
解决方案:
- 一个 Redis 实例对应绑一个物理核(把一个物理核的 2 个逻辑核都用上,缓解 CPU 竞争)
taskset -c 0,12 ./redis-server
- 优化 Redis 源码,把子过程和后盾线程绑到不同 CPU 上
18 响应提早
基线性能:一个零碎在低压力、无烦扰下的根本性能,这个性能只由以后的软硬件配置决定。
Redis 基线性能测试:
./redis-cli --intrinsic-latency 120
打印 120 秒内监测到的最大提早
留神:为了防止网络对基线性能的影响,刚刚说的这个命令须要在服务器端间接运行。
论断:如果 Redis 运行时提早是其基线性能的 2 倍及以上,就能够认定 Redis 变慢了。
慢查问命令
慢查问命令,就是指在 Redis 中执行速度慢的命令,这会导致 Redis 提早减少,和命令操作的复杂度无关。
排查办法:
- Redis 日志
- latency monitor 工具
解决办法:
- 用其余高效命令代替。比如说,如果你须要返回一个 SET 中的所有成员时,不要应用 SMEMBERS 命令,而是要应用 SSCAN 屡次迭代返回,防止一次返回大量数据,造成线程阻塞。
- 当你须要执行排序、交加、并集操作时,能够在客户端实现,而不要用 SORT、SUNION、SINTER 这些命令,免得拖慢 Redis 实例。
- 留神生成环境不要用 KEYS 命令,它会遍历存储的键值对,提早高。
过期 KEY 操作
过期 key 的主动删除机制,是回收内存空间的罕用机制,会引起 Redis 操作阻塞,导致性能变慢。
Redis 默认每 100 毫秒删除一些过期 key:
- 采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数(默认 20)的 key,并将其中过期的 key 全副删除;
- 如果超过 25% 的 key 过期了,则反复删除的过程,直到过期 key 的比例降至 25% 以下。
(触发该条件后会始终执行删除操作,导致 Redis 变慢)
算法 2 触发形式:
频繁应用带有雷同工夫参数的 EXPIREAT 命令设置过期 key,导致在同一时间大量 key 过期。
解决方案:加随机数
小结
排查和解决 Redis 变慢问题的办法:
- 从慢查问命令开始排查,并且依据业务需要替换慢查问命令;
- 排查过期 key 的工夫设置,并依据理论应用需要,设置不同的过期工夫。
问题:有哪些其余命令能够代替 KEYS 命令,实现同样的性能呢?
如果想要获取整个实例的所有 key,倡议应用 SCAN 命令代替。客户端通过执行 SCAN $cursor COUNT $count 能够失去一批 key 以及下一个游标 $cursor,而后把这个 $cursor 当作 SCAN 的参数,再次执行,以此往返,直到返回的 $cursor 为 0 时,就把整个实例中的所有 key 遍历进去了。
然而 SCAN 可能会失去反复的 key(Rehash 时,旧表已遍历过的 key 会映射到新表没有遍历过的地位)。
19 文件系统和操作系统
Redis 会长久化保留数据到磁盘,这个过程要依赖文件系统来实现,所以,文件系统将数据写回磁盘的机制,会间接影响到 Redis 长久化的效率。在长久化的过程中,Redis 也还在接管其余申请,长久化的效率高下又会影响到 Redis 解决申请的性能。
另一方面,Redis 是内存数据库,内存操作十分频繁,所以,操作系统的内存机制会间接影响到 Redis 的解决效率。比方 Redis 的内存不够用了,操作系统会启动 swap 机制,这就会间接拖慢 Redis。
文件系统:AOF 模式
AOF 日志提供了三种日志写回策略:no、everysec、always。
写回策略依赖文件系统的两个零碎调用实现,也就是 write 和 fsync:
- write 只有把日志记录写到内核缓冲区,就能够返回了,并不需要期待日志理论写回到磁盘;(no)
- fsync 须要把日志记录写回到磁盘后能力返回,工夫较长。(everysec, always)
no:调用 write 写日志文件,由操作系统周期性的将日志写回磁盘
everysec:容许失落一秒的操作记录,应用后台子线程实现 fysnc 操作
always:不应用后台子线程执行
另外,AOF 重写生成体量放大的新的 AOF 日志文件,须要的工夫很长,也容易阻塞 Redis 主线程,所以,Redis 应用子过程来进行 AOF 重写。
危险点:
- fsync 须要等到数据写到磁盘能力返回,AOF 重写会进行大量 IO,可能阻塞 fsync。
- 主线程会监控 fsync 进度,如果发现上一次还没执行完,主线程也会阻塞,导致 Redis 性能降落。
配置:
如果业务利用对提早十分敏感,但同时容许一定量的数据失落,那么,能够把配置项 no-appendfsync-on-rewrite 设置为 yes
no-appendfsync-on-rewrite yes
倡议:应用高速固态硬盘
操作系统:内存 swap
内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制。
swap 触发后影响的是 Redis 主 IO 线程,这会极大地减少 Redis 的响应工夫。
触发起因:物理机内存不足
1、Redis 实例本身应用了大量的内存,导致物理机器的可用内存有余;
2、和 Redis 实例在同一台机器上运行的其余过程,在进行大量的文件读写操作。文件读写自身会占用零碎内存,这会导致调配给 Redis 实例的内存质变少,进而触发 Redis 产生 swap。
解决思路:减少内存或应用集群。
查看 swap 状况:
# 1. 查看过程 ID
redis-cli info | grep process_id
process_id: 5332
# 2. 进入过程目录
cd /proc/5332
# 3. 查看过程应用状况
cat smaps | egrep '^(Swap|Size)'
留神:当呈现百 MB,甚至 GB 级别的 swap 大小时,就表明,此时,Redis 实例的内存压力很大,很有可能会变慢。
操作系统:内存大页
内存大页机制(Transparent Huge Page, THP),也会影响 Redis 性能,该机制反对 2MB 大小的内存调配,惯例内存调配是 4KB。
毛病:RDB 应用写时复制机制,有数据要被批改时,会先拷贝一份再批改。当批改或新写数据较多时,内存大页将导致大量拷贝,影响 Redis 性能。
查看内存大页:
cat /sys/kernel/mm/transparent_hugepage/enabled
启动:always 禁止:never
生产环境倡议:敞开大页机制
echo never > /sys/kernel/mm/transparent_hugepage/enabled
小结
查看 Redis 性能 9 点:
- 获取 Redis 实例在以后环境下的基线性能。
- 是否用了慢查问命令?如果是的话,就应用其余命令代替慢查问命令,或者把聚合计算命令放在客户端做。
- 是否对过期 key 设置了雷同的过期工夫?对于批量删除的 key,能够在每个 key 的过期工夫上加一个随机数,防止同时删除。
- 是否存在 bigkey?对于 bigkey 的删除操作,如果你的 Redis 是 4.0 及以上的版本,能够间接利用异步线程机制缩小主线程阻塞;如果是 Redis 4.0 以前的版本,能够应用 SCAN 命令迭代删除;对于 bigkey 的汇合查问和聚合操作,能够应用 SCAN 命令在客户端实现。
- Redis AOF 配置级别是什么?业务层面是否确实须要这一可靠性级别?如果咱们须要高性能,同时也容许数据失落,能够将配置项 no-appendfsync-on-rewrite 设置为 yes,防止 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 提早减少。当然,如果既须要高性能又须要高可靠性,最好应用高速固态盘作为 AOF 日志的写入盘。
- Redis 实例的内存应用是否过大?产生 swap 了吗?如果是的话,就减少机器内存,或者是应用 Redis 集群,摊派单机 Redis 的键值对数量和内存压力。同时,要避免出现 Redis 和其余内存需要大的利用共享机器的状况。
- 在 Redis 实例的运行环境中,是否启用了通明大页机制?如果是的话,间接敞开内存大页机制就行了。
- 是否运行了 Redis 主从集群?如果是的话,把主库实例的数据量大小管制在 2~4GB,免得主从复制时,从库因加载大的 RDB 文件而阻塞。
- 是否应用了多核 CPU 或 NUMA 架构的机器运行 Redis 实例?应用多核 CPU 时,能够给 Redis 实例绑定物理核;应用 NUMA 架构时,留神把 Redis 实例和网络中断处理程序运行在同一个 CPU Socket 上。
20 内存调配
问题:做了数据删除,应用 top 命令查看时,为什么 Redis 还是占用了很多内存呢?
起因:数据删除后,Redis 开释的内存空间会由内存分配器治理,并不会立刻返回给操作系统。所以,操作系统依然会记录着给 Redis 调配了大量内存。
内存碎片
内因:内存调配策略
外因:键值对大小不一样;删改操作;
内存调配策略
Redis 能够应用 libc、jemalloc、tcmalloc 多种内存分配器来分配内存,默认应用 jemalloc。
jemalloc 的调配策略之一,是依照一系列固定的大小划分内存空间。当程序申请的内存最靠近某个固定值时,jemalloc 会给它调配相应大小的空间。
如果 Redis 每次向分配器申请的内存空间大小不一样,这种调配形式就会有造成碎片的危险。
键值对大小
内存分配器只能按固定大小分配内存,所以,调配的内存空间个别都会比申请的空间大一些,不会完全一致,这自身就会造成肯定的碎片,升高内存空间存储效率。
批改删除操作
键值对被批改和删除,会导致空间的扩容和开释。
- 如果批改后的键值对变大或变小了,就须要占用额定的空间或者开释不必的空间
- 删除的键值对不再须要内存空间了,会把空间释放出来,造成闲暇空间
判断内存碎片大小
应用 INFO 命令:
INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86
mem_fragmentation_ratio 的指标,它示意的就是 Redis 以后的内存碎片率。
mem_fragmentation_ratio = used_memory_rss / used_memory
used_memory_rss 是操作系统理论调配给 Redis 的物理内存空间,外面就蕴含了碎片;
used_memory 是 Redis 为了保留数据理论申请应用的空间。
教训:
- mem_fragmentation_ratio 大于 1.5。这表明内存碎片率曾经超过了 50%。个别状况下,这个时候,咱们就须要采取一些措施来升高内存碎片率了。
- 小于 1,阐明没有足够的物理内存,产生 swap 了。
清理内存碎片
从 4.0-RC3 版本当前,Redis 本身提供了一种内存碎片主动清理的办法。
代价:
碎片清理是有代价的,操作系统须要把多份数据拷贝到新地位,把原有空间释放出来,这会带来工夫开销。因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无奈及时处理申请,性能就会升高。
解决方案:
能够通过设置参数,来管制碎片清理的开始和完结机会,以及占用的 CPU 比例,从而缩小碎片清理对 Redis 自身申请解决的性能影响。
Redis 须要启用主动内存碎片清理,能够把 activedefrag 配置项设置为 yes:
config set activedefrag yes
配置主动清理条件(同时满足):
- active-defrag-ignore-bytes 100mb:示意内存碎片的字节数达到 100MB 时,开始清理;
- active-defrag-threshold-lower 10:示意内存碎片空间占操作系统调配给 Redis 的总空间比例达到 10% 时,开始清理。
配置 CPU 占比:
- active-defrag-cycle-min 25:示意主动清理过程所用 CPU 工夫的比例不低于 25%,保障清理能失常发展;
- active-defrag-cycle-max 75:示意主动清理过程所用 CPU 工夫的比例不高于 75%,一旦超过,就进行清理,从而防止在清理时,大量的内存拷贝阻塞 Redis,导致响应提早升高。
小结
info memory:查看碎片率的状况;
碎片率阈值:判断是否要进行碎片清理了;
内存碎片主动清理:进步内存理论利用率。