浅析Redis③:命令处理之数据返回Client(下)

写在前面

Redis作为我们日常工作中最常使用的缓存数据库,其重要性不言而喻,作为普通开发者,我们在日常开发中使用Redis,主要聚焦于Redis的基层数据结构的命令使用,很少会有人对Redis的内部实现机制进行了解,对于我而言,也是如此,但一直以来,我对于Redis的内部实现都很好奇,它为什么会如此高效,本系列文章是旨在对Redis源代码分析拆解,通过阅读Redis源代码,了解Redis基础数据结构的实现机制。

关于Redis的源码分析,已经有非常多的大佬写过相关的内容,最为著名的是《Redis设计与实现》,对于Redis源码的分析已经非常出色,本系列文章对于源码拆解时,并不会那么详细,相信大部分读者应该不是从事Redis的二次开发工作,对于源码细节过于深入,会陷入细节的泥潭,这是我在阅读源码时尽量避免的,我尽量做到对大体的脉络进行梳理,讲清楚主干逻辑,细节部分,如果读者有兴趣,可以自行参阅源码或相关资料。

本系列源代码,基于Redis 3.2.6

前言

在上两篇中

浅析Redis①:命令处理核心源码分析(上)

浅析Redis②:命令处理之epoll实现(中)

我们大致了解了Redis客户端命令请求的处理流程,在整个流程中,我们了解了Redis是如何处理来自客户端的命令请求,epoll的执行逻辑,我们还有最后一个问题没有解释,Redis是如何将数据写回Client端的?

本篇我们就围绕第一个问题,寻找答案,继续看Redis客户端命令请求的处理流程。

Redis数据返回Client端流程

Redis在命令处理时,在命令执行的末尾,都会调用一个addReply(),这里我们以最简单的STRING get为例:

t_string.c getGenericCommand()

int getGenericCommand(client *c) {robj *o;// 从字典中查询数据if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)return C_OK;// 将数据返回Clientif (o->type != OBJ_STRING) {addReply(c,shared.wrongtypeerr);return C_ERR;} else {addReplyBulk(c,o);return C_OK;}
}void addReplyBulk(client *c, robj *obj) {addReplyBulkLen(c,obj);addReply(c,obj);addReply(c,shared.crlf);
}

继续看addReply()的实现:

networking.c addReply()

void addReply(client *c, robj *obj) {if (prepareClientToWrite(c) != C_OK) return;// 核心,将数据写入内存缓存区,等待后续流程处理,写回Client Socketif (sdsEncodedObject(obj)) {if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)_addReplyObjectToList(c,obj);} else if (obj->encoding == OBJ_ENCODING_INT) {if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {char buf[32];int len;len = ll2string(buf,sizeof(buf),(long)obj->ptr);if (_addReplyToBuffer(c,buf,len) == C_OK)return;}obj = getDecodedObject(obj);if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)_addReplyObjectToList(c,obj);decrRefCount(obj);} else {serverPanic("Wrong obj->encoding in addReply()");}
}

上述流程,是string get命令执行后,数据处理的流程,可以发现,Redis并没有将数据直接返回Client端,而是将数据写入了一个叫做缓冲区的内存区域,那么缓冲区是什么?

Redis的内存缓冲区

在 Redis 中,缓冲区(buffer)是用于存储数据的内存区域。Redis 使用缓冲区来管理数据的读取、写入和传输过程。

Redis 的缓冲区主要有两个方面的应用:

  • 输入缓冲区(Input Buffer):当 Redis 接收到客户端发送的命令请求时,会先将请求数据存储在输入缓冲区中,然后再进行解析和处理。输入缓冲区用于临时存储从网络或其他输入源接收到的原始数据。
  • 输出缓冲区(Output Buffer):当 Redis 响应客户端的命令请求时,会先将响应数据存储在输出缓冲区中,然后再发送给客户端。输出缓冲区用于临时存储待发送的数据。

缓冲区在 Redis 中的作用是提高数据的处理效率和性能。通过使用缓冲区,Redis 可以批量读取和写入数据,减少了频繁的系统调用和网络传输开销。此外,缓冲区还可以用于临时存储数据,以便进行数据的加工和处理。

