learn.ochresign
译文 · 资料译文

Redis benchmark 使用指南

Redis 官方 redis-benchmark 文档的中文节选译文:全部参数、只跑部分测试、键空间、管道、常见误区,以及影响性能的因素。阶段 3 压测时对照着看。

译文说明原文:https://redis.io/docs/latest/operate/oss_and_stack/management/optimization/benchmarks/,作者 Redis Ltd.。原文按 CC BY-NC-SA 4.0 授权,本译文为节选翻译,有删改和补充说明,按相同授权提供,仅供非商业学习使用。

Redis 有多快?

Redis 自带一个 redis-benchmark 工具,可以模拟 N 个客户端同时发送命令、总共发送 M 个请求(和 Apache 的 ab 工具类似)。

支持的参数如下:

用法:redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests>] [-k <boolean>]
参数 说明
-h <hostname> 服务器主机名(默认 127.0.0.1)
-p <port> 服务器端口(默认 6379)
-s <socket> 服务器 Unix socket(会覆盖 host 和 port)
-a <password> Redis 认证密码
--user <username> 发送 ACL 风格的 AUTH username pass,需要配合 -a
-c <clients> 并发连接数(默认 50)
-n <requests> 请求总数(默认 100000)
-d <size> SET/GET 的值大小,单位字节(默认 3)
--dbnum <db> 选择指定编号的数据库(默认 0)
--threads <num> 开启多线程模式
--cluster 开启集群模式
--enable-tracking 测试开始前发送 CLIENT TRACKING on
-k <boolean> 1 表示保持连接,0 表示每次重连(默认 1)
-r <keyspacelen> SET/GET/INCR 使用随机 key,SADD 使用随机值,ZADD 使用随机成员和分数。参数里的 __rand_int__ 会被替换成 0 到 keyspacelen-1 之间的 12 位数字,每执行一次命令替换一次
-P <numreq> 管道模式,每次发送 numreq 个请求(默认 1,即不使用管道)
-e 服务器返回错误时打印到标准输出(每秒最多显示 1 条)
-q 安静模式,只显示每秒请求数
--precision 延迟输出保留的小数位数(默认 0)
--csv 以 CSV 格式输出
-l 循环模式,一直运行
-t <tests> 只运行逗号分隔的这几个测试,测试名和输出里显示的名字相同
-I 空闲模式,只打开 N 个空闲连接并等待

运行之前,必须先有一个正在运行的 Redis 实例。一个典型的例子:

redis-benchmark -q -n 100000

这个工具很好用,你也可以自己写压测程序,但和所有压测一样,有一些坑需要避开。

译注:你的 mini-Redis 兼容 RESP,所以可以直接用 -p 指向它的端口来压测。用 -t 只测你已经实现的命令,否则它会发送你不支持的命令。

只运行部分测试

不需要每次都跑全部默认测试。最简单的做法是用 -t 选择要跑的测试:

$ redis-benchmark -t set,lpush -n 100000 -q
SET: 180180.17 requests per second, p50=0.143 msec
LPUSH: 188323.91 requests per second, p50=0.135 msec

上面的例子在安静模式(-q)下只测试了 SET 和 LPUSH 两个命令。

也可以直接指定要压测的命令:

$ redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"
script load redis.call('set','foo','bar'): 69881.20 requests per second

选择键空间的大小

默认情况下,压测只针对同一个 key。对 Redis 这种内存数据库来说,这种合成测试和真实场景的差别不算太大;但使用更大的键空间,可以制造缓存未命中,更接近真实负载。

这要用 -r 参数。比如,要执行一百万次 SET,每次从十万个可能的 key 中随机选一个:

$ redis-cli flushall
OK

$ redis-benchmark -t set -r 100000 -n 1000000
====== SET ======
  1000000 requests completed in 13.86 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

99.76% <= 1 milliseconds
99.98% <= 2 milliseconds
100.00% <= 3 milliseconds
100.00% <= 3 milliseconds
72144.87 requests per second

