Redis教程

19 Redis 的文件系统和操作系统对性能的影响

本文主要是介绍19 Redis 的文件系统和操作系统对性能的影响,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

19 Redis 的文件系统和操作系统对性能的影响

  • 前言
  • 一、文件系统:AOF 模式
  • 二、操作系统:内存 swap
  • 三、操作系统:内存大页
  • 总结


前言

在排查时发现 Redis 没有执行大量的慢查询命令,也没有同时删除大量过期 keys,这就要关注影响性能的文件系统和操作系统。

Redis 会持久化保存数据到磁盘,要依赖文件系统来完成,文件系统将数据写回磁盘的机制,会直接影响到 Redis 持久化的效率。在持久化的过程中,Redis 也还在接收其他请求,持久化的效率高低又会影响到 Redis 处理请求的性能。

Redis 是内存数据库,内存操作非常频繁,操作系统的内存机制会直接影响到 Redis 的处理效率。Redis 的内存不够用了,操作系统会启动 swap 机制,这就会直接拖慢 Redis。
在这里插入图片描述

一、文件系统:AOF 模式

Redis 会采用 AOF 日志或 RDB 快照。AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的 write 和 fsync两个系统调用完成。

write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘;而 fsync 需要把日志记录写回到磁盘后才能返回,时间较长。三种写回策略所执行的系统调用。
在这里插入图片描述
当写回策略配置为 everysec 和 always 时,Redis 需要调用 fsync 把日志写回磁盘。这两种写回策略的具体执行情况还不太一样。

  • everysec :Redis 允许丢失一秒的操作记录,Redis 主线程并不需要确保每个操作记录日志都写回磁盘。而且fsync 的执行时间很长,在 Redis 主线程中执行 fsync容易阻塞主线程。当写回策略配置为 everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。
  • always:Redis 需要确保每个操作记录日志都写回磁盘,如果用后台子线程异步完成,主线程就无法及时地知道每个操作是否已经完成了。always 策略不使用后台子线程来执行。

使用 AOF 日志时,为了避免日志文件不断增大,Redis 会执行 AOF 重写,生成体量缩小的新的 AOF 日志文件。AOF 重写本身需要的时间很长,也容易阻塞 Redis 主线程,所以Redis 使用子进程来进行 AOF 重写。

