浅析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.hqwc.cn/news/450531.html

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

相关文章

109.乐理基础-五线谱-五线谱的附点、休止符、连线、延音线

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;五线谱的拍号、音符与写法-CSDN博客 上一个内容里练习的答案&#xff1a; 附点&#xff1a;写在符头的右方&#xff0c;附点的作用与简谱一样&#xff0c;延长前面音符本身时值的一半&#xff08;附点&#xff09;…

242. Valid Anagram(有效的字母异位词)

问题描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数都相同&#xff0c;则称 s 和 t 互为字母异位词。 问题分析 此问题与383. Ransom Note(赎金信)类似&#xff0c;只是字符变为了…

dubbo+sentinel最简集成实例

说明 在集成seata后&#xff0c;下面来集成sentinel进行服务链路追踪管理&#xff5e; 背景 sample-front网关服务已配置好 集成 一、启动sentinel.jar 1、官网下载 选择1:在本地启动 nohup java -Dserver.port8082 -Dcsp.sentinel.dashboard.serverlocalhost:8082 -Dp…

springcloud bus消息总线

简介 Spring Cloud Bus 配合Spring Cloud Config 使用可以实现配置的动态刷新。 Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架&#xff0c;它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus目前支持RabbitMQ和Kafka。 Spring C…

Transformer实战-系列教程1:Transformer算法解读1

&#x1f6a9;&#x1f6a9;&#x1f6a9;Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 Transformer实战-系列教程1&#xff1a;Transformer算法解读1 Transformer实战-系列教程2&#xff1a;Transformer算法解读2 现在最火的AI内容&#xff0c;chatGPT、视觉大模…

2024年【道路运输企业安全生产管理人员】免费试题及道路运输企业安全生产管理人员模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员免费试题参考答案及道路运输企业安全生产管理人员考试试题解析是安全生产模拟考试一点通题库老师及道路运输企业安全生产管理人员操作证已考过的学员汇总&#xff0c;相对有效帮助道路运…

Python||五城P.M.2.5数据分析与可视化_使用华夫图分析各个城市的情况(下)

目录 沈阳市的空气质量 华夫图 柱状图 总结 五城P.M.2.5数据分析与可视化——北京市、上海市、广州市、沈阳市、成都市&#xff0c;使用华夫图和柱状图分析各个城市的情况 沈阳市的空气质量 华夫图 import numpy as np import pandas as pd import matplotlib.pyplot as plt …

网络安全之漏洞扫描

漏洞是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷&#xff0c;从而可以使攻击者能够在未授权的情况下访问或破坏系统。这些缺陷、错误或不合理之处可能被有意或无意地利用&#xff0c;从而对一个组织的资产或运行造成不利影响&#xff0c;如信息系统被攻击或控制…

华为数通方向HCIP-DataCom H12-821题库(单选题:401-420)

第401题 R1的配置如图所示,此时在R1查看FIB表时,关于目的网段192.168.1.0/24的下跳是以下哪一项? A、10.0.23.3 B、10.0.12.2 C、10.0.23.2 D、10.0.12.1 【答案】A 【答案解析】 该题目考查的是路由的递归查询和 RIB 以及 FIB 的关系。在 RIB 中,静态路由写的是什么,下…

2阶段提交_3阶段提交(phase-commit)

1. 2PC&#xff08;两阶段提交) 如上所示是2阶段提交的一个过程&#xff0c;可为什么要进行两阶段提交呢&#xff1f;这里主要来说是将操作事务能力和提交、回滚事务能力分开来做成2阶段&#xff0c;如果不分开会造成什么后果呢&#xff1a; 如果单纯 A 向 B 发送一个请求就以…

AS-V1000 视频监控平台产品介绍:客户端功能介绍(一)

目 录 一、引言 1.1 AS-V1000视频监控平台介绍 1.2平台服务器配置说明 二、软件概述 2.1 客户端软件用途 2.2 客户端功能 三、客户端功能说明 3.1 登陆和主界面 3.1.1登陆界面 3.1.2登陆操作 3.1.3主界面 3.1.4资源树 3.2 视频预览 3.2.1视频预览界面 3.2.…

YGG 为 Axie Infinity: Origins 发布超级任务游戏内训练器,深化对 Ronin 的支持

自 2023 年以来&#xff0c;Ronin 已成为增长最快的游戏区块链。由于 Axie Infinity 和 Pixels 等游戏的持续成功&#xff0c;日活跃用户数量至少增长了 10 倍。在过去的一年里&#xff0c;有超过 120 万个新地址加入&#xff0c;并且&#xff0c;这个数字还在持续增长。 ​Ron…