为什么单线程redis那么快

我们通常说redis是单线程的,主要指redis的网络IO和键值对读写是由一个线程完成的,这也是redis对外提供键值存储服务的主要流程,但Redis的其他功能,⽐如持久化、 异步删除、集群数据同步等,其实是由额外的线程执⾏的

Redis的单线程设计机制以及多路复⽤机制

redis为什么要用单线程?

多线程的开销

多线程编程面临共享资源并发访问控制问题,⼀个关键的瓶颈在于,系统中通常会存在被多线程同时访问的共享资源,⽐如⼀个共享的数据结构。当有多个线程要修改这个共享资源时,为了保证共享资源的正确性,就需要有额外的机制进⾏保证,⽽这个额外的机制,就会带来额外的开销。

redis为什么这么快
  1. redis的大部分操作在内存中完成,再加上采用了高效的数据结构例如哈希表和跳表

  2. redis采用了多路复用机制,使其在⽹络IO操作中能并发处理⼤量的客⼾端请求,实现⾼吞吐率。

基于多路复用的高性能IO模型

在redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字,内核会一直监听这些套接字上的连接请求和数据请求,一旦请求到达,便会交给redis线程处理,这就实现了一个redis线程处理多个IO流的效果

image-20210827112243828

为了在请求到达时能通知到Redis线程,select/epoll提供了基于事件的回调机制,针对不同的事件调用相应的处理函数

回调机制是怎么⼯作的呢?其实,select/epoll⼀旦监测到FD上有请求到达时,就会触发相应的事件。这些事件会被放进⼀个事件队列,Redis单线程对该事件队列不断进⾏处理。这样⼀来,Redis⽆需⼀直轮询是否有请求实际发⽣,这就可以避免造成CPU资源浪费。同时Redis在对事件队列中的事件进⾏处理时, 会调⽤相应的处理函数,这就实现了基于事件的回调。因为Redis⼀直在对事件队列进⾏处理,所以能及时响应客⼾端请求,提升Redis的响应性能。

Redis单线程处理IO请求性能瓶颈主要包括2个⽅⾯:

1、任意⼀个请求在server中⼀旦发⽣耗时,都会影响整个server的性能,也就是说后⾯的请求都要等前⾯这个耗时请求处理完成,⾃⼰才能被处理到。耗时的操作包括以下⼏种:

  • a、操作bigkey:写⼊⼀个bigkey在分配内存时需要消耗更多的时间,同样删除bigkey释放内存同样会产⽣耗时;

  • b、使⽤复杂度过⾼的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很⼤,例如lrange key 0 -1⼀次查询全量数据;

  • c、⼤量key集中过期:Redis的过期机制也是在主线程中执⾏的,⼤量key集中过期会导致处理⼀个请求时,耗时都在删除过期key,耗时变⻓;

  • d、淘汰策略:淘汰策略也是在主线程执⾏的,当内存超过Redis内存上限后,每次写⼊都需要淘汰⼀些key,也会造成耗时变⻓;

  • e、AOF刷盘开启always机制:每次写⼊都需要把这个操作刷到磁盘,写磁盘的速度远⽐写内存慢,会拖慢Redis的性能;

  • f、主从全量同步⽣成RDB:虽然采⽤fork⼦进程⽣成数据快照,但fork这⼀瞬间也是会阻塞整个线程的, 实例越⼤,阻塞时间越久;

2、并发量⾮常⼤时,单线程读写客⼾端IO数据存在性能瓶颈,虽然采⽤IO多路复⽤机制,但是读写客⼾端数据依旧是同步IO,只能单线程依次读取客⼾端的数据,⽆法利⽤到CPU多核。

针对问题1,⼀⽅⾯需要业务⼈员去规避,⼀⽅⾯Redis在4.0推出了lazy-free机制,把bigkey释放内存的耗时操作放在了异步线程中执⾏,降低对主线程的影响。

针对问题2,Redis在6.0推出了多线程,可以在⾼并发场景下利⽤CPU多核多线程读写客⼾端数据,进⼀步提升server性能,当然,只是针对客⼾端的读写是并⾏的,每个命令的真正操作依旧是单线程的。