扣丁学堂Java开发培训之Redis命令执行过程详解

2018-09-14 10:21:27 386浏览

今天扣丁学堂Java培训老师给大家介绍一下关于Redis学习教程之命令执行过程的详细介绍,首先Redis是单线程应用,它是如何与多个客户端简历网络链接并处理命令的?由于Redis是基于I/O多路复用技术,为了能够处理多个客户端的请求,Redis在本地为每一个链接到Redis服务器的客户端创建了一个redisClient的数据结构,这个数据结构包含了每个客户端各自的状态和执行的命令。Redis服务器使用一个链表来维护多个redisClient数据结构。



在服务器端用一个链表来管理所有的redisClient。

struct redisServer {
 //...
 list *clients;  /* List of active clients */
 //...
}

所以我就看看redisClient包含的数据结构和重要参数:

typedef struct redisClient {
 
 // 客户端状态标志
 int flags;  /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */
  
 // 套接字描述符
 int fd;
 
 // 当前正在使用的数据库
 redisDb *db;
 
 // 当前正在使用的数据库的 id (号码)
 int dictid;
 
 // 客户端的名字
 robj *name;  /* As set by CLIENT SETNAME */
 
 // 查询缓冲区
 sds querybuf;
 
 // 查询缓冲区长度峰值
 size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */
 
 // 参数数量
 int argc;
 
 // 参数对象数组
 robj **argv;
 
 // 记录被客户端执行的命令
 struct redisCommand *cmd, *lastcmd;
 
 // 请求的类型:内联命令还是多条命令
 int reqtype;
 
 // 剩余未读取的命令内容数量
 int multibulklen; /* number of multi bulk arguments left to read */
 
 // 命令内容的长度
 long bulklen;  /* length of bulk argument in multi bulk request */
 
 // 回复链表
 list *reply;
 
 // 回复链表中对象的总大小
 unsigned long reply_bytes; /* Tot bytes of objects in reply list */
 
 // 已发送字节,处理 short write 用
 int sentlen;  /* Amount of bytes already sent in the current
    buffer or object being sent. */
 
 // 回复偏移量
 int bufpos;
 // 回复缓冲区
 char buf[REDIS_REPLY_CHUNK_BYTES];
 // ...
}

这里需要特别的注意,redisClient并非指远程的客户端,而是一个Redis服务本地的数据结构,我们可以理解这个redisClient是远程客户端的一个映射或者代理。

flags

flags表示了目前客户端的角色,以及目前所处的状态。他比较特殊可以单独表示一个状态或者多个状态。

querybuf

querybuf是一个sds动态字符串类型,所谓buf说明是它只是一个缓冲区,用于存储没有被解析的命令。

argc&argv

上文的querybuf是一个没有处理过的命令,当Redis将querybuf命令解析以后,会将得出的参数个数和以及参数分别保存在argc和argv中。argv是一个redisObject的数组。

cmd

Redis使用一个字典保存了所有的redisCommand。key是redisCommand的名字,值就是一个redisCommand结构,这个结构保存了命令的实现函数,命令的标志,命令应该给定的参数个数,命令的执行次数和总消耗时长等统计信息,cmd是一个redisCommand。

当Redis解析出argv和argc后,会根据数组argv[0],到字典中查询出对应的redisCommand。上文的例子中Redis就会去字典去查找SET这个命令对应的redisCommand。redis会执行redisCommand中命令的实现函数。

buf&bufpos&reply

buf是一个长度为REDIS_REPLY_CHUNK_BYTES的数组。Redis执行相应的操作以后,就会将需要返回的返回的数据存储到buf中,bufpos用于记录buf中已用的字节数数量,当需要恢复的数据大于REDIS_REPLY_CHUNK_BYTES时,redis就会是用reply这个链表来保存数据。

其他参数

其他参数大家看注释就能明白,就是字面的意思。省略的参数基本上涉及Redis集群管理的参数,在之后的文章中会继续讲解。

客户端的链接和断开

上文说过redisServer是用一个链表来维护所有的redisClient状态,每当有一个客户端发起链接以后,就会在Redis中生成一个对应的redisClient数据结构,增加到clients这个链表之后。

一个客户端很可能被多种原因断开。

总体分为几种类型:

客户端主动退出或者被kill。

timeout超时。

Redis为了自我保护,会断开发的数据超过限制大小的客户端。

Redis为了自我保护,会断需要返回的数据超过限制大小的客户端。

调用总结

当客户端和服务器端的嵌套字变得可读的时候,服务器将会调用命令请求处理器来执行以下操作:

读取嵌套字中的数据,写入querybuf。

解析querybuf中的命令,记录到argc和argv中。

根据argv[0]查找对应的recommand。

执行recommand对应的实现函数。

执行以后将结果存入buf&bufpos&reply中,返回给调用方。

RedisServer(服务端)

上文是从redisClient的角度来观察命令的执行,文章接下来的部分将会从Redis的代码层面,微观的观察Redis是怎么实现命令的执行的。

