Redis性能测试工具redis-benchmark使用详解

Redis包含一个名为redis-benchmark的性能测试工具,它可以模拟N个客户端同时向Redis发送M条查询命令的应用场景(这个工具类似于Apache的ab工具)。在下文中,我们会在Linux主机中运行一次基准测试,然后你可以查看完整的测试输出结果。

redis-benchmark工具的使用方法如下所示:

  1. redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

redis-benchmark工具支持以下选项:

  • -h <hostname>
    服务器的主机名(默认值为127.0.0.1)。

  • -p <port>
    服务器的端口号(默认值为6379)。

  • -s <socket>
    服务器的套接字(会覆盖主机名和端口号)。

  • -a <password>
    登录Redis时进行身份验证的密码。

  • -c <clients>
    并发的连接数量(默认值为50)。

  • -n <requests>
    发出的请求总数(默认值为100000)。

  • -d <size>
    SET/GET命令所操作的值的数据大小,以字节为单位(默认值为2)。

  • –dbnum <db>
    选择用于性能测试的数据库的编号(默认值为0)。

  • -k <boolean>
    1=保持连接;0=重新连接(默认值为1)。

  • -r <keyspacelen>
    SET/GET/INCR命令使用随机键,SADD命令使用随机值。通过这个选项,基准测试会将参数中的__rand_int__字符串替换为一个12位的整数,这个整数的取值范围从0到keyspacelen-1。每次执行一条命令的时候,用于替换的整数值都会改变。通过这个参数,默认的测试方案会在指定范围之内尝试命中随机键。

  • -P <numreq>
    使用管道机制处理<numreq>条Redis请求。默认值为1(不使用管道机制)。

  • -q
    静默测试,只显示QPS的值。

  • –csv
    将测试结果输出为CSV格式的文件。

  • -l
    循环测试。基准测试会永远运行下去。

  • -t <tests>
    基准测试只会运行列表中用逗号分隔的命令。测试命令的名称和结果输出产生的名称相同。

  • -I
    空闲模式。只会打开N个空闲的连接,然后等待。

在启动基准测试之前,你必须确保Redis服务器正在运行。典型的示例命令如下所示:

  1. redis-benchmark -q -n 100000

这个工具非常易于使用,你还可以编写你自己的基准测试。虽然你可以任意指定基准测试的参数,但还是要避免一些陷阱。

一、只运行一个测试子集

当你使用redis-benchmark工具时,你不需要每次都运行所有的默认测试项。最简单的方式就是只选择一个测试项的子集,你可以使用-t选项指定子集,如以下示例:

  1. redis-benchmark -t set,lpush -n 100000 -q

运行结果如下图所示:

每秒请求数

在上述的示例中,我们请求Redis只以静默的方式运行针对于SETLPUSH命令的测试项(请参考-q开关的用法)。

你还可以直接为基准测试指定需要测试的命令,如以下示例:

  1. redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')"

运行结果如下图所示:

为基准测试直接指定命令

二、选择键空间的大小

在默认情况下,基准测试会偶然命中单个键。在使用Redis时,由于它是一种内存系统,因此这种假想的基准测试和真实的工作负载之间的差别其实并不大。然而,如果使用一个较大的键空间,那么基准测试可能会偏重于缓存命中率,通常能够模拟一个更加真实的工作负载。

你可以使用-r开关指定基准测试的键空间的大小。例如,如果你的基准测试需要运行100W次SET操作,每次操作都会在容量为10W的键空间中随机选取一个键,那么你可以使用以下命令示例:

  1. redis-cli flushall
  2. redis-benchmark -t set -r 100000 -n 1000000
  3. redis-cli dbsize

运行结果如下图所示:

指定键空间大小

三、使用管道机制

在默认情况下,每个客户端(如果没有使用-c选项指定客户端的数量,那么基准测试会模拟50个客户端)只会在收到前一个命令的响应信息之后,才会发送下一个命令。这就意味着,服务器将需要处理每个读取调用,这样才能读取每个客户端发来的操作命令。除此之外,还要付出网络往返时间(RTT)的代价。

Redis支持管道机制,可以参考《Redis的请求/响应协议和往返时间详解》。Redis可以一次性发送多条命令,实际的应用程序经常会利用这项功能特性。Redis的管道机制能够极大地提高单台服务器每秒钟收发和处理的操作总量。

