要分析这个问题,需要详细了解下Redis的IO模型。
Redis I/O模型
Redis是单线程的为什么还那么快?首先要厘清一个事实,Redis的单线程指的是网络I/O和键值对的读写是单线程的,这也是Redis的主要功能,其他功能模块是由其他的额外线程完成的。
Redis为什么使用单线程
使用多线程能提高系统的吞吐率,增加系统的扩展性,对于一个系统来说,增加线程确实可以提高系统的吞吐率,但这是在没有竞争的共享资源的情况下。
并发访问控制一直是多线程开发中的一个难点问题,如果没有精细的设计,比如说只是简单使用粗粒度的互斥锁,就会出现不理想的结果。即使增加了线程,大部分线程也在等待获取访问共享资源的互斥锁,并行变串行。而且采用多线程也会增加系统的易读性和可维护性。为了避免这些问题,Redis直接使用了单线程。
Redis单线程为什么快?
除了前文(你以为Redis快仅仅是因为内存操作?-CSDN博客)提到的内存操作、高效的数据结构外,另一方面是Redis使用了多路复用机制,使其在网络I/O操作中能并发处理大量的客户端请求。
基本的网络模型阻塞点
以GET请求为例,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从socket中读取数据(recv),解析客户端发送请求(parse),根据请求类型读取键值数据(get),最后给客户端返回结果(send)。
如上图,bind/listen、accept、recv、paese、send属于网络IO处理,而get属于键值数据库操作。既然Redis是单线程,最直观的一种实现是在一个线程中依次处理上面的操作。但是,在这里的网络IO操作中,有潜在的阻塞点,分别是accept和recv。当redis监听到一个客户端有连接请求,但一直未能成功建立起链接时,会阻塞在accept这个函数上,导致其他客户端无法与Redis建立连接。类似的,当Redis通过recv从一个客户端读取数据时,如果数据一直没有到达,Redis也会一直阻塞在recv。
基于多路复用的I/O模型
Linux中的多路复用机制是指一个线程处理多个IO流,就是我们常听到的select/epoll机制。简单来说,在redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求,一旦有请求到达,就会交给redis线程处理,这就实现了一个redis线程处理多个IO流的效果。
图中多个FD就是刚才所说的多个套接字,redis网络框架调用epoll机制,让内核监听这些套接字,此时redis线程不会阻塞在某一个特定的监听或已连接的套接字上,也就是说不会阻塞在某个特定的客户端的处理上,正因如此,redis可以同时和多个客户端连接并处理请求,从而提高并发性。
为了在请求到达时能通知到redis线程,select/epoll提供了基于事件的回调机制,即针对不同的事件发生,调用相应的处理函数。select/epoll一但监测到FD上有请求到达时,就会触发相应的事件。这些事件会被放进一个事件队列,redis单线程对该事件队列不断进行处理。这样,redis无需一直轮询是否有请求实际发生,这就可以避免CPU的浪费,同时redis在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调,因为redis一直在对事件队列进行处理,所以能及时响应客户端请求,提升redis响应性能。
Redis大key的影响
回到标题的问题,Redis大key的影响。操作bigkey时,写入bigkey时分配内存时需要分配内存时需要消耗更多的时间,同样删除bigkey时释放内存也是同样的道理。因为Redis是单线程模型,处理bigkey分配或销毁内存时消耗时间较长,此时就会造成一定程度的阻塞,所以要避免bigkey,从IO模型上能提高性能。
往期精彩内容推荐
不止内存优势:Redis高性能背后的神秘面纱,超越想象加速秘诀-CSDN博客