“你对redis的单线程是不是有点误会?
“你对redis 6.0的多线程是不是也有点误会?
“redis多线程一定可以提高性能吗?
redis官方刚刚发布的6.0版本已经掀起了业界一阵热波,在这个版本中新加了很多新特性,如果你打开redis的官网,可以看到6.0现在已经是稳定版本
了。
redis现在已经成为了面试官必问的知识点之一,尤其是当新版本加入了“多线程”概念之后,面试题又是增加了一道难题。
redis单线程
redis在6.0之前的版本,很多同学认为是单线程,其实这个说法严格意义上来讲不太准确。“单线程”是指客户端发送的命令的接收,解析,执行,结果
返回这个过程是由一个线程处理,这个线程就是主线程,这也是redis素有“单线程”定义的来源。
但是,redis也有其他后台线程在处理其他操作,比如那些比较慢,不太适合放主线程执行的操作,例如大key的删除,AOF的重写,快照的生成,无用连接的释放
等。
单线程机制使得redis内部代码实现的复杂度和难度大大降低,请求都是按照串行化的顺序来依次执行,这大大降低了由于线程切换带来的资源消耗,而且又可以避免锁带来的一系列问题。所以平时开发使用redis的时候,我们可以实现分布式锁等一系列骚操作,这和Actor模型中,单个Actor的行为十分类似,串行的操作使我们可以摆脱多线程带来的一系列执行顺序的痛苦。
至于redis为什么不使用多线程呢?官方曾经做过类似问题的回复,大体意思是,redis由于cpu成为瓶颈的几率几乎不存在
,redis的性能主要受限于内存和网络
,当然,我认为这个解释并非是绝对的。当redis开启持久化之后,吞吐量会大幅度下降,除非非常必要,不然在很多业务场景下尽量不要开启redis的持久化。
redis单线程瓶颈
redis将所有数据放在内存中,在充分利用pipelining技术的情况下,QPS可达百万,这个量级对于普通的中小公司已经足以,就算是再大一点的请求量,利用redis集群方式也能抗住。但是redis集群也是有缺陷的:
redis集群需要更多的服务器资源来支撑,这无疑加大了公司的支出费用和资源成本。
redis集群虽然可以通过增加副本的方式来解决热点key的读问题,但是热点key的写依然比较棘手
从redis自身的处理请求过程来看,对网络数据的读写以及对命令的解析占用了大部分cpu时间
,瓶颈在于网络IO的消耗以及对CPU不能充分的利用。而要突破这个瓶颈呢,一般有两种解决方案:
网络请求的处理不再依靠内核,而在用户态处理。但是这种方式需要修改内核网络栈的实现方式,这会带来很多开发工作量,而且设计到核心代码修改,可能会引入新的bug,导致系统不稳定
利用多线程优势,充分利用服务器多核的特性,采用多个IO线程来并行处理网络请求。
很显然,redis6.0采用的是多线程的方式。
无论是针对redis集群,还是针对单体架构,提高单机redis的处理速度和吞吐量目前看百利而无一害。
redis多线程
无论redis采用单线程还是多线程,其实每个请求的整体处理流程是一致的。
在整个流程中,读取解析redis客户端命令和返回客户端结果两个步骤分别对应网络数据
的读取和写入,这两个步骤对于redis来说,占用了大部分cpu时间,所以redis6.0多线程机制是针对这两个步骤的。
为了直观的更好了解整个流程,一般分为以下几个步骤:
当客户端有新的socket连接时,主线程会负责接收连接,并把socket放入全局的等待队列中,当主线程处理完读事件之后,通过轮训的方式将这些连接分配给IO线程。
主线程会一直阻塞到IO线程读取Socket并解析完毕,这个解析过程是多个IO线程并行处理的,所以会很快。
等到IO线程解析命令完成,主线程以单线程的方式来执行这些命令,并把执行结果写入缓冲区,然后阻塞的等待IO线程回写socket数据
IO线程读取缓冲区结果数据,把这些数据回写socket,返回给客户端。这个过程也是多个IO线程并行处理的,所以也会很快。
当所有的IO线程回写socket完毕,主线程回清空全局队列,等待下次新的请求到来。
从以上步骤可以看出,IO线程只涉及到socket的读和写,而实际命令的执行还是主线程以顺序化的方式来执行,这不仅仅是利用多线程的优势,同时又保留了单线程的优势,在命令执行上不会产生多线程的一系列问题,比如加锁带来的耗时,控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。
“其实关于上面所说,我有一点没想明白:假如有两个socket,在单线程的时候,主线程可以保证优先到来的socket命令数据优先被执行,但是在加入了多个IO线程并行解析过程之后,本来先接收的命令是否可以保证优先执行呢?希望大佬在评论区给予指点
redis 6.0默认是禁用多线程机制
的,如果需要开启,请修改redis.conf:
io-threads-do-reads yes
开启时候还要设置线程数,否则多线程机制是不生效的。至于设置多少个线程,官方有一个建议:4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好
,官方认为超过了8个基本就没什么意义了。
redis6.0多线程测试
Redis 作者 antirez 在 RedisConf 2019 分享时曾提到:Redis 6 引入的多线程 IO 特性对性能提升至少是一倍以上。国内也有大牛曾使用 unstable 版本在阿里云 esc 进行过测试,GET/SET 命令在 4 线程 IO 时性能相比单线程是几乎是翻倍了。
“Redis Server:阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge Redis Benchmark Client:阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 内存,主机型号 ecs.ic5.2xlarge
“这些性能验证的测试并没有针对严谨的延时控制和不同并发的场景进行压测。数据仅供验证参考而不能作为线上指标。
“如果开启多线程,至少要4核的机器,且Redis实例已经占用相当大的CPU耗时的时候才建议采用,否则使用多线程没有意义。所以估计80%的公司开发人员看看就好。
写在最后
redis6.0利用多线程的优势很好的解决了当前redis的瓶颈问题,同时又保留了核心命令执行过程单线程机制。不过将来单线程的命令执行
机制会不会是redis的瓶颈呢?这个留给大佬们在评论区!!
最后提出一个我的疑问:redis6.0在启用了多线程机制之后,那先后到达的socket数据,在命令执行的时候是否有可能不是按照数据到达的顺序呢?redis6.0 是否有机制来保证这个顺序呢?请大佬在留言区赐教!!
程序员过关斩将--论系统设计的高可扩展性
程序员过关斩将--从未停止过的系统架构设计步伐
程序员过关斩将--真的可以用版本号的方式来保证MQ消费消息的幂等性?