性能突出的 Redis 是咋使用 epoll 的?

c0b44862ec5b8d3bf8ac728e888cde5f.gif

作者 | 闪客

来源 | 低并发编程

我是个 redis 服务,我马上就要启动了

因为我的主人正在控制台输入:

./redis-server

bc3f6620349c72e6a8da89a0ce521fb6.png

宏观上看下我的流程

突然,主人按下了回车键,不得了了。

shell 程序把我的程序加载到了内存,开始执行我的 main 方法,一切就从这里开始了。

int main(int argc, char **argv) {...initServer();...aeCreateFileEvent(fd, acceptHandler, ...);...aeMain();...
}

不要觉得我这里很复杂,其实主要就三大步。

第一步,我通过 listenToPort() 方法创建了一个 TCP 连接。

f2e29ff0adaba51a6ccb7e9560a0b8bf.png

我的这个方法真是见名知意,而且如果展开看就更会发现没什么神秘的,就是 socket bind listen 标准三步走,建立了一个 TCP 监听,返回了一个文件描述符 fd。

第二步,我通过 aeCreateFileEvent() 方法,将上面那个创建了 TCP 连接返回的文件描述符 fd,加入到一个叫 aeFileEvent的链表中。

502a9879382a745dc4ef868764efd196.png

同时将这个文件描述符绑定一个函数 acceptHandler,这样当有客户端连接进来时,便会执行这个函数。

1ddfbf8a8380ac9c9bb76b0c50f5f849.png

第三步,我通过 aeMain() 方法,将上面的 aeFileEvent 链表中的文件描述符,统统作为 select 的入参,这是 IO 多路复用模式。

cedc3e64f2c54d312d4130716caf1c7b.png

好了,其实就是开启了一个 TCP 监听,然后如果有客户端进来的话,让他执行 acceptHandler 函数而已。

之后我就一直死等着客户端连接了。

void aeMain(aeEventLoop *eventLoop)
{eventLoop->stop = 0;while (!eventLoop->stop)aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}

7a720951200b59398b1aaf0ae68aca14.gif

fc6afb27841769910a12073ac0b4dd33.png

展开体验下我的具体工作

此时,另外一个人启动了一个 redis-client,连接到了我。

redis-cli -h host -p port

那么我头上的 fd 就会感知有数据读入,并执行 acceptHandler 方法。

static void acceptHandler(...) {...cfd = anetAccept(...);...c = createClient(cfd))...
}

可以看到,当有新客户端连接进来时,便会调用 createClient 创建一个专属的 client 为其服务。

static redisClient *createClient(int fd) {...aeCreateFileEvent(c->fd, readQueryFromClient, ...);...
}

这里又可以看到,所谓的专属服务,其实仍然是这个 aeCreateFileEvent 函数。

这个上面说了,这个函数的功能就是把文件描述符挂在链表上,然后分配一个处理函数。

当然,这回的处理函数不再是处理新客户端连接的 acceptHandler,而是处理具体客户端传来的 redis 命令的函数 readQueryFromClient

542e23435454ed8dbd07ec94bf77a4d3.png

不难想象,如果再来一个客户端,又来一个客户端... 那么不断将新客户端的文件描述符挂上去即可,而监听新客户端连接的,始终是最上面那个文件描述符。

fcc190c1ab3620585ea720cb894d1b46.png

好了,服务端开启了监听,客户端也连上了服务端,此时我仍然在死等状态,只不过等的不只是新客户端连接到达,还在等待已经连接上的客户端发来命令。

a258af58804e638dc2060b17298d5a0f.gif

请注意,这里的死等,只有一个线程,循环调用 aeProcessEvents 函数,用 select 的方式监听多个文件描述符。放上刚刚 main 方法的第三步,帮大家回忆一下。

void aeMain(aeEventLoop *eventLoop)
{eventLoop->stop = 0;while (!eventLoop->stop)aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}

当有新客户端建立连接时,会触发 acceptHandler 函数执行,多出一个等待数据的描述符。

1809db805ac56e77dc88fbb9bbe9bc40.gif

当有客户端数据传来时,会触发 readQueryFromClient 函数执行,完成这个命令的操作。

a3ab62080cf9dc64fe8e7f89fb41b535.gif

注意,由于只有一个线程在监听这些描述符,并做处理。所以即使客户端并发地发送命令,后面仍然是依次取出命令,顺序执行