下面有一个在VMware虚拟机(1核2G内存)中执行的示例基准测试,使用管道机制一次性收发16条命令:

  1. redis-benchmark -n 1000000 -t set,get -P 16 -q

运行结果如下图所示:

使用管道机制的基准测试

使用管道机制能够明显地提高缓存系统的性能。

四、陷阱和误解

首先有一点是显而易见的:任何有效的基准测试都需要遵循一个指导原则,那就是你只能比较相同类型的系统。例如,在相同工作负载的条件下,你可以比较不同版本的Redis系统的性能。相反,在Redis系统版本相同的条件下,你可以比较不同的配置选项的性能。如果你计划将Redis和其他系统进行比较,那么你必须评估功能和技术之间的差异性,并且仔细考虑这些差异性带来的影响。

  • Redis是一种服务器:所有的命令都会有网络或IPC(进程间通信)的往返开销。将Redis和嵌入式数据库(例如,SQLite、Berkeley DB、Tokyo/Kyoto Cabinet,等等)进行比较是毫无意义的。因为,大多数操作的开销基本上都耗费在网络/协议的管理上。

  • Redis所有的通用命令在执行完之后都会返回一个应答信息。一些其他的数据存储方案没有这样的行为(例如,MongoDB并不会为写入操作返回任何应答信息)。将Redis和涉及单向查询的存储方案进行比较,结果几乎没有参考意义。

  • 反复执行同步的Redis命令是非常盲目的,因为这样并不是对Redis自身进行基准测试,实际上是对你的网络(或IPC)进行延时性测试。如果你真的想要测试Redis系统,那么你需要使用多个连接(例如,使用redis-benchmark工具),且/或使用管道机制来聚集若干条命令,且/或使用多个线程或进程。

  • Redis是一种内存数据库,它具有一些可选的持久化选项。如果你想要将Redis和事务型数据库(例如,MySQL、PostgreSQL,等等)进行比较,那么你应当考虑启用AOF持久化,并且选用合适的fsync调用策略。

  • Redis是一种单线程的服务器。这种设计方式并不能受益于多核CPU平台带来的性能提升。如果有需要的话,建议在多核CPU的平台中启动多个Redis实例,这样便能充分利用硬件平台的性能。因此,将单个Redis实例和多线程数据库进行比较,实际上是非常不公平的。

一个常见的误解便是,使用redis-benchmark工具得到的Redis性能数据只是更好看而已,redis-benchmark工具达到的吞吐量看起来有点做作,真实的应用程序不可能达到这么高的吞吐量。这实际上完全是错误的!

如果你想要获取某些性能数据,那么redis-benchmark程序是一种快速和有效的方法,你可以通过这些性能数据评估某个给定的硬件平台上的Redis实例的性能。然而,在默认情况下,这个工具并不能体现Redis实例所能承担的最大吞吐量。实际上,通过使用管道机制和某种较为快速的客户端(例如,hiredis),要编写一个能够产生比redis-benchmark更多吞吐量的基准测试工具也是非常简单的。redis-benchmark的默认行为就是只利用并发机制达到一定的吞吐量(换言之,它会创建若干个连接至Redis服务器的客户端连接)。这个工具完全没有使用管道机制和并行机制(每个连接最多只有一个挂起的查询,而且不支持多线程操作)。

如果想要在基准测试中使用管道模式(并且获得更高的吞吐量),那么你需要显式地使用-P选项。请注意,这是一种具有现实意义的行为特性,因为有很多基于Redis的应用程序正在积极地使用管道机制来改善性能。

最后,当你比较多个不同的数据存储方案时,基准测试应当使用相同的操作,并且以相同的方式工作。注意,将redis-benchmark的测试结果和另一种不同的基准测试程序的测试结果(及其外推结果)进行比较,绝对是毫无意义的。

例如,如果Redis和memcached都以单线程模式工作,那么就可以比较GET/SET操作的性能。Redis和memcached都是基于内存的数据存储方案,它们在协议层次几乎以相同的方式工作。假设它们的基准测试工具分别以相同的方式(管道模式)聚集查询操作,并且使用的客户端连接数较为相似,那么比较结果是有实际意义的。

