1 前言
首先,redis是一个高性能的分布式缓存中间件。其复杂性不言而喻,对于redis整体而言肯定不是只有一个线程。
我们常说的redis 是单线程,主要是指 redis 在网络 io和键值对读写是采用一个线程来完成的,这也是 redis 对外提供键值存储服务的核心流程。但对于 redis 的其他功能来说,比如持久化、异步删除、集群数据同步等,其实都是由额外的线程执行的。
2 为什么要融入多线程?
单线程的优势:
使用单线程可以避免频繁的上下文切换
redis 中有各种类型的数据操作,甚至包括一些事务处理,如果采用多线程,还可能因为加锁导致软件复杂度提升,更有可能会因为加解锁,甚至出现死锁,造成的性能损耗,所以使用单线程反而性能会更好
单线程的劣势:
- 无法发挥多核cpu的优势
- 当删除大建,会导致服务阻塞
- qps达到瓶颈
基于上诉劣势,redis也进行了相关优化,在4.0版本和6.0版本分别引入了lazy free和多线程io。
3 redis单线程模型
redis基于reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。
文件事件处理器的四个重要组成部分:
redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是redis的单线程基本模型,如下图所示
4 带你理解redis处理流程
4.1 redis 4.0之前的事件处理流程
我们先介绍一下redis( 4.0之前)的执行原理,我们通过io多路复用器监听来自客户端的socket网络连接,然后由主线程进行io请求的处理以及命令的处理,所有操作都是线性的,我们可以抽象的理解为下图。
4.2 redis 4.0 之后加入lazy free机制
redis 4.0 之前在处理客户端命令和io操作时都是以单线程形式运行,期间不会响应其他客户端请求,但若客户端向redis发送一条耗时较长的命令,比如删除一个含有上百万对象的set键,或者执行flushdb,flushall操作,redis服务器需要回收大量的内存空间,这事就会导致redis服务阻塞,对于负载较高的缓存系统来说将会是个灾难。为了解决这个问题,在redis 4.0版本引入了lazy free,目的是将慢操作异步化,这也是在事件处理上向多线程迈进了一步,其过程我们可以理解为下图。
4.3 redis 6.0 之后将网络io异步化
从以上的发展历程中,我们也能看出redis 的瓶颈并不在cpu上,即使是单线程redis也能做到很快的响应,除非是遇到个别极其耗时的命令,这一块我们的redis也在4.0版本做出了优化,但是我们是不是能更进一步优化redis呢?从上图中我们可以看出主线程不光处理大量的命令,还需要处理大量的网络io,redis6.0就是基于此,将io操作交由其他线程处理,抽象的理解如下图所示。
redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件:io-threads-do-reads yes
开启多线程后,还需要设置线程数,否则是不生效的。
线程数一定要小于机器核数。线程数并不是越大越好,官方认为超过了 8 个基本就没什么意义了。
设置线程数,修改redis.conf配置文件: io-threads 6