为什么redis是单线程,但是还能那么快?

redis真的是单线程吗?

首先我们得厘清一个事实,我们说的Redis单线程,其实指的是redis的网络IO和键值对是由一个线程执行,这要是redis对外提供键值存储服务的主要流程。
此外,还有持久化、意不删除、集群数据同步等都是用额外的线程来执行的。

所以严格来说,Redis并不是单线程。但是我们还是说成“单线程”,因为“酷”~
redis为什么用单线程?
Redis为例在多线程当中,如果有线程1为List做LPUSH操作,线程2做LPOP操作,那么为了List的长度的正确性,就需要将两个线程串行来完成。为了防止多线程的资源共享的并发问题,redis就采用了单线程模式。

redis单线程为什么会这么快呢?

  1. redis的大部分操作都在内存中完成,再加上它使用了高效数据结构hash表。
  2. redis采用的是多路复用机制,能使网络I/O流并发处理大量的客户端请求,提高吞吐率。

多路复用是什么?
在学习多路复用之前,要理清基本网络I/O模型中可能存在的阻塞点,因为redis是单线程,若线程被阻塞了,那么就没有办法进行多路复用。

网络I/O主要流程如下:
监听客户端socket与服务端连接(listen/bind),和客户端建立连接(accept),连接成功后,服务端获取socket请求(recv),解析socket请求(parse),根据请求类型使用键值操作(get)获取数据返回到客户端(send)。

这其中,accept、recv是可能存在的阻塞点。当redis监听到客户端连接,但是迟迟没有连接成功,accept函数就会阻塞,就导致其他客户端无法与redis进行连接。还好,可以设置非阻塞模式。

非阻塞模式

套接字 返回套接字类型 非阻塞模式 效果
socket 主动套接字
listen 监听套接字 accept()非阻塞
accept 已连接套接字 recv()、send()非阻塞

设置分阻塞模式后,如果redis线程调用accept但是迟迟没有响应,redis线程还可以执行其他操作,线程不会阻塞。虽然不用阻塞等待,但是还是需要机制监听accept并在有数据请求的时候通知redis,要注意的是调用accept()时已经存在监听套接字。

在Linux中,多路复用就是指在一个线程中要同时处理多个I/O流,就是select/epoll机制。基于多路复用机制,在redis中只启动单线程的情况下,该机制允许内核中存在多个监听套接字和多个已连接套接字,由内核一直监听这些套接字,如果有请求到达,则提示通知redis。

为了能收到请求后通知redis,select/epoll提供了基于事件的回调函数。针对不同事件的发生,调用不同处理函数。
在这里插入图片描述 如上图,每个RQ都是之前所说的套接字,在监听到套接字后,触发对应的事件,再将事件放入到事件队列中,redis单线程会一直不断的处理事件队列。在处理事件队列的时候,会根据事件调用对应的处理函数。因为redis会不断的处理事件队列,所以能及时的响应客户端请求,从而提高响应性能。