并发服务器:Redis案例研究分析

并发服务器:Redis案例研究分析并发服务器:Redis案例研究分析

1.事件处理库html

Redis 最初发布于 2009 年,它最牛逼的一件事情大概就是它的速度 —— 它可以处理大量的并发客户端链接。须要特别指出的是,它是用一个单线程来完成的,并且还不对保存在内存中的数据使用任何复杂的锁或者同步机制。linux

Redis 之因此如此牛逼是由于,它在给定的系统上使用了其可用的最快的事件循环,并将它们封装成由它实现的事件循环库(在 Linux 上是 epoll,在 BSD 上是 kqueue,等等)。这个库的名字叫作 ae。ae 使得编写一个快速服务器变得很容易,只要在它内部没有阻塞便可,而 Redis 则保证 注1 了这一点。redis

在这里,咱们的兴趣点主要是它对文件事件的支持 —— 当文件描述符(如网络套接字)有一些有趣的未决事情时将调用注册的回调函数。与 libuv 相似,ae 支持多路事件循环(参阅本系列的第三节和第四节)和不该该感到意外的 aeCreateFileEvent 信号:
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData);
它在 fd 上使用一个给定的事件循环,为新的文件事件注册一个回调(proc)函数。当使用的是 epoll 时,它将调用 epoll_ctl 在文件描述符上添加一个事件(多是 EPOLLIN、EPOLLOUT、也或许二者都有,取决于 mask 参数)。ae 的 aeProcessEvents 功能是 “运行事件循环和发送回调函数”,它在底层调用了 epoll_wait。数据库

2.处理客户端请求服务器

咱们经过跟踪 Redis 服务器代码来看一下,ae 如何为客户端事件注册回调函数的。initServer 启动时,经过注册一个回调函数来读取正在监听的套接字上的事件,经过使用回调函数 acceptTcpHandler 来调用 aeCreateFileEvent。当新的链接可用时,这个回调函数被调用。它调用 accept 注2 ,接下来是 acceptCommonHandler,它转而去调用 createClient 以初始化新客户端链接所须要的数据结构。网络

createClient 的工做是去监听来自客户端的入站数据。它将套接字设置为非阻塞模式(一个异步事件循环中的关键因素)并使用 aeCreateFileEvent 去注册另一个文件事件回调函数以读取事件 —— readQueryFromClient。每当客户端发送数据,这个函数将被事件循环调用。数据结构

readQueryFromClient 就让咱们指望的那样 —— 解析客户端命令和动做,并经过查询和/或操做数据来回复。由于客户端套接字是非阻塞的,因此这个函数必须可以处理 EAGAIN,以及部分数据;从客户端中读取的数据是累积在客户端专用的缓冲区中,而完整的查询可能被分割在回调函数的多个调用当中。多线程

3.将数据发送回客户端并发

在前面的内容中,我说到了 readQueryFromClient 结束了发送给客户端的回复。这在逻辑上是正确的,由于 readQueryFromClient 准备要发送回复,但它不真正去作实质的发送 —— 由于这里并不能保证客户端套接字已经准备好写入/发送数据。咱们必须为此使用事件循环机制。异步

Redis 是这样作的,它注册一个 beforeSleep 函数,每次事件循环即将进入休眠时,调用它去等待套接字变得能够读取/写入。beforeSleep 作的其中一件事情就是调用 handleClientsWithPendingWrites。它的做用是经过调用 writeToClient 去尝试当即发送全部可用的回复;若是一些套接字不可用时,那么当套接字可用时,它将注册一个事件循环去调用 sendReplyToClient。这能够被看做为一种优化 —— 若是套接字可用于当即发送数据(通常是 TCP 套接字),这时并不须要注册事件 ——直接发送数据。由于套接字是非阻塞的,它从不会去阻塞循环。

4.Redis中的多线程

在 Redis 的绝大多数历史中,它都是一个彻彻底底的单线程的东西。一些人以为这太难以想象了,有这种想法彻底能够理解。Redis 本质上是受网络束缚的 —— 只要数据库大小合理,对于任何给定的客户端请求,其大部分延时都是浪费在网络等待上,而不是在 Redis 的数据结构上。

然而,如今事情已经再也不那么简单了。Redis 如今有几个新功能都用到了线程:
1.“惰性” 内存释放。
2.在后台线程中使用 fsync 调用写一个 持久化日志。
3.运行须要执行一个长周期运行的操做的用户定义模块。

对于前两个特性,Redis 使用它本身的一个简单的 bio(它是 “Background I/O" 的首字母缩写)库。这个库是根据 Redis 的须要进行了硬编码,它不能用到其它的地方 —— 它运行预设数量的线程,每一个 Redis 后台做业类型须要一个线程。

而对于第三个特性,Redis 模块 能够定义新的 Redis 命令,而且遵循与普通 Redis 命令相同的标准,包括不阻塞主线程。若是在模块中自定义的一个 Redis 命令,但愿去执行一个长周期运行的操做,这将建立一个线程在后台去运行它。在 Redis 源码树中的 src/modules/helloblock.c 提供了这样的一个示例。

有了这些特性,Redis 使用线程将一个事件循环结合起来,在通常的案例中,Redis 具备了更快的速度和弹性,这有点相似于在本系统文章中 第四节 讨论的工做队列。

原文来自:https://www.linuxprobe.com/redis-case-study.html

相关文章
相关标签/搜索