c4cad8891e212562849c1ad8816dacca.gif

这也就是我们常常说的,redis 是单线程的,命令与命令之间是顺序执行,无需考虑线程安全的问题。

fcba724021cd409c9a195d5cdd924182.png

为了方便大家吹牛,我来拔高一下

大家发现没,我的启动过程,其实就分成两个大的部分。

一个是监听客户端的请求,就是用 IO 多路复用的方式,监听多个文件描述符,就刚刚那个 aeMain() 方法干的事嘛。

一个是执行相应的函数去处理这个请求,具体执行什么函数就是出现多次的 aeCreateFileEvent() 方法去绑定的,这个相应的函数说得高大上一点,叫做事件处理器

633194c064092424b963f7d7db36a5c6.png

这里所谓的连接应答处理器,就是刚刚监听连接的文件描述符所绑定的函数 acceptHandler。

所谓的命令请求处理器,就是监听客户端命令(读事件)的文件描述符绑定的函数 readQueryFromClient。

所谓的命令回复处理器,就是后面要提到的,监听客户端响应(写事件)的文件描述符绑定的函数 sendReplyToClient。

这种一个负责响应 IO 事件,一个负责交给相应的事件处理器去处理,就叫做 Reactor 模式

Redis 正是基于 Reactor 模式开发了自己的文件事件处理器,实现了高性能的网络通信模型,并且保持了 Redis 内部单线程设计的简单性

有点担心这句话吹牛的逼格不够,其实我是参考了《Redis 设计与实现》,截图给大家。

d0a0604cdb482c6b381a83872a22bd6c.png



ef1bd6685e64d5025b5914e1306773ca.png

具体怎么执行一个 Redis 命令

现在,我们通过一个已建立好连接的客户端,发一个 redis 命令。

<client 6379> set dibingfa niubi

此时 readQueryFromClient 函数将被执行。

这个函数会去一张表中寻找命令所对应的函数,这部分用的编码技巧叫命令模式。

static struct redisCommand cmdTable[] = {{"get",getCommand,2,REDIS_CMD_INLINE},{"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},{"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM},{"del",delCommand,-2,REDIS_CMD_INLINE},{"exists",existsCommand,2,REDIS_CMD_INLINE},...
}

找到了 set 命令对应的函数就是 setCommand

这个函数,最终就会一步步地将 key 和 value 分别存储起来。

处理完命令后,就要发送响应给客户端了。

static void setCommand(redisClient *c) {...addReply(c, nx ? shared.cone : shared.ok);
}

这个响应,并不是直接同步写回去,当然也不是开启一个线程去异步写回去。

它仍然是调用那个万恶的 aeCreateFileEvent 函数,将 sendReplyToClient 函数挂在需要响应的客户端连接的文件描述符上。

static void addReply(redisClient *c, robj *obj) {...aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c, NULL) == AE_ERR);
}

好了,这回上一小节挖的坑,终于补上了。

以上这些个破玩意,就是我的启动过程啦,我是不是很可爱。

69c18e046eb2e51a447860a347ba3531.gif

后记

整篇文章我好像没讲 Redis 为啥那么快,因为我感觉这个问题问得不好。

你可以从接收网络请求的 IO 多路复用角度说起,也可以从事件处理器驱动的 Reactor 模式说起,还可以从具体处理命令时的数据结构说起,比如单单是字符串背后的 sds 其实就做了很多的巧妙设计。

如果我是面试官,我会具体让面试者聊聊 Redis 的启动流程,或者 Redis 处理命令的整个流程。

这里面可挖的点挺多的,如果能谈笑风生,那自然是技术水平还不错。

另外,你会发现本文出现的很多唬人的术语,比如 Reactor 模式,事件处理器等,看一遍 Redis 源码后你会发现真的非常简单。

毫不客气地说,一切丝毫不谈具体实现,和你堆砌一大堆唬人名词的文章或者人,都是在耍流氓。

本文我参考的是 Redis3.0.0 源码,但成文时用的讲解代码是 Redis1.0.0,整个网络模块的设计是完全一样的。

7f3dc345d1321257fefbe5d014466cdf.gif

35b6589e06ed7b16be24dc67864f3c8f.png

往期推荐

Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?

如果被问到分布式锁,应该怎样回答?

三分钟教你用 Scarlet 写一个 WebSocket App

Java 底层知识:什么是 “桥接方法” ?

