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 相关文章推荐
浅谈redis五大数据结构和使用场景
Apr 12 Redis
Redis安装启动及常见数据类型
Apr 14 Redis
Redis 配置文件重要属性的具体使用
May 20 Redis
详解缓存穿透击穿雪崩解决方案
May 28 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
Redis做数据持久化的解决方案及底层原理
Jul 15 Redis
Redis集群新增、删除节点以及动态增加内存的方法
Sep 04 Redis
redis缓存存储Session原理机制
Nov 20 Redis
redis数据结构之压缩列表
Mar 21 Redis
解决redis批量删除key值的问题
Mar 23 Redis
Redis安装使用RedisJSON模块的方法
Mar 23 Redis
一文搞懂Redis中String数据类型
Apr 03 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中时间函数date及常用的时间计算
2017/05/12 PHP
PHP实现十进制数字与二十六进制字母串相互转换操作示例
2018/08/10 PHP
使用PHPWord生成word文档的方法详解
2019/06/06 PHP
javascript垃圾收集机制与内存泄漏详细解析
2013/11/11 Javascript
JavaScript中的alert()函数使用技巧详解
2014/12/29 Javascript
JS实现仿中关村论坛评分后弹出提示效果的方法
2015/02/23 Javascript
Bootstrap前端开发案例二
2016/06/17 Javascript
jQuery AJAX timeout 超时问题详解
2016/06/21 Javascript
Angularjs---项目搭建图文教程
2016/07/08 Javascript
如何在AngularJs中调用第三方插件库
2017/05/21 Javascript
vue watch自动检测数据变化实时渲染的方法
2018/01/16 Javascript
vue2.0路由切换后页面滚动位置不变BUG的解决方法
2018/03/14 Javascript
基于vue-router 多级路由redirect 重定向的问题
2018/09/03 Javascript
vue3.0 CLI - 2.5 - 了解组件的三维
2018/09/14 Javascript
jQuery 点击获取验证码按钮及倒计时功能
2018/09/20 jQuery
JS数组求和的常用方法总结【5种方法】
2019/01/14 Javascript
JavaScript学习教程之cookie与webstorage
2019/06/23 Javascript
python局部赋值的规则
2013/03/07 Python
Python的Django框架中设置日期和字段可选的方法
2015/07/17 Python
Python实现Smtplib发送带有各种附件的邮件实例
2017/06/05 Python
TensorFLow 不同大小图片的TFrecords存取实例
2020/01/20 Python
python使用html2text库实现从HTML转markdown的方法详解
2020/02/21 Python
HTML5声音录制/播放功能的实现代码
2018/05/03 HTML / CSS
英国家用电器折扣网站:Electrical Discount UK
2018/09/17 全球购物
办公室保洁员岗位职责
2013/12/02 职场文书
医药工作岗位求职信分享
2013/12/31 职场文书
庆元旦活动总结
2014/07/09 职场文书
低碳环保演讲稿
2014/08/28 职场文书
解除租房协议书
2014/12/03 职场文书
表扬稿范文
2015/01/17 职场文书
Python 把两层列表展开平铺成一层(5种实现方式)
2021/04/07 Python
Flask搭建一个API服务器的步骤
2021/05/28 Python
Python标准库之typing的用法(类型标注)
2021/06/02 Python
关于redisson缓存序列化几枚大坑说明
2021/08/04 Redis
python保存图片的四个常用方法
2022/02/28 Python
「SHOW BY ROCK!!」“雫シークレットマインド”组合单曲MV公开
2022/03/21 日漫