Redis 6.0 多线程性能测试结果及分析

  单线程的Redis一向以简洁高效著称,但也有其阿喀琉斯之踵:阻塞!单个线程在最容易产生瓶颈的网络读写(Redis大key,也包括其他一些重量级的操作sort/sunion/zunionstore/sdiff,集中性的expired key清理,内存溢出的maxmemory-policy策略等)请求完成之前,其他所有请求都将会被阻塞,严重影响其效率,因此Redis的多线程呼声就越来越高。由于是基于内存的操作延迟非常低,所以即便是单线程模式下CPU资源也不会是的瓶颈。最容易出现瓶颈的还是网络IO操作。在Redis 6.0开始支持多线程之后,所谓的多线程也只是socket层面的多线程,核心的内存读写还是单线程模式。

弄清楚了多线程的本质之后,就会有一系列的问题,多线程会比单线程有多大的提升?设置多少个线程合适?见一些大神测试过(目前全网也只有美图做过unstable版本的测试,所有的转载都是来源于这个测试),其结果也非常理想,但只是看看也不太过瘾,决定一试为快,本文将对Redis的多线程进行一个粗浅的测试验证。同时需要思考另外一个问题:面对多线程版本的Redis,和Redis cluster,该如何选择?

多线程Redis

redis 6.0 的“多线程”特性让很多标题党高潮连连,参考图片源自于:美图技术团队侵删,核心的线程(Execute Command)还是单线程,多线程是指网络IO(socket)读写的多线程化。
如下图所示,读写网络socket中的数据是可以用多个线程,所以Redis的多线程也叫做io thread,相关参数:“io-threads”。另一个参数是io-threads-do-reads,这里涉及另外一个细节:多线程IO主要用在请求完成后返回结果集的过程,也就是socket写操作,至于读socket,单线程本身采用的多路IO复用技术,也不会产生瓶颈,因此Redis给出了一个io-threads-do-reads 参数,决定读socket的时候是否启用多线程。其实io-threads-do-reads是否启用,对性能的影响并不大,最后会做一个验证。
Redis 6.0 多线程性能测试结果及分析

 

测试环境及策略

本机配置:centos 7,16C+32GB内存
Redis 6.0 多线程性能测试结果及分析

Redis版本:6.0.6


Redis 6.0 多线程性能测试结果及分析 下面分别以1线程,2线程,4线程,6线程,8线程,10线程的配置下,200个并发连接进行100百万次请求(./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q ),同时为避免网络延迟带来的影响,redis-benchmark在Redis实例本地,测试Redis的get和set性能。

 

翻车

整个测试开始之前,经历了两次翻车才得以继续 翻车现场1

centos 7上默认的gcc是4.*版本,无法编译Redis 6.0,所以需要升级gcc,因为本机不支持yum安装,
参考这里使用源码包安装,gcc编译的时候那个酸爽,本机16C+32GB内存的环境下,因为缺少某些依赖包,导致失败了几次,最终编译成功的一次,花了大概1个小时10分钟


Redis 6.0 多线程性能测试结果及分析 翻车现场2

没有认真读配置文件中的说明,设置io-threads后,重启Redis服务后,上来就用redis-benchmark直接怼,其结果跟单线程差不多,令人大跌眼镜。

最后还是在原始配置文件发现了这段话:

If you want to test the Redis speedup using redis-benchmark, make sure you also run the benchmark itself in threaded mode, using the --threads option to match the number of Redis threads, otherwise you'll not be able to notice the improvements. 意思是必须在redis-benchmark设置--threads参数,并且要match Redis中的线程设置,--threads参数是redis 6.0后新增的一个参数。只有加上--threads这个参数才能体现出来多线程Redis的效率。
Redis 6.0 多线程性能测试结果及分析

关于Thread IO的说明

经历了第二次翻车之后决定好好看一看redis.conf中关于thread io的注释信息

################################ THREADED I/O #################################

