gitbook/Redis源码剖析与实战/docs/414582.md
2022-09-03 22:05:03 +08:00

224 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 加餐1 | Redis性能测试工具的使用
你好,我是蒋德钧。
咱们的课程已经更新过半了在前面几个模块里我带你从源码层面分别了解和学习了Redis的数据结构、事件驱动框架和缓存算法的具体实现过程相信你现在对Redis的数据类型和运行框架有了更加深入的认识。不过阅读源码确实是一个比较烧脑的任务需要你多花些时间钻研。所以今天这节课我们就通过加餐来聊聊相对比较轻松的话题Redis的性能测试工具。
我们在使用Redis的时候经常会遇到需要评估Redis性能的场景。比如当我们需要为部署Redis实例规划服务器配置规格时或者当需要根据工作负载大小决定Redis实例个数的时候我们都需要了解Redis实例的运行性能。
那么这节课我就来和你聊聊Redis的性能测试工具redis-benchmark并带你了解下redis-benchmark的使用方法和基本实现。掌握了今天学习的内容之后你既可以把redis-benchmark用在需要评估Redis性能的场景中而且你还可以对redis-benchmark进行二次开发添加新的功能特性来满足实际业务场景中的需求。
下面我们就先来看看redis-benchmark的使用。
## redis-benchmark的使用
redis-benchmark这个工具是在Redis源码的[redis-benchmark.c](https://github.com/redis/redis/tree/5.0/src/redis-benchmark.c)文件中实现的。这个工具实际上是模拟多个客户端给Redis server发送请求。这些请求可以包括Redis对不同数据类型的多种操作比如对String类型的GET、SET操作对List类型的LPUSH、LPOP操作等等。在测试的过程中redis-benchmark工具会记录每个请求的响应时间最后会把请求响应时间的分布以及请求吞吐率统计并打印出来。
现在我们可以先运行一下这个工具一是对redis-benchmark有个直观的印象二是可以来学习下这个工具的使用。
我在一台启动了Redis server的机器上直接执行redis-benchmark命令如下所示
```
./redis-benchmark
```
然后我们就可以得到性能测试结果。以下给出的代码片段只是展示了一部分的测试结果是所测试的Redis server执行SET和GET两个命令的性能结果。
```
...
====== SET ======
100000 requests completed in 1.43 seconds
50 parallel clients
3 bytes payload
keep alive: 1
95.80% <= 1 milliseconds
99.04% <= 2 milliseconds
99.37% <= 3 milliseconds
99.50% <= 4 milliseconds
99.61% <= 5 milliseconds
99.68% <= 6 milliseconds
99.84% <= 7 milliseconds
100.00% <= 8 milliseconds
100.00% <= 8 milliseconds
69832.40 requests per second
====== GET ======
100000 requests completed in 1.22 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.79% <= 1 milliseconds
99.99% <= 2 milliseconds
100.00% <= 2 milliseconds
81766.15 requests per second
...
```
现在,我们来解读下这个测试结果,主要包括了两方面的信息。
**一方面,测试结果会展示测试的命令操作,以及测试的配置。**其中测试配置包括一共发送的请求个数、使用的并发客户端个数、键值对的value大小等。在刚才运行的测试中我们没有设置任何选项所以redis-benchmark使用了默认配置。
这里我把redis-benchmark常用的配置项列在了下面的表中你可以看下。
![](https://static001.geekbang.org/resource/image/86/b4/86973efc3c3c8acf49e9f4c71217b0b4.jpg?wh=2000x1125)
其中和Redis server性能测试密切相关的选项主要是这几个
* **\-c-n选项**
我们可以增加它们的选项值从而增加给Redis server发送请求的客户端数量以及发送给Redis server的请求数量。这两个选项在对Redis server进行压力测试时是非常重要的。因为在大压力情况下Redis server通常要处理大并发客户端连接以及大量的请求通过增加这两个选项值这样的测试结果能体现Redis server本身性能以及所使用的服务器硬件配置效果。
* **\-d选项**
我们可以根据实际业务场景中的value大小来设置这个选项值。默认情况下redis-benchmark测试的value大小只有3字节而通常业务场景下value的大小从几字节、几十字节到几百字节甚至上千字节不等。
value的字节数越多对Redis server的内存访问、网络传输、RDB/AOF文件读写影响越大。所以如果我们只是使用默认配置这样测试的性能结果不一定能反映业务场景下Redis server的真实表现。
* **\-r选项**
我们可以设置访问的key的随机性。如果不设置这个选项那么redis-benchmark访问的key是相同的都是`"key:__rand_int__"`。比如,我们运行`./redis-benchmark -t set -n 1000`命令测试1000次SET操作的性能。运行完之后我们使用keys命令查看Redis数据库中的key可以看到其实这1000次SET操作都是访问同一个key也就是`"key:__rand_int__"`。这个过程如下所示。
```
./redis-benchmark -t set -n 1000
... //测试性能结果
/redis-cli keys \*
1) "key:__rand_int__"
```
当使用相同的key进行测试时这会影响到我们评估Redis server随机访问性能的效果。而且在实际业务场景中key通常是随机的所以我们在实际测试过程中也需要把-r选项使用起来。
比如,我们执行`./redis-benchmark -t set -n 1000 -r 10`命令测试1000次SET操作的性能。在这种情况下这1000次SET操作实际访问的key它们的值是在`"key:000000000000"`和`"key:000000000009"`之间,也就是说,-r选项的值N指定了key中的数字取值范围在大于等于0到小于N之间。这个过程如下所示
```
./redis-benchmark -t set -n 1000 -r 10
... //测试性能结果
./redis-cli keys \*
1) "key:000000000000"
2) "key:000000000002"
3) "key:000000000007"
4) "key:000000000003"
5) "key:000000000008"
6) "key:000000000006"
7) "key:000000000001"
8) "key:000000000004"
9) "key:000000000005"
10) "key:000000000009"
```
* **\-P选项**
我们可以通过该选项来设置Redis客户端以批处理的形式让一个请求发送多个操作给Redis server从而可以测试批处理发送操作给Redis server吞吐率带来的性能提升效果。
比如,我们执行`./redis-benchmark -t set -n 1000000`命令测试一百万次SET操作的性能。然后我们再执行`./redis-benchmark -t set -n 1000000 -P 10`命令同样测试一百万次SET操作的性能不过此时我们一个请求会发送10个操作。
下面的代码片段就展示了在这两种方式下Redis server的性能结果。你可以看到不批量发送操作的吞吐率是每秒68898个操作而每次批量发送10个操作的吞吐率是每秒375798个操作。所以批量发送操作能有效提升Redis server的性能。
```
./redis-benchmark -t set -n 1000000 -q
SET: 68898.99 requests per second
./redis-benchmark -t set -n 1000000 -q -P 10
SET: 375798.56 requests per second
```
好了了解了redis-benchmark的主要配置选项以及这其中和性能评估密切相关的选项后我们再来看下**redis-benchmark运行后包含的另一方面信息也就是测试性能结果信息**。
redis-benchmark运行后提供的性能结果包括两部分一是**操作的延迟分布**。这部分信息展示了不同百分比的操作,它们的延迟最大值。二是**server的吞吐率**也就是每秒完成的操作数。下面的代码片段就展示了我们测试1000次SET操作后的性能结果。其中98.65%的操作延迟小于等于1毫秒99.17%的操作延迟小于等于2毫秒而所有操作也就是100%操作的延迟都小于等于3毫秒。
```
./redis-benchmark -t set -n 10000
====== SET ======
10000 requests completed in 0.13 seconds
50 parallel clients
3 bytes payload
keep alive: 1
98.65% <= 1 milliseconds
99.17% <= 2 milliseconds
100.00% <= 3 milliseconds
75187.97 requests per second
```
这里你需要注意的是在redis-benchmark的测试结果中**延迟分布对于Redis来说是非常重要的信息**。因为Redis通常需要服务大量的并发客户端而以百分比统计的延迟分布可以告诉我们这其中有多少比例的操作它们的延迟较高。
为了帮助你更好地理解百分比延迟分布的作用我给你举个例子。假设redis-benchmark的测试结果显示99%的操作延迟小于等于1毫秒而所有操作也就是100%的操作延迟小于等于5毫秒那么就表明有1%的操作延迟是在1毫秒到5毫秒之间的。如果某个操作的延迟正好是5毫秒那么和其他99%的操作相比它的延迟就增加了5倍这样一来发送这个操作的客户端就会受到明显的性能影响。
我们再假设Redis server处理的请求数一共是100万个请求那么1%的操作影响的就是1万个请求。而且Redis server处理的请求越多这个影响的范围就越大。所以这个以百分比统计的延迟分布可以帮助我们更加全面地评估Redis server的性能表现。
好了到这里我们就可以通过运行redis-benchmark这个工具来了解我们所测试的Redis server处理不同请求操作的延迟分布和吞吐率了。
那么接下来我们再来了解下redis-benchmark是怎么实现的。
## redis-benchmark的实现
redis-benchmark本身可以单独运行这是因为它本身就自带main函数。我们了解它的main函数就可以了解redis-benchmark的基本实现。
它的main函数的主要执行流程可以分成三步。
**第一步**main函数设置各种配置参数的默认值比如待测试的Redis server的IP、端口号、客户端数量、value大小等等。紧接着main函数会调用parseOptions函数解析通过redis-benchmark命令传入的各项参数这就包括了我刚才给你介绍的redis-benchmark的基本配置项。
另外在这一步中main函数还会调用aeCreateEventLoop函数创建一个事件循环如下所示。redis-benchmark在实际运行时会通过这个事件循环流程来处理客户端的读写事件。
```
config.el = aeCreateEventLoop(1024*10);
```
**第二步**main函数会检查redis-benchmark命令参数中是否包含了其他命令如果有的话那么redis-benchmark工具会调用benchmark函数在redis-benchmark.c文件中来实际测试这些命令操作。
benchmark函数会调用createClient函数在redis-benchmark.c文件中创建一个客户端。然后它再调用createMissingClients函数在redis-benchmark.c文件中检查是否有多个并发客户端要创建。如果是的话createMissingClients函数也会调用createClient函数来创建剩余的客户端。
这里,**你需要注意的是**createClient函数在创建完客户端后只要redis-benchmark没有设置idle模式也就是只创建客户端而不发送请求那么它就会调用aeCreateFileEvent函数在客户端上注册写事件。这里的写事件回调函数是writeHandler在redis-benchmark.c文件中负责向Redis server发送命令操作如下所示
```
if (config.idlemode == 0)
aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
```
而writeHandler函数完成命令操作发送后会调用aeDeleteFileEvent函数将当前客户端上监听的写事件删除同时创建当前客户端上监听的读事件读事件的回调函数是readHandler在redis-benchmark.c文件中负责读取Redis server的返回结果。
```
if (sdslen(c->obuf) == c->written) {
aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
}
```
那么再回到benchmark函数中在创建完客户端后紧接着benchmark函数会调用aeMain函数进入刚才第一步中创建的事件循环流程开始处理读写事件。如果事件循环流程结束了benchmark函数调用showLatencyReport函数在redis-benchmark.c文件中打印测试结果并调用freeAllClients函数在redis-benchmark.c文件中释放所有客户端。
好了到这里你就了解了benchmark函数是如何使用事件驱动框架来完成操作测试的。
实际上如果redis-benchmark命令运行时自带了测试操作此时在main函数的第二步中在完成这些操作测试后redis-benchmark工具就运行结束了而不会再测试它的-t选项设置的命令操作了。
而如果redis-benchmark命令运行时没有自带测试操作那么main函数就会进入第三步。
在**第三步**中main函数会调用test\_is\_selected函数在redis-benchmark.c文件中判断-t选项中设置了哪些命令操作然后main函数调用benchmark函数来完成这些操作的测试。
这样一来redis-benchmark工具的基本执行流程就结束了。
## 小结
今天这节课我给你介绍了redis-benchmark工具的使用。redis-benchmark是常用的Redis性能测试工具它可以通过设置并发客户端、总操作数、value大小、key的随机性、批量发送等配置项来给Redis server施加不同的压力。
redis-benchmark工具本身提供了一些常见命令的测试比如SET、GET、LPUSH等等。这些命令的测试是redis-benchmark在它的实现文件中固定写好的。你可以在redis-benchmark.c文件中的main函数里面找到这些命令。而如果我们想要测试不在固定测试命令集中的其他命令我们可以在redis-benchmark命令的最后设置其他的Redis命令从而可以测试其他命令的性能结果。
最后我也给你介绍了redis-benchmark的基本实现。它其实是启动多个客户端向Redis server发送命令操作。这个过程中redis-benchmark使用了事件驱动框架。每当启动一个测试客户端这个客户端会在事件驱动框架中创建写事件和读事件。写事件对应了测试客户端向Redis server发送操作命令而读事件对应了测试客户端从Redis server读取响应结果。
从这里你可以看到Redis实现的事件驱动框架不仅用在server的运行过程中而且还用在了性能测试工具实现的客户端中。
## 每课一问
你在实际工作中还用过什么其他的Redis性能测试工具吗欢迎在留言区分享我们一起交流探讨。