Redis(antirez)和memcached(dormando)的开发者之间的对话可以阐述这个完美的示例。

antirez 1 – On Redis, Memcached, Speed, Benchmarks and The Toilet

dormando – Redis VS Memcached (slightly better bench)

antirez 2 – An update on the Memcached/Redis benchmark

最后你会发现,一旦考虑到所有的技术方面,这两种方案之间的差距其实并不是很大。请注意,在这些基准测试结束之后,Redis和memcached将来都会进行一定的优化。

最后,当你对非常高效的数据存储服务器(很显然,类似于Redis或memcached的存储方案就属于这一类)进行基准测试时,服务器的工作负载可能很难饱和。有时候,性能瓶颈在客户端一侧,而不在服务端一侧。在这种情况下,就必须修正客户端(即基准测试程序自身)的性能问题,或者扩充客户端的数量,这样才能达到最大的吞吐量。

五、影响Redis性能的因素

有多种因素会对Redis性能产生直接后果。因为这些因素可能改变任何基准测试的结果,所以我们会在此处对它们进行详细描述。然而,请注意,虽然Redis实例通常会在一台配置较低、没有经过优化的主机上运行,但是仍然能为大多数应用程序提供足够好的性能。

  • 网络带宽和延迟时间通常会对Redis性能造成直接影响。在启动基准测试之前,你可以使用ping程序来检查客户端和服务端之间的延迟时间是否正常,这是一种很好的实用方法。对于带宽来说,估算吞吐量时,通常以Gbit/s为单位,并且会和网络的理论带宽进行比较。例如,如果基准测试将数据长度设置为4 KB的字符串,操作速率设置为100000 q/s,那么实际将消耗3.2 Gbit/s的带宽,借此可以推断,一条10 Gbit/s的网络链路能够承担这种带宽消耗,而一条1 Gbit/s的网络链路则不能。在很多真实的应用场景中,Redis的吞吐量首先会受限于网络性能,然后才会受限于CPU性能。如果要将若干个高吞吐量的Redis实例合并部署在一台服务器上,那么可以考虑使用一块10 Gbit/s的网卡,或者通过TCP/IP聚合(bonding),将多块1 Gbit/s的网卡并联起来。

  • CPU性能是另一个非常重要的因素。因为Redis是单线程的,所以它能够受益于具有较大缓存(cache)的高频CPU,而不会受益于多核CPU。在这场较量中,Intel的CPU是目前的赢家。在进行Redis基准测试时,对比AMD的Opteron系列的CPU和Intel的Nehalem EP/Westmere EP/Sandy Bridge系列的CPU,前者的性能只有后者的一半,这种现象并不少见。当客户端和服务端在相同的主机中运行时,CPU性能是影响redis-benchmark测试成绩的重要因素之一。

  • RAM的速度和内存带宽看起来对全局性能没有太大影响,特别是对于小对象来说。对于大对象来说(>10 KB),性能影响可能会变得比较明显。通常,为了优化Redis性能而特意购买昂贵的高速内存模组是非常不划算的。

  • 如果使用相同的硬件,那么在虚拟机上运行Redis的速度会比不使用虚拟化的物理机更慢。如果你有机会在一台物理机上运行Redis示例,那么这是最好的选择。但是,这并不意味着Redis在虚拟化环境中的运行速度会很慢,它的性能仍然非常好。在虚拟化环境中,你可能遇到的大多数严重的性能问题都可以归结为以下几个因素:超量配置;使用延迟较高的非本地磁盘;或者,使用老版本的虚拟机管理程序(hypervisor),通常它们运行fork系统调用的速度会比较慢。

  • 当服务端和客户端的基准测试程序都在同一台主机上运行时,既可以使用TCP/IP的回环接口,又可以使用Unix的域套接字。根据不同的平台,Unix的域套接字的吞吐量可能会比TCP/IP的回环接口要高大约50%左右(例如,使用Linux系统)。redis-benchmark工具默认会使用TCP/IP的回环接口。

  • 当经常使用管道机制时,Unix的域套接字的性能增益相比TCP/IP的回环接口,会倾向于降低。

  • 当客户端通过以太网访问Redis服务器,并且操作的数据大小一直小于以太网数据包的大小(大约1500字节)时,通过管道机制聚集命令能够明显提高效率。实际上,处理10字节、100字节或1000字节的查询,几乎会产生相同的吞吐量。可以参考下图:
    每种数据长度对应的吞吐量

  • 在拥有多个CPU插槽的服务器上,Redis的性能会变成依赖于NUMA(非统一内存访问架构)的配置和进程的位置。最明显的效果便是redis-benchmark的测试结果看起来并不确定,因为客户端和服务端的进程是随机分布在不同的CPU核心上的。如果想要获得确定的结果,那么你需要使用进程放置工具(在Linux平台上,可以使用tasksetnumactl工具)。最有效的组合就是确保客户端和服务端分布在相同CPU的不同核心上,这样便能受益于L3缓存。下图展示了3种服务器CPU(AMD Istanbul, Intel Nehalem EX, and Intel Westmere)上的基准测试结果,使用长度为4 KB的数据,测试的命令为SET命令,并且还使用不同的进程放置策略。请注意,这次基准测试并不是为了比较各种CPU型号之间的性能差距(因此,本文不会透露CPU的具体型号和频率)。
    SET命令的吞吐量

  • 使用高级配置时,客户端连接的数量也是一个重要的因素。因为Redis基于epoll/kqueue事件机制,所以它的事件循环的可扩展性很高。Redis的基准测试所使用的连接数已经超过60000个连接,在这些情况下,Redis仍然能够承受50000 q/s的速率。根据经验,具有30000个连接的Redis实例的吞吐量只能达到具有100个连接的Redis实例的吞吐量的一半。下面的示例图显示了Redis实例的吞吐量和连接数之间的对应关系:
    每秒请求次数

  • 使用高级配置时,通过调整网卡的配置和关联的中断,你可以获得更高的吞吐量。如果在网卡的Rx/Tx队列和CPU核心之间设置关联性,并且激活RPS(Receive Packet Steering,接收数据包转向)支持,那么就能够获得最佳的吞吐量。当使用大对象时,巨型帧(jumbo frame)也能够带来一定的性能提升。

  • 根据平台的不同,Redis可以在编译时指定使用不同的内存分配器(libc malloc、jemalloc、tcmalloc),这些内存分配器具有不同的行为,例如:原始速度、内部和外部碎片。如果你没有亲自编译Redis,那么你可以使用INFO命令来检查mem_allocator字段。请注意,大多数的基准测试都不会运行足够长的时间,这样便不能产生明显的外部碎片(相反,生产环境的Redis实例可能会产生明显的外部碎片)。