$ redis-cli dbsize
(integer) 99993

使用管道

默认情况下,每个客户端(不用 -c 指定时是 50 个)都要等收到上一条命令的回复,才会发送下一条。这意味着服务器很可能每读一条命令都要调用一次 read,而且每条命令都要付出一次网络往返(RTT)。

Redis 支持管道,可以一次发送多条命令,真实应用也经常这样做。管道能大幅提高服务器每秒能处理的操作数。

下面是在一台 MacBook Air 11 英寸上、以 16 条命令为一组使用管道的结果:

$ redis-benchmark -n 1000000 -t set,get -P 16 -q
SET: 1536098.25 requests per second, p50=0.479 msec
GET: 1811594.25 requests per second, p50=0.391 msec

使用管道后,性能显著提升。

误区和陷阱

第一条显而易见:有意义的压测,黄金法则是只比较同类的东西。比如在同样的负载下比较 Redis 的不同版本,或者比较同一个版本的不同配置。如果要拿 Redis 和别的产品比较,就必须评估并考虑它们在功能和技术上的差异。

  • Redis 是一个服务器,所有命令都涉及网络往返。嵌入式数据存储则没有网络和协议开销。和这类产品比较时,应该比较命令的执行时间,而不是端到端的耗时。
  • Redis 对所有普通命令都会返回确认,而有些数据存储不会。拿 Redis 和只做单向请求的存储比较,参考价值有限。
  • 简单地循环调用同步的 Redis 命令,测出来的并不是 Redis 本身,而是你的网络(或进程间通信)延迟和客户端库自身的延迟。要真正测试 Redis,需要使用多个连接(像 redis-benchmark 那样),和/或用管道合并多条命令,和/或使用多线程、多进程。
  • Redis 是内存数据库,持久化是可选的。如果要和事务型数据库(MySQL、PostgreSQL 等)比较,应该打开 AOF,并选择合适的 fsync 策略。
  • 从命令执行的角度看,Redis 基本是单线程的(新版 Redis 实际上会把线程用于其他一些工作)。它的设计并不是为了利用多核 CPU;需要用满多核时,应该启动多个 Redis 实例来横向扩展。拿单个 Redis 实例和多线程的数据存储比较,并不公平。

一个常见的误解是:redis-benchmark 是为了让 Redis 的性能看起来很漂亮而设计的,它测出的吞吐量是人为的,真实应用达不到。事实并非如此。

redis-benchmark 能快速给出一组数字,用来评估 Redis 实例在某台硬件上的性能。但默认情况下,它测出的并不是 Redis 能承受的最大吞吐量。实际上,用管道加上一个快速的客户端(比如 hiredis),很容易写出比 redis-benchmark 吞吐量更高的程序。redis-benchmark 默认只通过并发(也就是建立多个连接)来获得吞吐量;除非用 -P 明确开启,否则它完全不使用管道或其他并行手段(每个连接最多只有一个未完成的请求,也不使用多线程)。所以,如果一边运行 redis-benchmark,一边在后台触发 BGSAVE,得到的数字更接近最差情况,而不是最好情况。

要用管道模式压测以获得更高的吞吐量,需要显式使用 -P 参数。这依然是贴近现实的,因为很多基于 Redis 的应用都在积极使用管道来提高性能。不过,管道长度应该设置得和你的应用实际能用到的平均管道长度差不多,这样得出的数字才有参考价值。

最后,要比较多个数据存储时,压测应当执行同样的操作、以同样的方式工作。把 redis-benchmark 的结果和另一个压测程序的结果放在一起比较、再推断结论,是毫无意义的。

译注:这对你写简历很重要。“达到原生 Redis 的 X%”这句话,必须是同一台机器、同样的参数(-c、-n、-P、-d)、同样的命令下测出来的,面试官一定会问。

影响 Redis 性能的因素