redisServer的启动

在了解redisServer的工作机制的工作机制之前,需要了解redisServer的启动做了什么:

可以继续观察Redis的main()函数。

int main(int argc, char **argv) {
 //...
 // 创建并初始化服务器数据结构
 initServer();
 //...
}

我们只关注initServer()这个函数,他负责初始化服务器的数据结构。继续跟踪代码:

void initServer() {
 //...
 //创建eventLoop
 server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
 /* Create an event handler for accepting new connections in TCP and Unix
 * domain sockets. */
 // 为 TCP 连接关联连接应答(accept)处理器
 // 用于接受并应答客户端的 connect() 调用
 for (j = 0; j < server.ipfd_count; j++) {
 if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
  acceptTcpHandler,NULL) == AE_ERR)
  {
  redisPanic(
   "Unrecoverable error creating server.ipfd file event.");
  }
 }
 
 // 为本地套接字关联应答处理器
 if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
 acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");
 //...
}

在这段代码里面:

初始化了事件处理器的eventLoop

向eventLoop中注册了两个事件处理器acceptTcpHandler和acceptUnixHandler,分别处理远程的链接和本地链接。

redisClient的创建

当有一个远程客户端连接到Redis的服务器,会触发acceptTcpHandler事件处理器.

acceptTcpHandler事件处理器,会创建一个链接。然后继续调用acceptCommonHandler。

acceptCommonHandler事件处理器的作用是:

调用createClient()方法创建redisClient

检查已经创建的redisClient是否超过server允许的数量的上限

如果超过上限就拒绝远程连接

否则创建redisClient创建成功

并更新连接的统计次数,更新redisClinet的flags字段

这个时候Redis在服务端创建了redisClient数据结构,这个时候远程的客户端就在redisServer中创建了一个代理。远程的客户端就与Redis服务器建立了联系,就可以向服务器发送命令了。

处理命令

在createClient()行数中:

// 绑定读事件到事件 loop (开始接收命令请求)
if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR)

向eventLoop中注册了readQueryFromClient。readQueryFromClient的作用就是从client中读取客户端的查询缓冲区内容。

然后调用函数processInputBuffer来处理客户端的请求。在processInputBuffer中有几个核心函数:

processInlineBuffer和processMultibulkBuffer解析querybuf中的命令,记录到argc和argv中。

processCommand根据argv[0]查找对应的recommen,执行recommend对应的执行函数。在执行之前还会验证命令的正确性。将结果存入buf&bufpos&reply中

返回数据

万事具备了,执行完了命令就需要把数据返回给远程的调用方。调用链如下

processCommand->addReply->prepareClientToWrite

在prepareClientToWrite中我们有见到了熟悉的代码:

aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;

向eventloop绑定了sendReplyToClient事件处理器。

在sendReplyToClient中观察代码发现,如果bufpos大于0,将会把buf发送给远程的客户端,如果链表reply的长度大于0,就会将遍历链表reply,发送给远程的客户端,这里需要注意的是,为了避免reply数据量过大,就会过度的占用资源引起Redis相应慢。为了解决这个问题,当写入的总数量大于REDIS_MAX_WRITE_PER_EVENT时,Redis将会临时中断写入,记录操作的进度,将处理时间让给其他操作,剩余的内容等下次继续。这样的套路我们一路走来看过太多了。

远程客户端连接到redis后,redis服务端会为远程客户端创建一个redisClient作为代理。redis会读取嵌套字中的数据,写入querybuf中。解析querybuf中的命令,记录到argc和argv中。根据argv[0]查找对应的recommand。执行recommend对应的执行函数。执行以后将结果存入buf&bufpos&reply中。返回给调用方。返回数据的时候,会控制写入数据量的大小,如果过大会分成若干次。保证redis的相应时间。

Redis作为单线程应用,一直贯彻的思想就是,每个步骤的执行都有一个上限(包括执行时间的上限或者文件尺寸的上限)一旦达到上限,就会记录下当前的执行进度,下次再执行。保证了Redis能够及时响应不发生阻塞。

以上就是关于Redis学习教程之命令执行过程的详细介绍,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,扣丁学堂不仅有专业的Java培训班供大家学习,还有与时俱进的课程体系和大量的Java视频教程让学员免费观看学习,想要学好Java开发的小伙伴快到扣丁学堂来了解详情吧。扣丁学堂Java技术交流群:670348138。

扣丁学堂微信公众号

【关注微信公众号获取更多学习资料】



查看更多关于“Java开发资讯”的相关文章>>

标签: Java培训 Java视频教程 Java多线程 Java面试题 Java学习视频 Java开发

热门专区

暂无热门资讯

课程推荐

微信
微博
15311698296

全国免费咨询热线

邮箱:codingke@1000phone.com

官方群:148715490

北京千锋互联科技有限公司版权所有   北京市海淀区宝盛北里西区28号中关村智诚科创大厦4层
京ICP备12003911号-6   Copyright © 2013 - 2019

京公网安备 11010802030908号