关于redis:Redis核心技术笔记1620

46次阅读

共计 7717 个字符,预计需要花费 20 分钟才能阅读完成。

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 文件都是由子过程来实现的,不会阻塞主线程。

  1. 对于从库来说,它在接管了 RDB 文件后,须要应用 FLUSHDB 命令清空以后数据库,造成阻塞
  2. 从库在清空以后数据库后,还须要把 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 申请提早减少

解决方案:

  1. 一个 Redis 实例对应绑一个物理核(把一个物理核的 2 个逻辑核都用上,缓解 CPU 竞争)
taskset -c 0,12 ./redis-server
  1. 优化 Redis 源码,把子过程和后盾线程绑到不同 CPU 上

18 响应提早

基线性能:一个零碎在低压力、无烦扰下的根本性能,这个性能只由以后的软硬件配置决定。

Redis 基线性能测试:

./redis-cli --intrinsic-latency 120

打印 120 秒内监测到的最大提早
留神:为了防止网络对基线性能的影响,刚刚说的这个命令须要在服务器端间接运行。

论断:如果 Redis 运行时提早是其基线性能的 2 倍及以上,就能够认定 Redis 变慢了。

慢查问命令

慢查问命令,就是指在 Redis 中执行速度慢的命令,这会导致 Redis 提早减少,和命令操作的复杂度无关。

排查办法:

  1. Redis 日志
  2. latency monitor 工具

解决办法:

  1. 用其余高效命令代替。比如说,如果你须要返回一个 SET 中的所有成员时,不要应用 SMEMBERS 命令,而是要应用 SSCAN 屡次迭代返回,防止一次返回大量数据,造成线程阻塞。
  2. 当你须要执行排序、交加、并集操作时,能够在客户端实现,而不要用 SORT、SUNION、SINTER 这些命令,免得拖慢 Redis 实例。
  3. 留神生成环境不要用 KEYS 命令,它会遍历存储的键值对,提早高。

过期 KEY 操作

过期 key 的主动删除机制,是回收内存空间的罕用机制,会引起 Redis 操作阻塞,导致性能变慢。

Redis 默认每 100 毫秒删除一些过期 key:

  1. 采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数(默认 20)的 key,并将其中过期的 key 全副删除;
  2. 如果超过 25% 的 key 过期了,则反复删除的过程,直到过期 key 的比例降至 25% 以下。
    (触发该条件后会始终执行删除操作,导致 Redis 变慢)

算法 2 触发形式:
频繁应用带有雷同工夫参数的 EXPIREAT 命令设置过期 key,导致在同一时间大量 key 过期。

解决方案:加随机数

小结

排查和解决 Redis 变慢问题的办法:

  1. 从慢查问命令开始排查,并且依据业务需要替换慢查问命令;
  2. 排查过期 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 点:

  1. 获取 Redis 实例在以后环境下的基线性能。
  2. 是否用了慢查问命令?如果是的话,就应用其余命令代替慢查问命令,或者把聚合计算命令放在客户端做。
  3. 是否对过期 key 设置了雷同的过期工夫?对于批量删除的 key,能够在每个 key 的过期工夫上加一个随机数,防止同时删除。
  4. 是否存在 bigkey?对于 bigkey 的删除操作,如果你的 Redis 是 4.0 及以上的版本,能够间接利用异步线程机制缩小主线程阻塞;如果是 Redis 4.0 以前的版本,能够应用 SCAN 命令迭代删除;对于 bigkey 的汇合查问和聚合操作,能够应用 SCAN 命令在客户端实现。
  5. Redis AOF 配置级别是什么?业务层面是否确实须要这一可靠性级别?如果咱们须要高性能,同时也容许数据失落,能够将配置项 no-appendfsync-on-rewrite 设置为 yes,防止 AOF 重写和 fsync 竞争磁盘 IO 资源,导致 Redis 提早减少。当然,如果既须要高性能又须要高可靠性,最好应用高速固态盘作为 AOF 日志的写入盘。
  6. Redis 实例的内存应用是否过大?产生 swap 了吗?如果是的话,就减少机器内存,或者是应用 Redis 集群,摊派单机 Redis 的键值对数量和内存压力。同时,要避免出现 Redis 和其余内存需要大的利用共享机器的状况。
  7. 在 Redis 实例的运行环境中,是否启用了通明大页机制?如果是的话,间接敞开内存大页机制就行了。
  8. 是否运行了 Redis 主从集群?如果是的话,把主库实例的数据量大小管制在 2~4GB,免得主从复制时,从库因加载大的 RDB 文件而阻塞。
  9. 是否应用了多核 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 为了保留数据理论申请应用的空间。

教训:

  1. mem_fragmentation_ratio 大于 1.5。这表明内存碎片率曾经超过了 50%。个别状况下,这个时候,咱们就须要采取一些措施来升高内存碎片率了。
  2. 小于 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:查看碎片率的状况;
碎片率阈值:判断是否要进行碎片清理了;
内存碎片主动清理:进步内存理论利用率。

正文完
 0