• 当我们发现 Redis 并没有再执行一些类似于集合的全量遍历,执行集合的排序,交集,并集操作,KEYS 操作等这些慢操作,也没有同时删除过大量的过期的 keys 操作时,Redis 仍然还是变慢了,那么此时就需要考虑影响 Redis 性能的另外两个因素:文件系统及其操作系统。
  • Redis 实例需要进行数据的持久化,就需要将数据保存在磁盘上,所以文件系统机制直接影响到了 Redis 的持久化方面的性能,而正在进行数据持久化的 Redis 同样也可以处理其他客户端的请求,因此持久化的效率的高低会影响到 Redis 处理请求的性能。
  • 而 Redis 属于的是内存数据库,需要频繁的进行内存上的操作,所以操作系统的内存机制直接影响到了 Redis 执行命令的性能,同时,如果 Redis 的内存不够用时,就需要操作系统执行 swap 机制,就会拖慢 Redis。

一、文件系统 AOF 操作

  • Redis 需要进行数据的持久化的时候需要将生成的 AOF 日志及其 RDB 快照持久化到磁盘当中,而 Redis 实例每次执行完写操作的时候就需要将其执行的记录写成 AOF 日志的形式写回到磁盘中,在 Redis 中 AOF 日志的写回策略有三种,在前面有提及过,包括 no, enerysec, always。这三种策略的执行需要使用到文件系统的 write 和 fsync 两种系统调用来完成。
  • 其中 write 系统调用中,只需要将 AOF 日志写到内核缓存区中就可以返回了,不需要等到 AOF 日志写到磁盘中;而 fsync (文件同步) 还需要等到 AOF 日志写道磁盘中才能返回,因此执行的时间比较长。下面是 Redis 三种 AOF 写回策略对应需要使用的文件系统的系统调用。
  • 可以看到其中 everysec 和 always 者两种 AOF 写回策略中需要使用文件系统中的 fsync 系统调用,但是由于两种写回策略的不同性质,在使用 fsync 的方式也不一样。
  • 由于 everysec 策略中,允许有 1s 的 AOF 日志数据的丢失,因此不需要 Redis 主线程来负责执行确保每次的 AOF 日志都写回到了磁盘中,而是选择使用子线程来负责执行 AOF 日志写磁盘的操作,而 Redis 主线程需要对 fsync 的执行进度进行监控,由于定时需要 Redis 将 AOF 日志写回到磁盘上,当 Redis 主线程监控到上一次的 fsync 还没有执行返回,那么该 fsync 就会被阻塞,如果后台子进程执行的 fsync 频繁被阻塞的话(AOF 重写占用了大量的磁盘 IO 的带宽),主线程也会被阻塞,从而影响了 Redis 的性能。
  • 上面提到当进行 AOF 重写的时候容易阻塞 Redis 主线程,只是由于 Redis 避免 AOF 文件过大,因此会执行 AOF 日志重写机制,对 AOF 日志进行压缩,虽然上面在进行 AOF 日志写回磁盘是由后台子进程负责完成的,一般来说不会阻塞 Redis 主线程,但是在进行 Redis AOF 重写的时候,本身该操作消耗的时间比较长,并且需要占用大量的磁盘 IO 的带宽,进而使得进行 AOF 写回磁盘操作的时候会竞争磁盘的 IO 资源,使得原先执行时间较长的 fsync 需要消耗的执行时间更长了,当此时主线程将最新的操作记录写回到 AOF 文件中,监测到上一次的 fsync 还未执行完,就会阻塞它,进而导致负责执行 fsync 的子进程可能因此 AOF 重写较长的过程中,频繁地被阻塞,进而使得阻塞主线程。
  • 对于 always 策略而言,需要确保每次的 AOF 日志写回到磁盘中,因此 Redis 就不能交付给子线程负责执行,会阻塞主线程来执行该操作,进而导致在使用 always AOF 日志写回策略时会导致阻塞掉 Redis 主线程,直至等到 fsync 执行完写磁盘操作之后,Redis 才能正常的执行其他用户的操作,使得降低了 Redis 的性能。
  • 因此需要根据业务的需求选择合适的 AOF 回写策略,如果需要高性能的写同时还要保证数据的可靠性,推荐使用固态硬盘作为写 AOF 日志文件的磁盘,固态硬盘比传统的机械硬盘的带宽和并发度高出十倍以上。

二、操作系统:swap 操作

  • 对于 Redis 内存数据库而言,内存空间的设置就十分重要,当给 Redis 运行使用的内存空间不足或者与内存需求较大的应用程序一起运行就会导致操作系统的内存开启 swap 操作,该操作就涉及到内存中的数据在内存和磁盘中来回换进换出的机制,其中就涉及到慢速的 IO 磁盘操作,并且与由子线程负责执行 fsync 不同,执行系统内存的 swap 操作直接影响到 Redis 的主 IO 线程,一旦 swap 开启,Redis 主线程就必须等到磁盘中的数据写完才能执行其他的操作,无疑增加了 Redis 的响应时间。
  • 解决上面因为 swap 导致影响 Redis 实例性能的解决方案有两种:
  • 增大该 Redis 实例所处 CPU 的内存空间大小,避免因为内存空间不足而进行系统内存的 swap 操作
  • 如果该 Redis 在切片集群中,那么就增大该切片集群中的 Redis 实例的个数,这样使得每个 Redis 实例负载需要存储的数据量减少,避免因为内存空间不足导致的系统内存 swap 操作。

三、操作系统:内存大页

内存系统中除了内存系统的 swap 操作会影响到 Redis 实例的性能以外,内存的大页也会影响到 Redis 的性能,Linux 内核 2.6.38 开始就支持了内存大页机制了,该机制支持 2MB 的内存物理页的分配,而一般的内存物理页只有 4KB,然而各种机制都是有利有弊的,这些都是利弊权衡后的结果,对于内存数据库 Redis 而言,当系统每次分配的物理页不再是 4KB 小粒度的,而是 2MB 的内存大页进行内存的分配,在对 Redis 进行内存分配的时候,相同大小的内存需要执行内存分配的次数减少了,但是当 Redis 实例进行数据持久化的时候,生成 RDB 快照的时候为了避免负责执行生成 RDB 文件的子进程读取的数据保持一致性,又可以让 Redis 实例在持久化期间仍然可以继续执行客户端发送的读写请求,因此就使用 COW 写时复制机制,选择将需要修改的键值对所在的物理页进行拷贝,之后在更新主线程内存的页表中该数据所对应的物理页的信息,这样就可以在拷贝的物理页上进行写操作,又可以避免对生成 RDB 文件过程带来数据不一致的风险,但是由于操作系统是按照内存大页的方式给 Redis 实例分配内存,因此当 Redis 实例需要操作的键值对实际占用的内存仅仅只有 100B,但却要因此在内存中复制 2MB 大小的物理页中的数据,若当进行持久化的时候,Redis 实例本身就需要支持的大量的写操作,此时就会因为需要操作的键值对数量比较多,因此需要进行复制拷贝的物理页的数量比较多,内存大页机制使得需要复制拷贝的数据比较多,这就直接影响到了 Redis 主线程正常的访存操作,降低了 Redis 实例的性能,因此如果发现 Redis 实例运行变得很慢,发现由于 Redis 实例需要为大量执行大量的写操作,并且此时操作系统使用的是内存大页机制,导致在 Redis 在进行数据持久化的时候使得主线程被阻塞,那么关闭 Redis 实例运行所在操作系统的内存大页机制。