Get 所有 Redis 性能问题分析手段

  • Redis 性能的基本面

  • 优化网络延时

  • 警惕执行时间长的操作

  • 优化数据结构、使用正确的算法

  • 考虑操作系统和硬件是否影响性能

  • 考虑持久化带来的开销

  • 使用分布式架构 —— 读写分离、数据分片

Redis 性能的基本面

  • 单线程
  • 多路 I/O 复用机制
    • 一个指令 1 微秒
    • 单核 CPU 一秒就能处理 1 百万个指令(大概对应着几十万个请求)

优化网络延时

  • 使用 unix 进程间通信,如果单机部署
    • 使用 Unix 进程间通讯来请求 Redis 服务,速度比 localhost 局域网(学名 loopback)更快
  • 使用 multi-key 指令合并多个指令,减少请求数,如果有可能的话
    • 如两个 GET key 可以用 MGET key1 key2 合并
  • 使用 transaction、script 合并 requests 以及 responses
    • MULTI/EXEC
    • Script
  • 使用 pipeline 合并 response

警惕执行时间长的操作

  • 禁止 KEYS *LRANGE mylist 0 -1
    • rename-command KEYS ''来禁止使用这个耗时的指令
  • 慢指令
    • SLOWLOG GET count 多长算长,可以通过在 redis.conf 中设置 slowlog-log-slower-than 来定义
    • DEL DEL 一个大的 object 时候,回收相应的内存可能会需要很长时间(甚至几秒),所以,建议用 DEL 的异步版本:UNLINK .会启动一个新的 thread 来删除目标 key,而不阻塞原来的线程。
    • EXPIREAT警惕使用这个指令,可能使 keys 同时过期
    • lazyfree-lazy-expire yes 把 keys 的过期删除操作变为异步的

优化数据结构、使用正确的算法

一种数据类型(比如 string,list)进行增删改查的效率是由其底层的存储结构决定的。

我们在使用一种数据类型时,可以适当关注一下它底层的存储结构及其算法,避免使用复杂度太高的方法。举两个例子:

  1. ZADD 的时间复杂度是 O(log(N)),这比其他数据类型增加一个新元素的操作更复杂,所以要小心使用。

  2. 若 Hash 类型的值的 fields 数量有限,它很有可能采用 ziplist 这种结构做存储,而 ziplist 的查询效率可能没有同等字段数量的 hashtable 效率高,在必要时,可以调整 Redis 的存储结构。

除了时间性能上的考虑,有时候我们还需要节省存储空间。比如上面提到的 ziplist 结构,就比 hashtable 结构节省存储空间(Redis Essentials 的作者分别在 hashtable 和 ziplist 结构的 Hash 中插入 500 个 fields,每个 field 和 value 都是一个 15 位左右的字符串,结果是 hashtable 结构使用的空间是 ziplist 的 4 倍。)。但节省空间的数据结构,其算法的复杂度可能很高。所以,这里就需要在具体问题面前做出权衡。

考虑操作系统和硬件是否影响性能

  • CPU:Intel 多种 CPU 都比 AMD 皓龙系列好
  • 虚拟化:实体机比虚拟机好,主要是因为部分虚拟机上,硬盘不是本地硬盘,监控软件导致 fork 指令的速度慢(持久化时会用到 fork),尤其是用 Xen 来做虚拟化时。
  • 内存管理:在 linux 操作系统中,为了让 ranslation lookaside buffer 即 TLB,能够管理更多内存空间(TLB 只能缓存有限个 page),操作系统把一些 memory page 变得更大,比如 2MB 或者 1GB,而不是通常的 4096 字节,这些大的内存页叫做 huge pages。同时,为了方便程序员使用这些大的内存 page,操作系统中实现了一个 transparent huge pages(THP)机制,使得大内存页对他们来说是透明的,可以像使用正常的内存 page 一样使用他们。但这种机制并不是数据库所需要的,可能是因为 THP 会把内存空间变得紧凑而连续吧,就像 mongodb 的文档[11]中明确说的,数据库需要的是稀疏的内存空间,所以请禁掉 THP 功能。Redis 也不例外,但 Redis 官方博客上给出的理由是:使用大内存 page 会使 bgsave 时,fork 的速度变慢;如果 fork 之后,这些内存 page 在原进程中被修改了,他们就需要被复制(即 copy on write),这样的复制会消耗大量的内存(毕竟,人家是 huge pages,复制一份消耗成本很大)。所以,请禁止掉操作系统中的 transparent huge pages 功能。
  • 交换空间:当一些内存 page 被存储在交换空间文件上,而 Redis 又要请求那些数据,那么操作系统会阻塞 Redis 进程,然后把想要的 page,从交换空间中拿出来,放进内存。这其中涉及整个进程的阻塞,所以可能会造成延时问题,一个解决方法是禁止使用交换空间(Redis Essentials 中如是建议,如果内存空间不足,请用别的方法处理)。

考虑持久化带来的开销

  • RDB(全量持久化):
    • 原进程 fork 出来一个子进程, fork 过程中redis 是无法处理请求的.要使用合理的 RDB 持久化的时间间隔,不要太频繁。
  • AOF 增量持久化:
    • 会调用两个系统调用,一个是 write(2),同步完成,一个是 fsync(2),异步完成。
  • Redis 允许三种配置,选用哪种取决于你对备份及时性和性能的平衡:
  1. always:当把 appendfsync 设置为 always,fsync 会和客户端的指令同步执行,因此最可能造成延时问题,但备份及时性最好。

  2. everysec:每秒钟异步执行一次 fsync,此时 redis 的性能表现会更好,但是 fsync 依然可能阻塞 write,算是一个折中选择。

  3. no:redis 不会主动出发 fsync (并不是永远不 fsync,那是不太可能的),而由 kernel 决定何时 fsync

使用分布式架构 —— 读写分离、数据分片

  1. 把慢速的指令发到某些从库中执行

  2. 把持久化功能放在一个很少使用的从库上

  3. 把某些大 list 分片

Redis Cluster 批量操作严格要求数据在同一个slot上

参考资料

  1. 多路 I/O 复用机制: https://redis.io/topics/clients#how-client...
  2. 博客: https://redis.io/topics/latency#i39ve-litt...
  3. 详细文档,本人只是在自己理解上做了裁剪
本作品采用《CC 协议》,转载必须注明作者和本文链接
快乐就是解决一个又一个的问题!
CrazyZard
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!