Redis是一个开源的远程内存型数据库(Remote Dictionary Server(远程字典服务器)),它不仅性能强劲,而且提供了5 种不同类型的数据结构,我们项目实践中可能会遇到的大部分问题都可以很自然地映射到这些数据结构上。除此之外,Redis通过复制、持久化(persistence)、Redis Sentinel、客户端分片(client-side sharding)等特性,让用户可以很方便地将Redis 扩展成一个高可用能够包含数百GB 数据、每秒处理上百万次请求的系统。
本节我们讨论下Redis的单线程、多线程网络模型,以及多线程异步任务模型。
Redis的核心网络模型选择用单线程来实现。正如redis官网上说,对于一个 DB 来说,CPU 通常不会是瓶颈,因为大多数请求不会是 CPU 密集型的,而是 I/O 密集型。具体到 Redis 的话,如果不考虑 RDB/AOF 等持久化方案,Redis 是完全的纯内存操作,执行速度是非常快的,因此这部分操作通常不会是性能瓶颈,Redis 真正的性能瓶颈在于网络 I/O,也就是客户端和服务端之间的网络传输延迟,因此 Redis 6.0版本前选择了单线程的 I/O 多路复用来实现它的核心网络模型。
使用单线程网络模型好处:
在 v6.0 版本之前,Redis 的核心网络模型一直是一个典型的单 Reactor 模型:利用 epoll/select/kqueue 等多路复用技术,在单线程的事件循环中不断去处理事件(客户端请求),最后回写响应数据到客户端:
image.png
下面我们描绘一下 客户端client 与 Redis server 建立连接、发起请求到接收到返回的整个过程:
image.png
如上图,这种模式不再是单线程的事件循环,而是有多个线程(IO Thread)各自维护一个独立的事件循环。整体模型是由 Main 线程负责接收新连接,并分发给 IO Thread 去独立处理(解析请求命令),但是具体命令的执行还是使用main 线程来执行,最后使用IO 线程回写响应给客户端。
IO线程轮训socket列表读事件,然后解析为redis命令,并把解析好的命令放到全局待执行队列,然后主线程从全局待执行队列读取命令然后具体执行命令,最后把响应结果分配到不同IO线程,由IO线程来具体执行把响应结果写回客户端。
也就是具体命令执行还是由main线程所在的事件循环单线程处理,只是读写socket事件由IO线程来处理。
虽然多线程方案能提升1倍以上的性能,但整个方案仍然比较粗糙:
//IO线程逻辑
void *IOThreadMain(void *myid)
while(1) {
long id = (unsigned long)myid;
// 忙轮询100w 次循环,等待主线程分配 I/O 任务。
for (int j = 0; j < 1000000; j++) {
if (io_threads_pending[id] != 0) break;
}
...
}
}
//主线程执行逻辑
int handleClientsWithPendingWritesUsingThreads(void) {
// 忙轮询,累加所有 I/O 线程的原子任务计数器,直到所有计数器的遗留任务数量都是 0。
// 表示所有任务都已经执行完成,结束轮询。
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += io_threads_pending[j];
if (pending == 0) break;
}
...
}
Redis 在 v4.0 版本的时就已经引入了的多线程来做一些异步操作,这主要是为了解决一些非常耗时的命令,通过将这些命令的执行进行异步化,避免阻塞单线程网络模型的事件循环。
Redis 启动时,会创建三个任务队列,并对应构建 3 个 BIO 线程,三个 BIO 线程与 3 个任务队列之间一一对应。BIO 线程分别处理如下 3 种任务。
BIO 线程的整个处理流程如图所示。当主线程有慢任务需要异步处理时。就会向对应的任务队列提交任务。提交任务时,首先申请内存空间,构建 BIO 任务。然后对队列锁进行加锁,在队列尾部追加新的 BIO 任务,最后尝试唤醒正在等待任务的 BIO 线程。
img
BIO 线程启动时或持续处理完所有任务,发现任务队列为空后,就会阻塞,并等待新任务的到来。当主线程有新任务后,主线程会提交任务,并唤醒 BIO 线程。BIO 线程随后开始轮询获取新任务,并进行处理。当处理完所有 BIO 任务后,则再次进入阻塞,等待下一轮唤醒。
在Redis6.0版本前,其提供单线程网络模型,使用单线程来处理socket的读写事件、命令解析、命令执行工作。
在Redis6.0版本后,提供了多线程模型逻辑,其中socket的读写事件、命令解析使用IO线程来处理,但是具体命令的执行还是使用单线程事件循环来进行处理。但是其实现并不优雅。
最后无论是单线程还是多线程网络模型,命令的具体执行还是靠单线程事件循环来执行的,如果要执行的命令非常耗时,则会阻塞事件循环的执行,使得其他命令得不到及时执行,所以Redis4.0时开始提供异步多线程任务来解决耗时比较长的命令的执行,将其异步化执行,使得主事件循环线程可以及时得到释放。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有