潜在的风险点:AOF 重写会对磁盘进行大量 IO 操作,fsync 又需要等到数据写到磁盘后才能返回,当 AOF 重写的压力比较大时,会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是主线程会监控 fsync 的执行进度。 当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,主线程发现上一次的 fsync 还没有执行完会阻塞。所以后台子线程执行的 fsync 频繁阻塞的话(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢。

磁盘压力小和压力大的时候,fsync 后台子线程和主线程受到的影响:
在这里插入图片描述
fsync 后台子线程和 AOF 重写子进程的存在,主 IO 线程一般不会被阻塞。如果在重写日志时,AOF 重写子进程的写入量比较大, fsync 线程也会被阻塞,进而阻塞主线程,导致延迟增加。

排查和解决建议:
检查下 Redis 配置文件中的 appendfsync 配置项: Redis 实例使用的是哪种 AOF 日志写回策略,如下:
在这里插入图片描述
如果 AOF 写回策略使用了 everysec 或 always 配置,先确认下业务方对数据可靠性的要求,明确是否需要每一秒或每一个操作都记日志。有的业务方不了解 Redis AOF 机制, 很可能就直接使用数据可靠性最高等级的 always 配置了。在有些场景中(例如 Redis 用于缓存),数据丢了还可以从后端数据库中获取,并不需要很高的数据可靠性。

如果业务应用对延迟非常敏感,同时允许一定量的数据丢失,可以把配置项 no- appendfsync-on-rewrite 设置为 yes,如下:

no-appendfsync-on-rewrite yes

设置为 yes 表示在 AOF 重写时,不进行 fsync 操作。Redis 把写命令写到内存后,不调用后台线程进行 fsync 操作,就直接返回了。发生宕机,就会导致数据丢失。设置为 no(也是默认配置),在 AOF 重写时,Redis 实例仍然会调用后台线程进行 fsync 操作,这就会给实例带来阻塞。

如果需要高性能,也需要高可靠数据保证,采用高速的固态硬盘作为 AOF 日志的写入设备。高速固态盘的带宽和并发度比传统的机械硬盘的要高出 10 倍及以上。在 AOF 重写和 fsync 后台线程同时执行时,固态硬盘可以提供较为充足的磁盘 IO 资源,让 AOF 重写和 fsync 后台线程的磁盘 IO 资源竞争减少,从而降低对 Redis 的性能影响。

二、操作系统:内存 swap

内存 swap 是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写,一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,性能都会受到慢速磁盘读写的影响。

Redis 是内存数据库,内存使用量大,如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行了,可能受到 swap 的影响,而导致性能变慢。

正常情况下 Redis 的操作是直接通过访问内存就能完成,一旦 swap 被触发了,Redis 的请求操作需要等到磁盘数据读写完成才行。和AOF 日志文件读写使用 fsync 线程不同,swap 触发后影响的是 Redis 主 IO 线程,会极大地增加 Redis 的响应时间。

在正常情况下运行的一个实例完成 5000 万个 GET 请求时需要 300s,但是有次完成 5000 万 GET 请求,花了将近 4 个小时的时间。发现该实例所在的机器已经发生了 swap。 从 300s 到 4 个小时,延迟增加了将近 48 倍,可以看到 swap 对性能造成的严重影响。

触发 swap 的条件: 触发 swap 的原因主要是物理机器内存不足, Redis有两种常见的情况:

  1. Redis 实例自身使用了大量的内存,导致物理机器的可用内存不足;
  2. 和 Redis 实例在同一台机器上运行的其他进程,在进行大量的文件读写操作。文件读写本身会占用系统内存,这会导致分配给 Redis 实例的内存量变少,进而触发 Redis 发生 swap。

解决思路:增加机器的内存或者使用 Redis 集群。

操作系统会在后台记录每个进程的 swap 使用情况,有多少数据量发生了 swap。下面的命令查看 Redis 的进程号 5332。

$ redis-cli info | grep process_id 
process_id: 5332 

然后,进入 Redis 所在机器的 /proc 目录下的该进程目录中:

$ cd /proc/5332

运行下面的命令,查看该 Redis 进程的使用情况。部分结果:

$cat smaps | egrep '^(Swap|Size)' 
Size: 584 kB 
Swap: 0 kB 
Size: 4 kB 
Swap: 4 kB 
Size: 4 kB 
Swap: 0 kB 
Size: 462044 kB 
Swap: 462008 kB 
Size: 21392 kB 
Swap: 0 kB 

每一行 Size 表示的是 Redis 实例所用的一块内存大小, Size 下方的 Swap 和它相对应,表示这块 Size 大小的内存区域有多少已经被换出到磁盘上了。如果这两个值相等,表示这块内存区域已经完全被换出到磁盘了。

Redis 会使用很多大小不一的内存块,可以看到有很多 Size 行,有的很小 4 KB,有的很大 462044KB。不同内存块被换出到磁盘上的大小也不一样,例如第一个 4KB 内存块,Swap 也是 4KB, 这表示这个内存块已经被换出了;462044KB 这个内存块也被换出了 462008KB, 差不多有 462MB。

当出现百 MB,甚至 GB 级别的 swap 大小时,表明此时 Redis 实例的内存压力很大,很有可能会变慢。swap 的大小是排查 Redis 性能变慢是否由 swap 引起的重要指标

发生内存 swap,最直接的解决方法是增加机器内存。如果该实例在一个 Redis 切片集群中,可以增加 Redis 集群的实例个数,来分摊每个实例服务的数据量,进而减少每个实例所需的内存量。

Redis 实例和其他操作大量文件的程序(例如数据分析程序)共享机器,可以将 Redis 实例迁移到单独的机器上运行,以满足它的内存需求量。如果该实例正好是 Redis 主从集群中的主库,而从库的内存很大,也可以进行主从切换,把大内存的从库变成主库,由它来处理客户端请求。

三、操作系统:内存大页

内存大页机制(Transparent Huge Page, THP),也会影响 Redis 性能。 Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。

系统的设计通常是一个取舍过程,称之为 trade-off。很多机制通常都是优势和劣势并存的。虽然内存大页可以给 Redis 带来内存分配方面的收益,但是 Redis 为了数据可靠性,需要将数据做持久化保存。这个写入过程由额外的线程执行,所以 Redis 主线程仍然可以接收客户端写请求。客户端的写请求可能会修改正在进行持久化的数据。过程中 Redis 就会采用写时复制机制,一旦有数据要被修改,Redis 并不会直接修改内存中的数据,而是将这些数据拷贝一份,然后再进行修改。

如果采用了内存大页,即使客户端请求只修改 100B 的数据,Redis 也需要拷贝 2MB 的大页。如果是常规内存页机制,只用拷贝 4KB。两者相比,当客户端请求修改或新写入数据较多时,内存大页机制将导致大量的拷贝,这就会影响 Redis 正常的访存操作,最终导致性能变慢。

关闭内存大页,首先要先排查下内存大页。方法是:在 Redis 实例运行的机器上执行如下命令:

cat /sys/kernel/mm/transparent_hugepage/enabled
  • always 表明内存大页机制被启动了;
  • never表示内存大页机制被禁止

在实际生产环境中部署时,不要使用内存大页机制,执行下面的命令:

echo never /sys/kernel/mm/transparent_hugepage/enabled

总结

遇到 Redis 性能变慢时 9 个检查点的 Checklist:

  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 上。

影响系统性能的因素还有很多,遇到了特殊情况,Redis 所在的机器上有没有一些其他占内存、磁盘 IO 和网络 IO 的程序,比如说数据库程序或者数据采集程序。有的话,将这些程序迁移到其他机器上运行。

这篇关于19 Redis 的文件系统和操作系统对性能的影响的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!