Redis:Redis为什么是单线程的

菜瓜:怎么愁眉苦脸的

水稻:哎呀,这两天被Redis的单线程设计折磨的死去活来

菜瓜:有什么说法,给科普一下呗。

水稻:说起Redis,都知道它是单线程的。前段时间研究并发编程的时候刚刚体验到多线程的好处,可是这高效的Redis底层命令执行却是单线程。看了官网的解释,大概可以从一下两方面来看

  • 一是和多线程对比,虽然多线程可以充分利用cpu资源,但是线程间上下文切换也是一笔开销,另外一旦引入多线程就要考虑数据一致性问题,会增加实现的复杂度。其次Redis是基于内存的缓存系统,单机每秒可处理10w+的请求操作,cpu不是它的瓶颈。
  • 另一个方面是Redis采用了相对高效的多路复用IO模型。

菜瓜: IO多路复用听了那么久,还真不知道是怎么玩的。

水稻:简单来说就是它实现了一个进程只需要利用一个线程就可以监听多个文件描述符IO事件的功能。具体它的优点还需要对比一下阻塞和非阻塞IO模型。

  • 客户端和操作系统内核建立连接时会创建文件描述符,服务端程序会监听FD的IO事件
  • 阻塞式IO的模型下,进程通过recvfrom函数调用内核阻塞等待数据返回。如果要处理多个客户端的请求,可以使用多线程单独处理,但是随着客户端连接数的变大,创建线程也是很大一笔开销。
  • 非阻塞式IO模型下,进程调用read函数立即返回结果,如果没有IO请求会直接返回error,可以通过不断轮询方式异步处理,一旦有就可以从内核获取数据。还是存在大量内核调用
  • IO多路复用通过多种方式譬如epoll、select、poll等实现了一次函数调用获取到有效FD的操作,之后再根据有效的FD获取数据。

菜瓜: soga,原来是为了解决服务端同事处理多客户端请求的问题。哥,再讲讲select、poll、epoll呗?

水稻: 嗯,这三个函数也是慢慢演进,有各自的应用场景

  • select函数将非阻塞式IO搬到了内核之中,通过调用函数可以获取到目前活跃fds,然后对活跃的fd调用read函数就可以获取到数据。内核中还是会轮询所有fds,且有连接数限制
  • poll函数使用链表解决了连接数限制的问题。
  • epoll通过三个函数调完成。epoll_create创建一个event_poll数据结构体,里面维护一个红黑树和双向链表。通过调用epoll_ctl注册一个文件描述符加入红黑树之中,一旦有文件描述符就绪就会将其引用放入双向链表之中并产生回调。最后调用epoll_wait函数等待通知。这里利用mmap技术省掉了文件描述符在内核和用户态之间来回调用的开销。redis默认是使用的epoll

菜瓜: 原来是这样啊,我下去也研究研究。听说Redis在6.0之中引入了多线程版本,你知道吗?

水稻:恩,有了解过

  • 这个多线程指的是IO多线程,其实在处理真正的指令的时候还是单线程的。命令的执行如果支持了多线程,势必会引入数据一致性问题。
  • 我们知道执行命令步骤一般伴随着命令读取解析的IO,执行命令action,回写结果 IO。如果多个指令能用多个线程将每个命令解析和回写的IO操作并行起来,就可以节省很多时间

菜瓜:学到了学到了

总结:

  1. 多线程是为了充分利用CPU,但是Redis是基于内存的缓存系统,CPU不是它的瓶颈,不太用得上多线程
  2. 在IO层面,应对多个客户端请求采用了IO多路复用。对比早前两种IO模型提高了效率
  3. epoll内部一个红黑树和双向链表,并使用mmap内存映射技术大大提高了IO响应效率
原文地址:https://www.cnblogs.com/nightOfStreet/p/redis.html