高性能IO模型:为什么单线程Redis能那么快?

你好 , 我是蒋德钧 。
今天 , 我们来探讨一个很多人都很关心的问题:“为什么单线程的Redis能那么快?”
首先 , 我要和你厘清一个事实 , 我们通常说 , Redis是单线程 , 主要是指Redis的网络IO和键值对读写是由一个线程来完成的 , 这也是Redis对外提供键值存储服务的主要流程 。 但Redis的其他功能 , 比如持久化、异步删除、集群数据同步等 , 其实是由额外的线程执行的 。
所以 , 严格来说 , Redis并不是单线程 , 但是我们一般把Redis称为单线程高性能 , 这样显得“酷”些 。 接下来 , 我也会把Redis称为单线程模式 。 而且 , 这也会促使你紧接着提问:“为什么用单线程?为什么单线程能这么快?”
要弄明白这个问题 , 我们就要深入地学习下Redis的单线程设计机制以及多路复用机制 。 之后你在调优Redis性能时 , 也能更有针对性地避免会导致Redis单线程阻塞的操作 , 例如执行复杂度高的命令 。
好了 , 话不多说 , 接下来 , 我们就先来学习下Redis采用单线程的原因 。
Redis为什么用单线程?
要更好地理解Redis为什么用单线程 , 我们就要先了解多线程的开销 。
多线程的开销
日常写程序时 , 我们经常会听到一种说法:“使用多线程 , 可以增加系统吞吐率 , 或是可以增加系统扩展性 。 ”的确 , 对于一个多线程的系统来说 , 在有合理的资源分配的情况下 , 可以增加系统中处理请求操作的资源实体 , 进而提升系统能够同时处理的请求数 , 即吞吐率 。 下面的左图是我们采用多线程时所期待的结果 。
但是 , 请你注意 , 通常情况下 , 在我们采用多线程后 , 如果没有良好的系统设计 , 实际得到的结果 , 其实是右图所展示的那样 。 我们刚开始增加线程数时 , 系统吞吐率会增加 , 但是 , 再进一步增加线程时 , 系统吞吐率就增长迟缓了 , 有时甚至还会出现下降的情况 。
高性能IO模型:为什么单线程Redis能那么快?
文章图片
为什么会出现这种情况呢?一个关键的瓶颈在于 , 系统中通常会存在被多线程同时访问的共享资源 , 比如一个共享的数据结构 。 当有多个线程要修改这个共享资源时 , 为了保证共享资源的正确性 , 就需要有额外的机制进行保证 , 而这个额外的机制 , 就会带来额外的开销 。
拿Redis来说 , 在上节课中 , 我提到过 , Redis有List的数据类型 , 并提供出队(LPOP)和入队(LPUSH)操作 。 假设Redis采用多线程设计 , 如下图所示 , 现在有两个线程A和B , 线程A对一个List做LPUSH操作 , 并对队列长度加1 。 同时 , 线程B对该List执行LPOP操作 , 并对队列长度减1 。 为了保证队列长度的正确性 , Redis需要让线程A和B的LPUSH和LPOP串行执行 , 这样一来 , Redis可以无误地记录它们对List长度的修改 。 否则 , 我们可能就会得到错误的长度结果 。 这就是多线程编程模式面临的共享资源的并发访问控制问题 。
高性能IO模型:为什么单线程Redis能那么快?
文章图片
并发访问控制一直是多线程开发中的一个难点问题 , 如果没有精细的设计 , 比如说 , 只是简单地采用一个粗粒度互斥锁 , 就会出现不理想的结果:即使增加了线程 , 大部分线程也在等待获取访问共享资源的互斥锁 , 并行变串行 , 系统吞吐率并没有随着线程的增加而增加 。
而且 , 采用多线程开发一般会引入同步原语来保护共享资源的并发访问 , 这也会降低系统代码的易调试性和可维护性 。 为了避免这些问题 , Redis直接采用了单线程模式 。