透视Redis大key背后的I/O挑战

        要分析这个问题,需要详细了解下Redis的IO模型。        

Redis I/O模型

        Redis是单线程的为什么还那么快?首先要厘清一个事实,Redis的单线程指的是网络I/O和键值对的读写是单线程的,这也是Redis的主要功能,其他功能模块是由其他的额外线程完成的。

        Redis为什么使用单线程

        使用多线程能提高系统的吞吐率,增加系统的扩展性,对于一个系统来说,增加线程确实可以提高系统的吞吐率,但这是在没有竞争的共享资源的情况下。

透视Redis大key背后的I/O挑战_第1张图片

        左图是我们期待的结果,右图是实际的情况。为什么会出现这种情况呢?一个关键点就是共享资源被多线程访问,为了保证共享资源的正确性,往往会引入额外的机制来保证,这就带来了很多开销。
        以Redis的列表类型为例,假设Redis才用多线程类型,有两个线程A和B,线程A进行入队操作,此时对列表长度加1,线程B进行出队操作,对比列表长度减1,为了保证长度的正确性,Redis需要对A和B两个线程做串行化处理,这样Redis可以得到正确的结果。这也是多线程模式下所面临的共享资源并发访问问题。 透视Redis大key背后的I/O挑战_第2张图片

        并发访问控制一直是多线程开发中的一个难点问题,如果没有精细的设计,比如说只是简单使用粗粒度的互斥锁,就会出现不理想的结果。即使增加了线程,大部分线程也在等待获取访问共享资源的互斥锁,并行变串行。而且采用多线程也会增加系统的易读性和可维护性。为了避免这些问题,Redis直接使用了单线程。

        Redis单线程为什么快?

        除了前文(你以为Redis快仅仅是因为内存操作?-CSDN博客)提到的内存操作、高效的数据结构外,另一方面是Redis使用了多路复用机制,使其在网络I/O操作中能并发处理大量的客户端请求。

        基本的网络模型阻塞点

        以GET请求为例,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从socket中读取数据(recv),解析客户端发送请求(parse),根据请求类型读取键值数据(get),最后给客户端返回结果(send)。​​​​​​​透视Redis大key背后的I/O挑战_第3张图片

        如上图,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流的效果。

透视Redis大key背后的I/O挑战_第4张图片

        图中多个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,数据库,缓存)