Redis命令处理过程源码解析


Posted in Redis onFebruary 12, 2022

本文基于社区版Redis 4.0.8

Redis命令处理过程源码解析

1、命令解析

Redis服务器接收到的命令请求首先存储在客户端对象的querybuf输入缓冲区,然后解析命令请求的各个参数,并存储在客户端对象的argv和argc字段。

客户端解析命令请求的入口函数为readQueryFromClient,会读取socket数据存储到客户端对象的输入缓冲区,并调用函数processInputBuffer解析命令请求。

注:内联命令:使用telnet会话输入命令的方式

void processInputBuffer(client *c) {
    ......
    //循环遍历输入缓冲区,获取命令参数,调用processMultibulkBuffer解析命令参数和长度
    while(sdslen(c->querybuf)) {
        if (c->reqtype == PROTO_REQ_INLINE) {
            if (processInlineBuffer(c) != C_OK) break;//处理telnet方式的内联命令
        } else if (c->reqtype == PROTO_REQ_MULTIBULK) {
            if (processMultibulkBuffer(c) != C_OK) break; //解析命令参数和长度暂存到客户端结构体中
        } else {
            serverPanic("Unknown request type");
        }
    }    
}

//解析命令参数和长度暂存到客户端结构体中
int processMultibulkBuffer(client *c) {
    //定位到行尾
    newline = strchr(c->querybuf,'\r');
    //解析命令请求参数数目,并存储在客户端对象的c->multibulklen字段
    serverAssertWithInfo(c,NULL,c->querybuf[0] == '*');
    ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
    c->multibulklen = ll;
    pos = (newline-c->querybuf)+2;//记录已解析命令的请求长度resp的长度
    /* Setup argv array on client structure */
    //分配请求参数存储空间
    c->argv = zmalloc(sizeof(robj*)*c->multibulklen);
    
    // 开始循环解析每个请求参数
    while(c->multibulklen) {
        ......
        newline = strchr(c->querybuf+pos,'\r');
        if (c->querybuf[pos] != '$') {
            return C_ERR;
        ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
        pos += newline-(c->querybuf+pos)+2;
        c->bulklen = ll;//字符串参数长度暂存在客户端对象的bulklen字段
        
        //读取该长度的参数内容,并创建字符串对象,同时更新待解析参数multibulklen
        c->argv[c->argc++] =createStringObject(c->querybuf+pos,c->bulklen);
        pos += c->bulklen+2;
        c->multibulklen--;
    }

2、命令调用

当multibulklen的值更新为0时,表示参数解析完成,开始调用processCommand来处理命令,处理命令前有很多校验逻辑,如下:

void processInputBuffer(client *c) {
    
    ......
     //调用processCommand来处理命令
     if (processCommand(c) == C_OK) {
         ......
     }
}

//处理命令函数
int processCommand(client *c) {
    //校验是否是quit命令
    if (!strcasecmp(c->argv[0]->ptr,"quit")) {
        addReply(c,shared.ok);
        c->flags |= CLIENT_CLOSE_AFTER_REPLY;
        return C_ERR;
    }
    //调用lookupCommand,查看该命令是否存在
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if (!c->cmd) {
        flagTransaction(c);
        addReplyErrorFormat(c,"unknown command '%s'",
            (char*)c->argv[0]->ptr);
        return C_OK;
    //检查用户权限
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
    {
        addReply(c,shared.noautherr);
    //还有很多检查,不一一列举,比如集群/持久化/复制等
    /* 真正执行命令 */
    if (c->flags & CLIENT_MULTI &&
        c->cmd->proc != execCommand && c->cmd->proc != discardCommand &&
        c->cmd->proc != multiCommand && c->cmd->proc != watchCommand)
        queueMultiCommand(c);
        //将结果写入outbuffer
        addReply(c,shared.queued);
    } 
// 调用execCommand执行命令
void execCommand(client *c) {
    call(c,CMD_CALL_FULL);//调用call执行命令
//调用execCommand调用call执行命令
void call(client *c, int flags) {
    start = ustime();
    c->cmd->proc(c);//执行命令
    duration = ustime()-start;
    //如果是慢查询,记录慢查询
    if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) {
        char *latency_event = (c->cmd->flags & CMD_FAST) ?
                              "fast-command" : "command";
        latencyAddSampleIfNeeded(latency_event,duration/1000);
        //记录到慢日志中
        slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
    //更新统计信息:当前命令执行时间和调用次数
    if (flags & CMD_CALL_STATS) {
        c->lastcmd->microseconds += duration;
        c->lastcmd->calls++;

3、返回结果

Redis返回结果并不是直接返回给客户端,而是先写入到输出缓冲区(buf字段)或者输出链表(reply字段)

int processCommand(client *c) {
    ......
    //将结果写入outbuffer
    addReply(c,shared.queued);
    ......
    
}
//将结果写入outbuffer
void addReply(client *c, robj *obj) {
    //调用listAddNodeHead将客户端添加到服务端结构体的client_pending_write链表,以便后续能快速查找出哪些客户端有数据需要发送
    if (prepareClientToWrite(c) != C_OK) return;
    
    //然后添加字符串到输出缓冲区
    if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
        //如果添加失败,则添加到输出链表中
        _addReplyObjectToList(c,obj); 
}

addReply函数只是将待发送给客户端的数据暂存在输出链表或者输出缓冲区,那么什么时候将这些数据发送给客户端呢?答案是开启事件循环时,调用的beforesleep函数,该函数专门执行一些不是很费时的操作,如过期键删除,向客户端返回命令回复等

void beforeSleep(struct aeEventLoop *eventLoop) {
    ......
     /* Handle writes with pending output buffers. */
    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))) {
        client *c = listNodeValue(ln);
        c->flags &= ~CLIENT_PENDING_WRITE;
        listDelNode(server.clients_pending_write,ln);
        /* 发送客户端数据 */
        if (writeToClient(c->fd,c,0) == C_ERR) continue;
        /* If there is nothing left, do nothing. Otherwise install
         * the write handler. */
         //如果数据量很大,一次性没有发送完成,则进行添加文件事件,监听当前客户端socket文件描述符的可写事件即可
        if (clientHasPendingReplies(c) &&
            aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
                sendReplyToClient, c) == AE_ERR)
        {
            freeClientAsync(c);
        }
    }
    return processed;

到这里,命令请求才算真正处理完成了。

到此这篇关于Redis命令处理过程源码解析的文章就介绍到这了,更多相关Redis命令处理内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
springboot使用Redis作缓存使用入门教程
Jul 25 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redis字典实现、Hash键冲突及渐进式rehash详解
Sep 04 Redis
Redis三种集群模式详解
Oct 05 Redis
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
Feb 12 Redis
面试分析分布式架构Redis热点key大Value解决方案
Mar 13 Redis
Redis 哨兵机制及配置实现
Mar 25 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
浅谈Redis 中的过期删除策略和内存淘汰机制
Apr 03 Redis
Redis入门基础常用操作命令整理
Jun 01 Redis
Redis批量生成数据的实现
Jun 05 Redis
利用Redis实现点赞功能的示例代码
Jun 28 Redis
Redis+Lua脚本实现计数器接口防刷功能(升级版)
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
基于Redis zSet实现滑动窗口对短信进行防刷限流的问题
Feb 12 #Redis
聊聊redis-dump工具安装问题
Jan 18 #Redis
redis的list数据类型相关命令介绍及使用
Jan 18 #Redis
关于使用Redisson订阅数问题
Jan 18 #Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
You might like
批量去除PHP文件中bom的PHP代码
2012/03/13 PHP
PHP中使用localhost连接Mysql不成功的解决方法
2014/08/20 PHP
利用PHP绘图函数实现简单验证码功能的方法
2016/10/18 PHP
tp5框架前台无限极导航菜单类实现方法分析
2020/03/29 PHP
Laravel相关的一些故障解决
2020/08/19 PHP
JS 实现点击a标签的时候让其背景更换
2013/10/15 Javascript
javascript获取checkbox复选框获取选中的选项
2014/08/12 Javascript
AngularJS iframe跨域打开内容时报错误的解决办法
2015/01/26 Javascript
JavaScript使用Replace进行字符串替换的方法
2015/04/14 Javascript
JavaScript性能优化总结之加载与执行
2016/08/11 Javascript
JS实现的点击表头排序功能示例
2017/03/27 Javascript
jQuery插件imgAreaSelect基础讲解
2017/05/26 jQuery
JavaScript之DOM_动力节点Java学院整理
2017/07/03 Javascript
详解如何用模块化的方式写vuejs
2017/12/16 Javascript
Angular使用cli生成自定义文件、组件的方法
2018/09/04 Javascript
浅谈React之状态(State)
2018/09/19 Javascript
Mint UI组件库CheckList使用及踩坑总结
2018/12/20 Javascript
Node.js Windows Binary二进制文件安装方法
2019/05/16 Javascript
js中console在一行内打印字符串和对象的方法
2019/09/10 Javascript
jQuery高级编程之js对象、json与ajax用法实例分析
2019/11/01 jQuery
python根据距离和时长计算配速示例
2014/02/16 Python
python使用Matplotlib绘制分段函数
2018/09/25 Python
anaconda如何查看并管理python环境
2019/07/05 Python
用Pytorch训练CNN(数据集MNIST,使用GPU的方法)
2019/08/19 Python
MATLAB数学建模之画图汇总
2020/07/16 Python
python多线程爬取西刺代理的示例代码
2021/01/30 Python
快速一键生成Python爬虫请求头
2021/03/04 Python
你不知道的5个HTML5新功能
2016/06/28 HTML / CSS
Old Navy加拿大官网:美式休闲服饰品牌
2017/09/26 全球购物
卫生院健康教育实施方案
2014/06/07 职场文书
2014年科室工作总结
2014/11/20 职场文书
销售员岗位职责范本
2015/04/11 职场文书
社区党支部公开承诺书
2015/04/29 职场文书
mysql使用instr达到in(字符串)的效果
2022/04/03 MySQL
mysqldump进行数据备份详解
2022/07/15 MySQL
win10如何快速切换窗口 win10切换窗口快捷键分享
2022/07/23 数码科技