需要注意的是,Redis 缓冲区大小是有限的,它受到配置参数 client-output-buffer-limit 和 client-query-buffer-limit 的影响。

如果缓冲区已满,而输入或输出数据仍在不断到达,则可能导致连接被拒绝或数据丢失。

因此,在高并发或大数据量的场景中,需要根据实际情况调整缓冲区大小以保证系统的稳定性和性能。

OK,命令处理部分流程结束,我们把逻辑拉回到main函数中,聚焦aeMain()

ae.c aeMain()

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

在前两篇中,我们介绍过aeMain(),这里使用一个死循环,aeProcessEvents()轮询epoll是否存在就绪的事件,在aeProcessEvents()之前,我们需要关注beforesleep()

if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);

在轮询之前,都会执行beforesleep(),这个函数就是我们要关注的核心,继续看beforesleep()实现:

server.c beforeSleep()

void beforeSleep(struct aeEventLoop *eventLoop) {
....
....    
此处省略部分非核心代码
....  /* Write the AOF buffer on disk */flushAppendOnlyFile(0);// 将数据写回ClienthandleClientsWithPendingWrites();
}

networking.c handleClientsWithPendingWrites()

int handleClientsWithPendingWrites(void) {listIter li;listNode *ln;int processed = listLength(server.clients_pending_write);listRewind(server.clients_pending_write,&li);while((ln = listNext(&li))) {......省略部分非核心代码    // 核心,将数据通过socket返回Clientif (writeToClient(c->fd,c,0) == C_ERR) continue;// 还有部分数据没有写完,加入epoll,等待异步执行if (clientHasPendingReplies(c) &&aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR){// 释放内存freeClientAsync(c);}}return processed;
}

上述代码是执行数据返回Client的核心逻辑,可以参见代码注释,令人疑惑的部分是,为什么这段代码中,writeToClient()可能会执行两次?

原因如下:

第一次调用 writeToClient() 是为了尝试向客户端套接字写入数据。这里的目的是将服务器待发送的数据写入到套接字缓冲区中,以便后续通过网络发送给客户端。如果写入成功,则会继续判断该客户端是否还有待发送的数据。

第二次调用 writeToClient() 是在判断客户端是否还有待发送的数据后执行的。如果客户端仍然有待发送的数据,那么说明套接字的发送缓冲区已满,无法一次性将所有数据发送出去。此时,为了确保后续的数据能够被及时发送,需要将该客户端的套接字注册到可写事件上,以便在套接字可写时继续发送剩余的数据。

需要注意的是,第二次调用 writeToClient() 并不会立即执行数据的发送,而是在套接字变为可写时由事件循环机制触发相应的写入操作。

这样可以避免在套接字无法写入数据时出现阻塞的情况,提高服务器的并发性能。

OK,我们继续看writeToClient() 的实现逻辑。

networking.c writeToClient()

int writeToClient(int fd, client *c, int handler_installed) {ssize_t nwritten = 0, totwritten = 0;size_t objlen;size_t objmem;robj *o;// 循环读取内存缓冲区的数据,写回socket,返回Client端while(clientHasPendingReplies(c)) {if (c->bufpos > 0) {// 核心,执行socket写回nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);if (nwritten <= 0) break;c->sentlen += nwritten;totwritten += nwritten;/* If the buffer was sent, set bufpos to zero to continue with* the remainder of the reply. */if ((int)c->sentlen == c->bufpos) {c->bufpos = 0;c->sentlen = 0;}} else {o = listNodeValue(listFirst(c->reply));objlen = sdslen(o->ptr);objmem = getStringObjectSdsUsedMemory(o);if (objlen == 0) {listDelNode(c->reply,listFirst(c->reply));c->reply_bytes -= objmem;continue;}nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);if (nwritten <= 0) break;c->sentlen += nwritten;totwritten += nwritten;/* If we fully sent the object on head go to the next one */if (c->sentlen == objlen) {listDelNode(c->reply,listFirst(c->reply));c->sentlen = 0;c->reply_bytes -= objmem;}}.........省略部分非核心代码.........    }.........省略部分非核心代码.........    return C_OK;
}

writeToClient()就是核心写入的部分了,这里获取redisClient对象的bufpos,可以理解为缓冲区中的标记位置,如果存在待写入的数据,循环调用系统方法write写入socketFD中。

write() 函数用于向文件描述符(包括套接字)写入数据。在这段代码中,write() 函数被用于将数据写入到客户端的套接字中,即向客户端发送数据。

就此,Redis将数据返回Client的流程,我们就了解完毕。

老规矩,我们还是用一张流程图来简略描述整个过程:
Redis命令执行结果返回Client流程

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

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

相关文章

华为鸿蒙系统第三方桌面手机管家版本号12.0.1.330

起因 放假在家&#xff0c;准备把自己的旧手机给家里老人看小视频&#xff0c;但是老人不懂智能手机的复杂操作&#xff0c;就想换成《极简桌面》这个软件&#xff0c;在“设置->默认应用->桌面”&#xff0c;但是华为为了“保护用户的隐私以及合法权益”禁用了第三方启…

202417读书笔记|《画•大师 阿尔丰斯•穆夏》——在明亮大胆的色调中熠熠生辉

202417读书笔记|《画•大师 阿尔丰斯•穆夏》——在明亮大胆的色调中熠熠生辉 这是读完的第二本或者第三本穆夏的画集。很赞&#xff0c;他的绘画风格&#xff0c;构图&#xff0c;元素&#xff0c;取大自然的月桂树&#xff0c;常青藤&#xff0c;&#x1f319;的不同形态&…

链表与二叉树-数据结构

链表与二叉树-数据结构 创建叶子node节点建立二叉树三元组&#xff1a;只考虑稀疏矩阵中非0的元素&#xff0c;并且存储到一个类&#xff08;三元组&#xff09;的数组中。 创建叶子node节点 class Node{int no;Node next;public Node(int no){this.nono;} } public class Lb…

Fink CDC数据同步(三)Flink集成Hive

1 目的 持久化元数据 Flink利用Hive的MetaStore作为持久化的Catalog&#xff0c;我们可通过HiveCatalog将不同会话中的 Flink元数据存储到Hive Metastore 中。 利用 Flink 来读写 Hive 的表 Flink打通了与Hive的集成&#xff0c;如同使用SparkSQL或者Impala操作Hive中的数据…

流畅的Python(七)-函数装饰器和闭包

一、核心要义 主要解释函数装饰器的工作原理&#xff0c;包括最简单的注册装饰器和较复杂的参数化装饰器。同时&#xff0c;因为装饰器的实现依赖于闭包&#xff0c;因此会首先介绍闭包存在的原因和工作原理。 二、代码示例 1、变量作用域规则 #!/usr/bin/env python # -*-…

前端学习笔记 | HTML5+CSS3静态网页制作的技巧(持续更新)

注&#xff1a;本文的css样式均使用less写法 1、字体居中 &#xff08;1&#xff09;先text-align:center;行内元素水平居中 &#xff08;2&#xff09;再line-heigh:(盒子高度);行内元素垂直居中 text-align: center;line-height: ( 30 / vw ); 2、盒子居中 情景1&#…

【课程作业_01】国科大2023模式识别与机器学习实践作业

国科大2023模式识别与机器学习实践作业 作业内容 从四类方法中选三类方法&#xff0c;从选定的每类方法中 &#xff0c;各选一种具体的方法&#xff0c;从给定的数据集中选一 个数据集&#xff08;MNIST&#xff0c;CIFAR-10&#xff0c;电信用户流失数据集 &#xff09;对这…

【大数据技术攻关专题】「Apache-Flink零基础入门」手把手+零基础带你玩转大数据流式处理引擎Flink(基础加强+运行原理)

手把手零基础带你玩转大数据流式处理引擎Flink&#xff08;运行机制原理加深&#xff09; 前提介绍运行Flink应用运行机制Flink的两大核心组件JobManagerTaskManagerTaskSlot Flink分层架构Stateful Stream ProcessingDataStream和DataSetDataStream&#xff08;数据流&#xf…

GPIO中断

1.EXTI简介 EXTI是External Interrupt的缩写&#xff0c;指外部中断。在嵌入式系统中&#xff0c;外部中断是一种用于处理外部事件的机制。当外部事件发生时&#xff08;比如按下按钮、传感器信号变化等&#xff09;&#xff0c;外部中断可以立即打断正在执行的程序&#xff0…

大红喜庆版UI猜灯谜小程序源码/猜字谜微信小程序源码

今天给大家带来一款UI比较喜庆的猜灯谜小程序&#xff0c;大家看演示图的时候当然也是可以看得到那界面是多么的喜庆&#xff0c;而且新的一年也很快就来了,所以种种的界面可能都比较往喜庆方面去变吧。 这款小程序搭建是免服务器和域名的&#xff0c;只需要使用微信开发者工具…

Linux一键部署telegraf 实现Grafana Linux 图形展示

influxd2前言 influxd2 是 InfluxDB 2.x 版本的后台进程,是一个开源的时序数据库平台,用于存储、查询和可视化时间序列数据。它提供了一个强大的查询语言和 API,可以快速而轻松地处理大量的高性能时序数据。 telegraf 是一个开源的代理程序,它可以收集、处理和传输各种不…

Linux开发工具

前言&#xff1a;哈喽小伙伴们&#xff0c;经过前边的学习我们已经掌握了Linux的基本指令和权限&#xff0c;相信大家学完这些之后都会对Linux有一个更加深入的认识&#xff0c;但是Linux的学习可以说是从现在才刚刚开始。 这篇文章&#xff0c;我们将讲解若干个Linux的开发工…

Java基础数据结构之Map和Set

Map和Set接口 1.Set集合&#xff1a;独特性与无序性 Set是Java集合框架中的一种&#xff0c;它代表着一组无序且独特的元素。这意味着Set中的元素不会重复&#xff0c;且没有特定的顺序。Set接口有多个实现类&#xff0c;如HashSet、LinkedHashSet和TreeSet。 2.Map集合&…

Redis核心技术与实战【学习笔记】 - 19.Pika:基于SSD实现大容量“Redis”

前言 随着业务数据的增加&#xff08;比如电商业务中&#xff0c;随着用户规模和商品数量的增加&#xff09;&#xff0c;就需要 Redis 能保存更多的数据。你可能会想到使用 Redis 切片集群&#xff0c;把数据分散保存到不同的实例上。但是这样做的话&#xff0c;如果要保存的…

利用牛顿方法求解非线性方程(MatLab)

一、算法原理 1. 牛顿方法的算法原理 牛顿方法&#xff08;Newton’s Method&#xff09;&#xff0c;也称为牛顿-拉弗森方法&#xff0c;是一种用于数值求解非线性方程的迭代方法。其基本思想是通过不断迭代来逼近方程的根&#xff0c;具体原理如下&#xff1a; 输入&#…

PCB笔记(二十三):allegro 标注长宽(一般用于测量板宽)时如何显示双单位

步骤&#xff1a;首先选择标注工具&#xff0c;然后右键→Parameters&#xff0c;在弹出来的窗口中√上如下图二所示选项 最终要达到显示单位的效果的话&#xff0c;需要在Text项键入%v%u。 今天就记录到这里啦O

Leetcode206:反转链表

一、题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表 示例&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]输入&#xff1a;head [] 输出&#xff1…

[ESP32 IDF]web server

目录 通过web server控制LED 核心原理解析 分区表 web server的使用 错误Header fields are too long的解决 通过web server控制LED 通过网页控制LED灯的亮灭&#xff0c;一般的ESP32开发板都可以实现&#xff0c;下面这篇文章是国外开发者提供的一个通过web server控制…

13.2K Star,12306 抢票助手帮你回家

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 马上过年了&#xff0c;今年你在哪里过年&#xff1f;回老家吗&#x…