一、Redis为什么快
主要是因为以下几个特性:
- C语言实现
- 纯内存操作
- IO多路复用
- 单线程模型
- 数据结构、算法优化
二、选择单线程模型的原因
- 避免过多的上下文切换开销
- 避免多线程之间同步机制的开销
- 简单、可维护
然而,Redis并不是单纯的单线程,即使是Redis 6.0之前,虽然Redis的网络IO部分一直是单线程,但还是有一些任务是通过异步线程去做的,例如删除Key的操作,不同步删除数据,而是先将key从keyspace中移除,然后将删除数据的任务放到一个异步队列里面去,由后台线程去删除,这在删除大key的时候可以有效避免操作阻塞。
6.0之后,引入IO多线程来提升IO的效率,但是,IO线程只是负责命令的解析以及网络IO,具体命令的执行,还是在主线程完成的,维持了Redis任务单线程的特性。
三、Redis多线程网络模型
- 使用 I/O 线程实现网络 I/O 多线程化,I/O 线程只负责网络 I/O 和命令解析,不执行客户端命令。
- 利用原子操作+交错访问实现无锁的多线程模型。
- 通过设置 CPU 亲和性,隔离主进程和其他子进程,让多线程网络模型能发挥最大的性能。
整体流程:
- 根据配置,初始化多线程。
- 主线程事件循环,将IO事件放到 clients_pending_read中,在beforeSleep方法中,通过RoundRobin的方式将IO事件分发给各个IO线程(包括主线程自己),放到他们的本地队列中;接着主线程会处理自己队列的IO任务,然后忙轮询等待所有IO线程完成任务(io_threads_pending和为0),主线程开始处理任务。
- 主线程处理完任务,遍历待写出的 client 队列 clients_pending_write,通过 RR 策略把所有任务分配给 I/O 线程和主线程去将响应数据写回到客户端。
- IO线程逻辑:
- I/O 线程启动之后,会先进入忙轮询,判断原子计数器中的任务数量,如果是非 0 则表示主线程已经给它分配了任务
- 主线程会在每次事件循环中尝试调用 startThreadedIO 唤醒 I/O 线程去执行任务
- 遍历自己的本地任务队列 io_threads_list[id],取出一个个 client 执行任务
- 全部完成后,计数器置为0(主线程在忙轮询等待)
在整个流程里面,虽然有多线程操作,但是主线程跟IO线程、IO线程之间,并不存在race,而是通过原子操作+交错访问来实现无锁。