Redis 6.0
redis单线程又加入了多线程
redis 发展史
redis 2.6 (2012年10月支持lua脚本)
redis 3.0 (2015年4月官方集群方案)
redis 4.0 (2017年7月混合持久化、多线程异步删除)
redis 5.0 (2018年10月核心代码重构)
redis 6.0 (2020年5月多线程IO)
redis为何是单线程?
主要是指redis的网络IO和键值对读写是由一个线程来完成的,redis在处理客户端的请求时包括获取(socket读)、解析、执行、内容返回(socket写)等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是redis对外提供键值存储服务的主要流程。
但redis的其他功能,比如持久化、异步删除、集群数据同步等等,其实是由额外的线程执行的。redis工作线程是单线程的,但是,整个redis来说,是多线程的。 redis6真真正正多线程登场!
redis 3.0的时候为什么这么快?
- 基于内存操作:redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以他的性能比较高;
- 数据结构简单:redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1),因此性能比较高;
- 多路复用和非阻塞I/O:redis使用I/O多路复用功能来监听多个socket连接客户端,这样可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了I/O阻塞操作;
- 避免上下文切换:因为是单线程模型,因此就避免了不必须要上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗。
redis是基于内存操作的,因此redis的瓶颈可能是机器的内存或者网络带宽而非CPU,既然CPU不是瓶颈,那么自然就采用单线程的解决方案了,况且使用多线程比较麻烦。但是在redis4.0中开始支持多线程了,例如后台删除等功能。
单线程的烦恼
正常的情况下使用del指令可以很快的删除数据,而当被删除的key是一个非常大的对象时,例如包含了成千上万个元素的hash集合时,那么del指令就会造成redis主线程卡顿。
这就是redis3.x单线程时代最经典的故障,大key删除的头疼问题。
由于redis是单线程的,del big key....等待很久这个线程才会释放,类似加了一个synchronized锁,可以想想一下高并发下,程序会堵成什么样子。。。
比如当redis需要删除一个很大的数据时,因为是单线程同步操作,这就会导致redis服务卡顿,于是redis4.0中就新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据比较低的问题。
unlink key //删除大key //把删除工作交给了后台的子线程一步来删除数据 flushdb async flushall async
Redis的IO多路复用
java的思想是一切皆对象,linux是一切皆文件。
I/0多路复用,简单来说就是通过检测文件的读写事件再通知线程执行相关操作,保证redis的非阻塞I/0能够顺利执行完成的机制。
多路指的是多个socket链接。
复用指的是复用一个线程。多路复用主要有三种技术:select、poll、epoll
epoll是最新的也是目前最好用的多路复用技术。采用多路I/O复用技术可以让单个线程搞笑的处理多个链接请求(尽量减少网络IO的时间消耗),且redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了redis具有很高的吞吐量。
redis工作线程是单线程的,但是整个redis来说是多线程的。
I/O的读和写本身是阻塞的,比如当socket中有数据时,redis会通过调用先将数据从内核空间拷贝到用户空间,再交给redis调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。
在redis6.0中新增了多线程的功能来提高I/O的读写性能,它的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使用多个socket的读写可以并行化,采用多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的socket的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。
结合上图可知,网络IO操作就变成了多线程,其他核心部分仍然是线程安全的,是个不错的折中办法。
redis6.0将网络数据读写、请求协议解通过多个IO线程来处理,对于真正的命令执行来说,仍然使用主线程操作,一举两得。
redis将所有的数据放在内存中,内存的影响时长大约为100纳秒,对于小数据包,redis服务器可以处理8W到10W的QPS。
这也是redis处理的极限了,对于80%的公司来说,单线程的redis足够使用。
在redis6.0中,多线程机制默认是关闭的,如果需要使用多线程功能,需要再redis.conf中完成两个设置。
- 设置io-thread-do-reads配置项为yes,表示启动多线程
- 设置线程个数。关于线程数的设置,官方的建议是如果为4核的CPU,建议线程数设置为2或者3;如果为8核的CPU建议线程数设置为6.
- redis出道就是优秀,基于内存操作、数据结构简单、多路复用和非阻塞IO、避免了不必要的上下文切换,单线程环境依然很快;
- 对于大数据的key删除还是卡顿,因此在redis4.0中引入了多线程unlink key.flushall async等命令,主要用户redis数据的异步删除。
- 而在redis6.0中引入了I/O多线程的读写,这样就可以更高效的处理更多任务了,redis只是将I/O读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此多线程下操作redis不会出现线程安全问题。