六、其他需要考虑的因素

任何基准测试都有一个重要的目标,那就是获得能够再现的测试结果。因此,我们可以将这些测试结果和其他的测试结果进行比较。

  • 你应当尽可能地在隔离的硬件环境中尝试运行基准测试,这是一种很好的习惯。如果这么做是不可能的,那么就必须对这个系统进行监控,检查是否有某些外部活动对基准测试造成影响。

  • 某些硬件配置(台式机和笔记本确定有这项功能,除此之外,某些服务器也支持这项功能)具有可变化的CPU核心频率的机制。控制这种机制的策略可以在操作系统的层面进行设置。某些型号的CPU要比其他CPU更有主动性,能够随着工作负载,动态地调整CPU的频率。为了获得能够重现的结果,你最好将基准测试中涉及的所有CPU核心都固定设置为最高的运行频率。

  • 你应当根据基准测试的结果估算系统内存的大小,这是非常重要的一点。这个系统必须有足够的RAM,并且不能使用交换机制。在Linux上,不要忘记将overcommit_memory参数设置正确。请注意,32位和64位的Redis实例的内存访问空间是不同的。

  • 如果你计划在基准测试期间使用RDB持久化或AOF持久化,请检查系统中是否有其他的I/O活动。请不要将RDB快照或AOF日志存放在NAS或NFS共享存储上,也不要存放在任何可能会影响网络带宽和/或延迟时间的其他设备上(例如,Amazon EC2上的EBS)。

  • 将Redis的日志级别(loglevel参数)设置为warning或notice。请不要将产生的日志文件存放在远程文件系统上。

  • 请不要使用可能会改变基准测试结果的监控工具。例如,使用INFO命令定期采集统计数据是非常好的,但是,若使用MONITOR命令,则会明显地影响待测量的性能数据。