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延迟队列的实现代码
May 13 Redis
浅谈redis缓存在项目中的使用
May 20 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
详解Redis基本命令与使用场景
Jun 01 Redis
Redis性能监控的实现
Jul 09 Redis
redis 解决库存并发问题实现数量控制
Apr 08 Redis
Redis实战高并发之扣减库存项目
Apr 14 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
浅谈Redis缓冲区机制
Jun 05 Redis
利用Redis实现点赞功能的示例代码
Jun 28 Redis
redis lua限流算法实现示例
Jul 15 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
《OVERLORD》手游英文版即将上线 手机上也能扮演骨王
2020/04/09 日漫
PHP脚本的10个技巧(7)
2006/10/09 PHP
elgg 获取文件图标地址的方法
2010/03/20 PHP
php 图片加水印与上传图片加水印php类
2010/05/12 PHP
PHP代码实现表单数据验证类
2015/07/28 PHP
php中Ioc(控制反转)和Di(依赖注入)
2017/05/07 PHP
Laravel框架运行出错提示RuntimeException No application encryption key has been specified.解决方法
2019/04/02 PHP
php使用lua+redis实现限流,计数器模式,令牌桶模式
2019/04/04 PHP
JavaScript 构造函数 面相对象学习必备知识
2010/06/09 Javascript
bootstrap data与jquery .data
2014/07/07 Javascript
JavaScript实现找出数组中最长的连续数字序列
2014/09/03 Javascript
JS+CSS实现鼠标经过弹出一个DIV框完整实例(带缓冲动画渐变效果)
2016/03/25 Javascript
JS实现静态页面搜索并高亮显示功能完整示例
2017/09/19 Javascript
[08:07]DOTA2每周TOP10 精彩击杀集锦vol.8
2014/06/25 DOTA
[00:27]DOTA2荣耀之路2:Patience from zhou!
2018/05/24 DOTA
Python实现按特定格式对文件进行读写的方法示例
2017/11/30 Python
python编程实现12306的一个小爬虫实例
2017/12/27 Python
详谈Python3 操作系统与路径 模块(os / os.path / pathlib)
2018/04/26 Python
Python3.6通过自带的urllib通过get或post方法请求url的实例
2018/05/10 Python
python使用matplotlib库生成随机漫步图
2018/08/27 Python
对Python生成器、装饰器、递归的使用详解
2019/07/19 Python
详解如何用TensorFlow训练和识别/分类自定义图片
2019/08/05 Python
django之从html页面表单获取输入的数据实例
2020/03/16 Python
详解用 python-docx 创建浮动图片
2021/01/24 Python
html5 音乐播放器 audio 标签使用概述
2013/07/15 HTML / CSS
英国领先的办公用品供应商:Viking
2016/08/01 全球购物
《狐假虎威》教学反思
2014/02/07 职场文书
拉拉队口号
2014/06/16 职场文书
村级四风对照检查材料
2014/08/24 职场文书
中学生爱国演讲稿
2014/09/05 职场文书
2014年电教工作总结
2014/12/19 职场文书
老公保证书
2015/01/17 职场文书
幼儿园感恩节活动总结
2015/03/24 职场文书
2015年电话销售工作总结范文
2015/04/20 职场文书
2016年大学生党员公开承诺书
2016/03/24 职场文书
MySQL数据库中varchar类型的数字比较大小的方法
2021/11/17 MySQL