# Redis is mostly single threaded, however there are certain threaded
# operations such as UNLINK, slow I/O accesses and other things that are
# performed on side threads.
#
# Now it is also possible to handle Redis clients socket reads and writes
# in different I/O threads. Since especially writing is so slow, normally
# Redis users use pipelining in order to speed up the Redis performances per
# core, and spawn multiple instances in order to scale more. Using I/O
# threads it is possible to easily speedup two times Redis without resorting
# to pipelining nor sharding of the instance.
#
# By default threading is disabled, we suggest enabling it only in machines
# that have at least 4 or more cores, leaving at least one spare core.
# Using more than 8 threads is unlikely to help much. We also recommend using
# threaded I/O only if you actually have performance problems, with Redis
# instances being able to use a quite big percentage of CPU time, otherwise
# there is no point in using this feature.
#
# So for instance if you have a four cores boxes, try to use 2 or 3 I/O
# threads, if you have a 8 cores, try to use 6 threads. In order to
# enable I/O threads use the following configuration directive:
#
# io-threads 4
#
# Setting io-threads to 1 will just use the main thread as usual.
# When I/O threads are enabled, we only use threads for writes, that is
# to thread the write(2) syscall and transfer the client buffers to the
# socket. However it is also possible to enable threading of reads and
# protocol parsing using the following configuration directive, by setting
# it to yes:
#
# io-threads-do-reads no
#
# Usually threading reads doesn't help much.
#
# NOTE 1: This configuration directive cannot be changed at runtime via
# CONFIG SET. Aso this feature currently does not work when SSL is
# enabled.
#
# NOTE 2: If you want to test the Redis speedup using redis-benchmark, make
# sure you also run the benchmark itself in threaded mode, using the
# --threads option to match the number of Redis threads, otherwise you'll not
# be able to notice the improvements.

大概意思如下:

大多数情况下redis是以单线程的方式运行的,然而,有一些线程操作,如断开链接,耗时的I/O操作(bgsave,expired key清理之类的操作)和其他任务是在side线程(主线程fork出来的子线程)中执行的。 现在也可以在不同的I/O线程中处理Redis客户端socket读和写。由于写入(指socket写入)速度非常慢,Redis用户通常使用pipelining来提高Redis在单核上的性能,并使用多个实例的方式来扩容。使用I/O线程可以很容易地提升Redis在socket读写上的性能,而无需求助于管道或实例的分片。 Redis 6.0中默认情况下多线程被是被禁用的,建议至少有4个或更多核的机器中启用多线程,且至少留下1备用核。使用超过8个线程不大可能有太大帮助。 由于Redis实例能够充分利用CPU资源(译者注:意思是即便是单线程下,CPU并不是瓶颈),多线程I/O只有在你确实有性能问题的情况下才能提升运行效率,否则就没有必要使用这个特性。 如果你有一个4核的服务器,尝试使用2或3个I/O线程,如果是8核,尝试使用6个线程。要启用多线程I/O,请使用以下配置参数:io-threads 4 设置io-threads为1会像传统的redis一样启用单个主线程,当I/O threads被启用之后,仅仅支持写操作(译者注:指的是socket的写操作,socket的读操作使用多路io复用技术,本身也不是瓶颈)即IO线程调用syscall并将客户端缓冲区传输到socket。但是,也可以启用读写线程,使用以下配置指令进行协议解析,方法是将其设置为“yes”:io-threads-do-reads no 通常情况下,threading reads线程对性能的提升帮助并不大 注1:此配置指令不能在运行时通过配置集进行更改,只能在修改配置文件之后重启。启用SSL时,当前此特性也无效。 注2:如果你想用Redis-benchmark测试Redis的性能,务必以threaded mode的方式运行Redis-benchmark,使用--threads选项来匹配Redis线程的数量,否则无法观察到测试结果的提升。  

测试结果及分析

如下是不同线程requests per second测试结果的横向对比,分别在不同的线程下,进行100W次get/set请求的QPS结果


Redis 6.0 多线程性能测试结果及分析
Redis 6.0 多线程性能测试结果及分析