有很多因素会直接影响 Redis 的性能,进而影响任何压测的结果。不过要说明的是,一个运行在普通、未经调优的机器上的 Redis 实例,性能通常已经能满足大多数应用。

  • 网络带宽和延迟通常会直接影响性能。压测之前,最好先用 ping 快速检查一下客户端和服务器之间的延迟是否正常。至于带宽,可以估算一下吞吐量折合多少 Gbit/s,再和网络的理论带宽比较。比如,以每秒 100000 次的速度写入 4 KB 的字符串,大约要消耗 3.2 Gbit/s 的带宽,10 Gbit/s 的网络可以承受,1 Gbit/s 的网络就不行了。在很多真实场景中,Redis 的吞吐量远在受限于 CPU 之前,就已经受限于网络了。
  • CPU 是另一个非常重要的因素。由于是单线程的,Redis 更偏好主频高、缓存大的 CPU,而不是核心多的 CPU。客户端和服务器运行在同一台机器上时,CPU 就是 redis-benchmark 的瓶颈。
  • 内存速度和内存带宽对整体性能的影响似乎没那么关键,尤其是对小对象而言。对于大对象(大于 10 KB),影响可能会变得明显。通常,为了优化 Redis 而购买昂贵的高速内存条并不划算。
  • 虚拟机:在同样的硬件上,Redis 在虚拟机里运行要比直接运行慢。如果有条件,最好在物理机上运行。但这并不意味着 Redis 在虚拟化环境里很慢,它的性能仍然非常好;虚拟化环境中遇到的大多数严重性能问题,都来自超售、高延迟的非本地磁盘,或者 fork 系统调用实现很慢的老旧虚拟化软件。
  • 本机回环和 Unix socket:服务器和压测程序在同一台机器上时,既可以用 TCP/IP 回环,也可以用 Unix domain socket。视平台而定,Unix domain socket 的吞吐量可以比 TCP/IP 回环高约 50%(比如在 Linux 上)。redis-benchmark 默认使用 TCP/IP 回环。当大量使用管道(管道很长)时,Unix domain socket 相对于 TCP/IP 回环的优势会逐渐缩小。
  • 数据包大小:通过以太网访问 Redis 时,如果数据大小保持在以太网包大小(约 1500 字节)以内,用管道合并命令的效果特别好。实际上,处理 10 字节、100 字节和 1000 字节的请求,吞吐量几乎相同。
  • NUMA:在多 CPU 插槽的服务器上,Redis 的性能会受到 NUMA 配置和进程所在位置的影响。最明显的表现是 redis-benchmark 的结果看起来不稳定,因为客户端和服务器进程被随机分配到不同的核心上。想得到稳定的结果,需要用进程绑定工具(Linux 上的 taskset 或 numactl)。最高效的组合是把客户端和服务器放在同一颗 CPU 的两个不同核心上,以便共享 L3 缓存。
  • 连接数:在高端配置下,客户端连接数也是一个重要因素。Redis 的事件循环基于 epoll/kqueue,扩展性很好。Redis 曾经在超过 60000 个连接的情况下进行过压测,仍然能维持每秒 50000 次请求。根据经验,30000 个连接的实例,吞吐量只有 100 个连接时的一半左右。
  • 网卡调优:在高端配置下,调整网卡及相关中断的配置可以获得更高的吞吐量。把网卡的收发队列和 CPU 核心绑定,并开启 RPS(Receive Packet Steering),可以达到最佳吞吐量。传输大对象时,巨型帧(Jumbo frames)也可能带来性能提升。
  • 内存分配器:根据平台的不同,Redis 可以链接不同的内存分配器(libc malloc、jemalloc、tcmalloc),它们在速度、内部碎片和外部碎片方面的表现各不相同。如果 Redis 不是你自己编译的,可以用 INFO 命令查看 mem_allocator 字段。注意,大多数压测运行的时间都不够长,不会产生明显的外部碎片(生产环境中的 Redis 实例则不同)。

译注:原文中的几张性能图表、“其他需要考虑的事项”、各版本裸机压测结果和其他压测工具几节没有翻译,需要时请看原文。