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 Cluster 字段模糊匹配及删除
May 27 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
Redis性能监控的实现
Jul 09 Redis
详解redis在微服务领域的贡献
Oct 16 Redis
Redis如何使用乐观锁(CAS)保证数据一致性
Mar 25 Redis
Redis分布式锁的7种实现
Apr 01 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
Redis基本数据类型Zset有序集合常用操作
Jun 01 Redis
Redis基本数据类型List常用操作命令
Jun 01 Redis
Redis入门基础常用操作命令整理
Jun 01 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
smarty section简介与用法分析
2008/10/03 PHP
浅谈laravel中的关联查询with的问题
2019/10/10 PHP
JavaScript窗口功能指南之在窗口中书写内容
2006/07/21 Javascript
JS 参数传递的实际应用代码分析
2009/09/13 Javascript
javascript 操作select下拉列表框的一点小经验
2010/03/20 Javascript
struts2+jquery组合验证注册用户是否存在
2014/04/30 Javascript
jQuery判断当前点击的是第几个li的代码
2014/09/26 Javascript
JS实现带有抽屉效果的产品类网站多级导航菜单代码
2015/09/15 Javascript
javascript实现tab切换的两个实例
2015/11/05 Javascript
Nodejs Express4.x开发框架随手笔记
2015/11/23 NodeJs
Jquery easyui开启行编辑模式增删改操作
2016/01/14 Javascript
JavaScript下的时间格式处理函数Date.prototype.format
2016/01/27 Javascript
JavaScript的Backbone.js框架入门学习指引
2016/05/07 Javascript
AngularJs Javascript MVC 框架
2016/06/20 Javascript
IOS中safari下的select下拉菜单文字过长不换行的解决方法
2016/09/26 Javascript
微信小程序 获取二维码实例详解
2017/06/23 Javascript
PHP 实现一种多文件上传的方法
2017/09/20 Javascript
Vue 中批量下载文件并打包的示例代码
2017/11/20 Javascript
Vue作用域插槽slot-scope实例代码
2018/09/05 Javascript
详解Vue源码学习之双向绑定
2019/04/10 Javascript
小程序实现搜索框功能
2020/03/26 Javascript
Vue + Scss 动态切换主题颜色实现换肤的示例代码
2020/04/27 Javascript
[46:55]Ti4 冒泡赛第二轮 LGD vs C9
2014/07/14 DOTA
在python下使用tensorflow判断是否存在文件夹的实例
2019/06/10 Python
Python3批量移动指定文件到指定文件夹方法示例
2019/09/02 Python
基于python3监控服务器状态进行邮件报警
2019/10/19 Python
如何基于python操作json文件获取内容
2019/12/24 Python
Windows 下更改 jupyterlab 默认启动位置的教程详解
2020/05/18 Python
python实现一个简单RPC框架的示例
2020/10/28 Python
html5清空画布方法(三种)
2017/10/16 HTML / CSS
美国帽子俱乐部商店:Hat Club
2019/07/05 全球购物
澳大利亚波希米亚风时尚品牌:Tree of Life
2019/09/15 全球购物
Ruby如何进行文件操作
2014/07/17 面试题
财务会计人员岗位职责
2013/11/30 职场文书
总经理助理岗位职责范本
2014/07/20 职场文书
创业计划书之校园跑腿公司
2019/09/24 职场文书