f86b7c8c9cb5498b5c7e0f270506aed9.gif

点分享

ea767ff9c4f0a921ec9e8e30f49de3cb.gif

点收藏

dc7b4e963069e3e670a94b29dd54fdaa.gif

点点赞

e3d90bba86b420b2debe9e0e7f09137b.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云重磅发布业务中台产品 BizWorks,中台发展进入下一个阶段

简介&#xff1a; 业务中台产品BizWorks重磅发布&#xff0c;这可以看作是阿里云在 “做厚中台” 战略上继 “云钉一体”之后的又一个新动作&#xff01; 10 月 19 日&#xff0c;2021 云栖大会正式开幕&#xff0c;连续举办多年的云栖大会俨然已经成为了国内科技产业展示前沿…

java32位怎么用eclipse_无法在Windows 7 32位上打开eclipse

我正在使用Eclipse Indigo(eclipse-jee-indigo-SR2-win32) . 当我双击eclipse.exe时&#xff0c;会出现以下对话框&#xff1a;日志文件的内容如下&#xff1a;!SESSION 2013-05-27 17:55:26.853 -----------------------------------------------eclipse.buildIdM20120208-080…

云栖发布|企业级互联网架构全新升级 ,助力数字创新

简介&#xff1a; 云原生产品家族全面升级&#xff0c;让业务技术团队有了更多选择&#xff0c;通过简单、丰富、开放和低成本的 PaaS 服务&#xff0c;帮助企业客户更简单、更高效的进行在云上创新&#xff0c;搭建更符合业务需要和团队情况的技术体系。 作者&#xff5c;白玙…

当类的泛型相关时,如何在两个泛型类之间创建类似子类型的关系呢

作者 | 阿Q来源 | 阿Q说代码事情是这样的&#xff1a;对话中的截图如下&#xff1a;看了阿Q的解释&#xff0c;你是否也和“马小跳”一样存在疑问呢&#xff1f;请往&#x1f447;看我们都知道在java中&#xff0c;只要是类型兼容&#xff0c;就可以将一种类型的对象分配给另一…

java 垃圾回收 新生代_Java垃圾回收

一、概述Java垃圾回收器实现内存的自动分配和回收&#xff0c;这两个操作都发生在Java堆上(还包括方法区&#xff0c;即永久代)。垃圾回收操作不是实时的发生(对象死亡不会立即释放)&#xff0c;当内存消耗完或者是达到某一指标(threshold,使用内存占总内存的比列&#xff0c;比…

一图看懂云栖大会「云原生」发布

简介&#xff1a; 云原生产品全新升级 原文链接 本文为阿里云原创内容&#xff0c;未经允许不得转载。

明明还有大量内存,为啥报错“无法分配内存”?

作者 | 张彦飞allen来源 | 开发内功修炼近日小伙伴和我说了线上服务器出现一个诡异的问题&#xff0c;执行任何命令都是报错“fork:无法分配内存”。这个问题最近出现的&#xff0c;前几次重启后解决的&#xff0c;但是每隔 2-3 天就会出现一次。# service docker stop -bash f…

先行一步,7大技术创新和突破,阿里云把 Serverless 领域的这些难题都给解了

简介&#xff1a; 函数计算 FC 首创 GPU 实例、业内首发实例级别可观测和调试、率先提供端云联调和多环境部署能力、GB 级别镜像启动时间优化至秒级、VPC 网络建连优化至200ms&#xff0c;Serverless 应用引擎 SAE 支持微服务框架无缝迁移、无需容器化改造、业内首创混合弹性策…

基于Delta lake、Hudi格式的湖仓一体方案

简介&#xff1a; Delta Lake 和 Hudi 是流行的开放格式的存储层&#xff0c;为数据湖同时提供流式和批处理的操作&#xff0c;这允许我们在数据湖上直接运行 BI 等应用&#xff0c;让数据分析师可以即时查询新的实时数据&#xff0c;从而对您的业务产生即时的洞察。MaxCompute…

如何新建java内部类_java内部类-1(内部类的定义)

小胖从官网出发&#xff0c;研究下为什么我们需要些内部类&#xff0c;内部类的区别和联系。思考三个问题&#xff1a;(1)为什么需要内部类&#xff1f;静态内部类和非静态内部类有什么区别&#xff1b;(2)为什么内部类可以无条件访问外部类成员&#xff1b;(3)为什么jdk1.8之前…