从中可以看到: 1,1个线程,也就是传统的单线程模式,get 操作的QPS可以达到9W左右 2,2个线程,get 操作的QPS可以达到18W左右,相比单线程有100%+的提升 3,4个线程,与2线程相比,会有30%左右的提高,但是已经没有从1个线程到2个线程翻一倍的提升了 4,6个线程,与4线程相比,没有明显的提升,对于SET操作,QPS从4线程到6线程,8线程开始没有出现明显的差异,都在23W~24W之间 5,8个线程,与4线程和6线程相比,8线程下大概有10%的提升 6,10个线程,相比效率最高的8线程,此时性能反倒是开始下降了,与4线程或者6线程的效率相当

因此在本机环境下,io-threads 4设置成2或者4个都ok,最多不超过8个,超出后性能反而会下降,同时也不能超出cpu的个数,正如配置文件中注释中说的,至少要留出一个CPU。   如下是不同线程下10测试结果中GET和SET的requests per second 平均值对比:


Redis 6.0 多线程性能测试结果及分析

 

关于io-threads-do-reads参数

上文提到过io-threads-do-reads这个参数,它是决定socket读操作是否开启多线程,Redis的socket读操作采用多路IO复用技术,本身不会成为瓶颈,所以这个参数对多线程下测试影响比较小。依旧参考这里的这个图 侵删,这个io-threads-do-reads在笔者理解起来,就是socket读的线程,是否开启影响不大。
Redis.conf中关于此参数的注释为:When I/O threads are enabled, we only use threads for writes, that is to thread the write(2) syscall and transfer the client buffers to the socket. However it is also possible to enable threading of reads and protocol parsing using the following configuration directive, by setting it to yes Usually threading reads doesn't help much.
以下就该参数启用与否进行一次对比测试
Redis 6.0 多线程性能测试结果及分析

参考如下截图,在开启了两个线程的情况下,分别开启和禁用io-threads-do-reads,从整体上看,性能影响差别很小。当然专业的大神可以从源码的角度去分析。
io-threads为2,且启动io-threads-do-reads
Redis 6.0 多线程性能测试结果及分析

io-threads为2,且禁动io-threads-do-reads
Redis 6.0 多线程性能测试结果及分析

 

多线程版本的Redis和Redis Cluster的选择

redis集群有两种模式:sentinel和cluster,这里暂时先不提sentinel,来思考多线程版本的Redis和Redis Cluster的选择问题。
Redis的Cluster解决的就是扩展性和单节点单线程的阻塞隐患,如果Redis支持了多线程(目前多线程的Redis最对不建议超出8个线程),在不考虑单个节点网卡瓶颈的情况下,其实这两个问题都已经解决了,单节点可以支持多线程和充分利用多核CPU,单节点可以支持到25W QPS,还要啥自行车?
同时要考虑到Redis cluster的痛点:
1,不支持multiple/pipline操作(第三方插件也不一定稳定)。
2,cluster中每个主节点要挂一个从节点,不管这个节点是不是独立的物理节点还是单机多实例中的一个节点,终究是增加了维护成本。
3,只能使用一个数据库
4,集群自身扩容、缩容带来的一系列slot迁移等性能问题,以及集群的管理问题
这些所谓的痛点也不复存在了,所以这里就面临一个重新选择的问题:是使用多线程版本的Redis,还是使用Redis cluster?这是一个需要思考的问题。

 

疑问

关于redis-benchmark 测试时候 ./bin/redis-benchmark -d 128  -c 200 -n 1000000 -t set -q --threads 2,涉及的两个参数-c和--threads,个人是不太理解的
-c的解释是:指定并发连接数
--threads是redis-benchmark的线程数?
对此去跟老钱(素未谋面,感谢)求证了一下,说该参数是redis-benchmark客户端的epoll,服务器端的多路IO复用原理已经看得我七荤八素了,客户端也是带epoll的,还是不太理解这两者之间的关系。

 

redis-benchmark测试现场

如下是redis-benchmark测试过程中部分截图

Redis 6.0 多线程性能测试结果及分析

Redis 6.0 多线程性能测试结果及分析

Redis 6.0 多线程性能测试结果及分析

Redis 6.0 多线程性能测试结果及分析

Redis 6.0 多线程性能测试结果及分析

图太多了,就不一一贴上来了。

发表评论

评论已关闭。

相关文章