红联Linux论坛
Linux帮助

北京pk10四码定位技巧:并发服务器(五):Redis案例研究

发布时间:2018-03-08 09:32:15来源:linux.cn作者:qhwdw
这是我写的并发网络服务器系列文章的第五部分。在前四部分中我们讨论了并发服务器的结构,这篇文章我们将去研究一个在生产系统中大量使用的服务器的案例—— Redis。
Redis 是一个非常有魅力的项目,我关注它很久了。它最让我着迷的一点就是它的 C 源代码非常清晰。它也是一个高性能、大并发的内存数据库服务器的非常好的例子,它是研究网络并发服务器的一个非常好的案例,因此,我们不能错过这个好机会。
我们来看看前四部分讨论的概念在真实世界中的应用程序。
 
本系列的所有文章有:
本系列的所有文章:
第一节 - 简介(http://www.fanyiwo.com/linux/32842.html)
第二节 - 线程(http://www.fanyiwo.com/linux/32844.html)
第三节 - 事件驱动(http://www.fanyiwo.com/linux/32997.html)
第四节 - libuv(http://www.fanyiwo.com/linux/33257.html)
 
事件处理库
Redis 最初发布于 2009 年,它最牛逼的一件事情大概就是它的速度 —— 它能够处理大量的并发客户端连接。需要特别指出的是,它是用一个单线程来完成的,而且还不对保存在内存中的数据使用任何复杂的锁或者同步机制。
Redis 之所以如此牛逼是因为,它在给定的系统上使用了其可用的最快的事件循环,并将它们封装成由它实现的事件循环库(在 Linux 上是 epoll,在 BSD 上是 kqueue,等等)。这个库的名字叫做 ae。ae 使得编写一个快速服务器变得很容易,只要在它内部没有阻塞即可,而 Redis 则保证 注1 了这一点。
在这里,我们的兴趣点主要是它对文件事件的支持 —— 当文件描述符(如网络套接字)有一些有趣的未决事情时将调用注册的回调函数。与 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。
 
处理客户端请求
我们通过跟踪 Redis 服务器代码来看一下,ae 如何为客户端事件注册回调函数的。initServer 启动时,通过注册一个回调函数来读取正在监听的套接字上的事件,通过使用回调函数 acceptTcpHandler 来调用 aeCreateFileEvent。当新的连接可用时,这个回调函数被调用。它调用 accept 注2 ,接下来是 acceptCommonHandler,它转而去调用 createClient 以初始化新客户端连接所需要的数据结构。
createClient 的工作是去监听来自客户端的入站数据。它将套接字设置为非阻塞模式(一个异步事件循环中的关键因素)并使用 aeCreateFileEvent 去注册另外一个文件事件回调函数以读取事件 —— readQueryFromClient。每当客户端发送数据,这个函数将被事件循环调用。
readQueryFromClient 就让我们期望的那样 —— 解析客户端命令和动作,并通过查询和/或操作数据来回复。因为客户端套接字是非阻塞的,所以这个函数必须能够处理 EAGAIN,以及部分数据;从客户端中读取的数据是累积在客户端专用的缓冲区中,而完整的查询可能被分割在回调函数的多个调用当中。
 
将数据发送回客户端
在前面的内容中,我说到了 readQueryFromClient 结束了发送给客户端的回复。这在逻辑上是正确的,因为 readQueryFromClient 准备要发送回复,但它不真正去做实质的发送 —— 因为这里并不能保证客户端套接字已经准备好写入/发送数据。我们必须为此使用事件循环机制。
Redis 是这样做的,它注册一个 beforeSleep 函数,每次事件循环即将进入休眠时,调用它去等待套接字变得可以读取/写入。beforeSleep 做的其中一件事情就是调用 handleClientsWithPendingWrites。它的作用是通过调用 writeToClient 去尝试立即发送所有可用的回复;如果一些套接字不可用时,那么当套接字可用时,它将注册一个事件循环去调用 sendReplyToClient。这可以被看作为一种优化 —— 如果套接字可用于立即发送数据(一般是 TCP 套接字),这时并不需要注册事件 ——直接发送数据。因为套接字是非阻塞的,它从不会去阻塞循环。
 
为什么 Redis 要实现它自己的事件库?
在 第四节 中我们讨论了使用 libuv 来构建一个异步并发服务器。需要注意的是,Redis 并没有使用 libuv,或者任何类似的事件库,而是它去实现自己的事件库 —— ae,用 ae 来封装 epoll、kqueue 和 select。事实上,Antirez(Redis 的创建者)恰好在 2011 年的一篇文章(http://oldblog.antirez_com.votiw.cn/post/redis-win32-msft-patch.html) 中回答了这个问题。他的回答的要点是:ae 只有大约 770 行他理解的非常透彻的代码;而 libuv 代码量非常巨大,也没有提供 Redis 所需的额外功能。
现在,ae 的代码大约增长到 1300 多行,比起 libuv 的 26000 行(这是在没有 Windows、测试、示例、文档的情况下的数据)来说那是小巫见大巫了。libuv 是一个非常综合的库,这使它更复杂,并且很难去适应其它项目的特殊需求;另一方面,ae 是专门为 Redis 设计的,与 Redis 共同演进,只包含 Redis 所需要的东西。
这是我 前些年在一篇文章中(http://eli.thegreenplace_net.wyrug.cn/2017/benefits-of-dependencies-in-software-projects-as-a-function-of-effort/) 提到的软件项目依赖关系的另一个很好的示例:
依赖的优势与在软件项目上花费的工作量成反比。
在某种程度上,Antirez 在他的文章中也提到了这一点。他提到,提供大量附加价值(在我的文章中的“基础” 依赖)的依赖比像 libuv 这样的依赖更有意义(它的例子是 jemalloc 和 Lua),对于 Redis 特定需求,其功能的实现相当容易。
 
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 具有了更快的速度和弹性,这有点类似于在本系统文章中 第四节 讨论的工作队列。
 
注1:Redis 的一个核心部分是:它是一个 内存中 数据库;因此,查询从不会运行太长的时间。当然了,这将会带来各种各样的其它问题。在使用分区的情况下,服务器可能最终路由一个请求到另一个实例上;在这种情况下,将使用异步 I/O 来避免阻塞其它客户端。
注2:使用 anetAccept;anet 是 Redis 对 TCP 套接字代码的封装。
 
如何让网站不下线而从Redis 2迁移到Redis 3:http://www.fanyiwo.com/linux/32802.html
ubuntu 17.04安装最新版本redis:http://www.fanyiwo.com/linux/32785.html
Linux Redis重启数据丢失解决方案:http://www.fanyiwo.com/linux/32076.html
Ubuntu16.04下安装nginx+mysql+php+redis:http://www.fanyiwo.com/linux/31935.html
在阿里云上开放Redis默认的6379端口:http://www.fanyiwo.com/linux/31347.html
推荐教程
热点推荐
北京快乐8在线计划 上海时时乐如何杀码 北京快乐8总和多少算大 北京快乐8中奖规则 北京快乐8选号软件 北京pk10 在哪里玩
北京pk10七码稳赚技巧 北京pk10技巧规律2码 上海时时乐跨度0怎么算 北京pk10预测冠军定3码
北京pk10万能八码技巧 北京pk10六码稳赚技巧 北京pk10技巧6码稳赚 北京pk10六码稳赚技巧 北京pk10技巧规律2码 北京pk10七码稳赚技巧
pk10冠军技巧 北京pk10大特秘籍 四川快乐12推荐号码 北京pk10投注技巧 快乐十分开奖结果查询
上海早餐加盟 早点面条加盟 中式早餐加盟 早餐小吃店加盟 特色早餐
早餐加盟品牌 早点快餐加盟店 早餐店加盟哪家好 早点加盟项目 全国招商加盟
首钢早餐加盟 早餐馅饼加盟 早餐肠粉加盟 全国连锁加盟 加盟早点
正宗早点加盟 天津早点加盟 湖南特色早点加盟 凡夫子早餐加盟 流动早餐加盟
辽宁快乐12走势图 星城娱乐下载 山东11选五走势图 米博油烟机 pc蛋蛋幸运28预测
3d天中图库好运彩 云南时时彩预测 黑龙江11选5预测 新疆时时彩信息 时时彩平台代理赚钱秘密
快乐十分前三组常出号 六合彩图库 四季生肖中特 保时捷娱乐在线 贵州省十一选五
11选5稳杀两码公式 三分时时彩计划群 三肖中特 中后付款 撒网捕鱼技巧 彩票模拟选号