在排查时发现 Redis 没有执行大量的慢查询命令,也没有同时删除大量过期 keys,这就要关注影响性能的文件系统和操作系统。
Redis 会持久化保存数据到磁盘,要依赖文件系统来完成,文件系统将数据写回磁盘的机制,会直接影响到 Redis 持久化的效率。在持久化的过程中,Redis 也还在接收其他请求,持久化的效率高低又会影响到 Redis 处理请求的性能。
Redis 是内存数据库,内存操作非常频繁,操作系统的内存机制会直接影响到 Redis 的处理效率。Redis 的内存不够用了,操作系统会启动 swap 机制,这就会直接拖慢 Redis。
Redis 会采用 AOF 日志或 RDB 快照。AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的 write 和 fsync两个系统调用完成。
write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘;而 fsync 需要把日志记录写回到磁盘后才能返回,时间较长。三种写回策略所执行的系统调用。
当写回策略配置为 everysec 和 always 时,Redis 需要调用 fsync 把日志写回磁盘。这两种写回策略的具体执行情况还不太一样。
使用 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,无论是被换入数据的进程,还是被换出数据的进程,性能都会受到慢速磁盘读写的影响。
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有两种常见的情况:
解决思路:增加机器的内存或者使用 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
在实际生产环境中部署时,不要使用内存大页机制,执行下面的命令:
echo never /sys/kernel/mm/transparent_hugepage/enabled
遇到 Redis 性能变慢时 9 个检查点的 Checklist:
影响系统性能的因素还有很多,遇到了特殊情况,Redis 所在的机器上有没有一些其他占内存、磁盘 IO 和网络 IO 的程序,比如说数据库程序或者数据采集程序。有的话,将这些程序迁移到其他机器上运行。