stack vs heap:栈区分配内存快还是堆区分配内存快 ?

作者 | 码农的荒岛求生来源 | 码农的荒岛求生有伙伴问到底是从栈上分配内存快还是从堆上分配内存快&#xff0c;这是个比较基础的问题&#xff0c;今天就来聊一聊。栈区的内存申请与释放毫无疑问&#xff0c;显然从栈上分配内存更快&#xff0c;因为从栈上分配内存仅仅就是栈指…

CDP 平台简介

简介&#xff1a; EDC 建立在 Cloudera Data Platform(CDP) 之上&#xff0c;该产品结合了 Cloudera Enterprise Data Hub 和 Hortonworks Data Platform Enterprise 的优点&#xff0c;并在技术堆栈中增加了新功能和对已有技术提供了增强功能。这种统一的发行是一个可扩展且可…

400倍加速, PolarDB HTAP实时数据分析技术解密

简介&#xff1a; PolarDB MySQL是因云而生的一个数据库系统, 除了云上OLTP场景&#xff0c;大量客户也对PolarDB提出了实时数据分析的性能需求。对此PolarDB技术团队提出了In-Memory Column Index(IMCI&#xff09;的技术方案&#xff0c;在复杂分析查询场景获得的数百倍的加速…

建立数字化、学习型人事平台,HR 与业务终于不再「隔空对话」

本篇文章暨 CSDN《中国 101 计划》系列数字化转型场景之一。 《中国 101 计划——探索企业数字化发展新生态》为 CSDN 联合《新程序员》、GitCode.net 开源代码仓共同策划推出的系列活动&#xff0c;寻访一百零一个数字化转型场景&#xff0c;聚合呈现并开通评选通道&#xff…

OpenYurt 深度解读|开启边缘设备的云原生管理能力

简介&#xff1a; 北京时间 9 月 27 号&#xff0c;OpenYurt 发布 v0.5.0 版本。新发布版本中首次提出 kubernetes-native非侵入、可扩展的边缘设备管理标准&#xff0c;使 Kubernetes 业务负载模型和 IOT 设备管理模型无缝融合。 作者&#xff5c;贾燚星(VMware), 何淋波(阿里…

Cloudera Manager 术语和架构

简介&#xff1a; 本文介绍了Cloudera Manager 的常见术语和架构 Cloudera Manager 术语 为了有效地使用Cloudera Manager&#xff0c;您应该首先了解其术语。 术语之间的关系如下所示&#xff0c;其定义如下&#xff1a; 有时&#xff0c;术语服务和角色用于同时指代类型和…

冬奥网络安全卫士被表彰突出贡献,探寻冬奥背后的安全竞技

奥运史上首次公开招募白帽子担任“冬奥网络安全卫士”。 据统计&#xff0c;从冬奥会开始到冬残奥会闭幕式结束&#xff0c;奇安信共检测日志数量累积超1850亿&#xff0c;日均检测日志超37亿&#xff0c;累计发现修复漏洞约5800个&#xff0c;发现恶意样本54个&#xff0c;排查…

打破 Serverless 落地边界,阿里云 SAE 发布 5 大新特性

简介&#xff1a; SAE 的 5 大新特性、4 大最佳实践&#xff0c;打破了 Serverless 落地的边界&#xff0c;让 All on Serverless 成为可能. 微服务场景&#xff0c;开源自建真的最快最省最稳的&#xff1f;复杂性真的会成为 Kubernetes 的“致命伤”吗&#xff1f;企业应用容…

java线程一定是thread_深入理解Java多线程(multiThread)

多线程的基本概念一个java程序启动后&#xff0c;默认只有一个主线程(Main Thread)。如果我们要使用主线程同时执行某一件事&#xff0c;那么该怎么操作呢&#xff1f;例如&#xff0c;在一个窗口中&#xff0c;同时画两排圆&#xff0c;一排在10像素的高度&#xff0c;一排在5…

技术解读|云上企业级存储——打开存储新维度,促进用户核心业务创新

简介&#xff1a; 将企业级存储和云的特点进行完美的融合是云上企业级存储的目标&#xff0c;它打开存储更多新的维度&#xff0c;在保障用户业务永续的同时&#xff0c;帮助用户更好的进行业务创新。本文属ESSD技术解读的总篇&#xff0c;总体介绍ESSD 云盘创新融合了云和企业…