Redis源码解析(1)

来源:互联网 发布:golang占位符 编辑:程序博客网 时间:2024/06/06 02:18

在文章的开头我们把所有服务端文件列出来,并且标示出其作用:
adlist.c //
双向链表
ae.c //
事件驱动
ae_epoll.c //epoll
接口, linux
ae_kqueue.c //kqueue
接口, freebsd
ae_select.c //select
接口, windows
anet.c //
网络处理
aof.c //
处理AOF文件
config.c //
配置文件解析
db.c //DB
处理
dict.c //hash

intset.c //
转换为数字类型数据
multi.c //
事务,多条命令一起打包处理
networking.c //
读取、解析和处理客户端命令
object.c //
各种对像的创建与销毁,stringlistsetzsethash
rdb.c //redis
数据文件处理
redis.c //
程序主要文件
replication.c //
数据同步master-slave
sds.c //
字符串处理
sort.c //
用于listsetzset排序
t_hash.c //hash
类型处理
t_list.c //list
类型处理
t_set.c //set
类型处理
t_string.c //string
类型处理
t_zset.c //zset
类型处理
ziplist.c //
节省内存方式的list处理
zipmap.c //
节省内存方式的hash处理
zmalloc.c //
内存管理
上面基本是redis最主要的处理文件,部分没有列出来,如VM之类的,就不在这里讲了。
首先我们来回顾一下redis的一些基本知识:
1
redisNDB(默认为16DB),并且每个db有一个hash表负责存放key,同一个DB不能有相同的KEY,但是不同的DB可以相同的KEY;
2
、支持的几种数据类型:stringhashlistsetzset;
3
redis可以使用aof来保存写操作日志(也可以使用快照方式保存数据文件)

对于数据类型在这里简单的介绍一下(网上有图,下面我贴上图片可能更容易理解)
1
、对于一个string对像,直接存储内容;
2
、对于一个hash对像,当成员数量少于512的时候使用zipmap(一种很省内存的方式实现hash table),反之使用hash(key存储成员名,value存储成员数据);
3
、对于一个list对像,当成员数量少于512的时候使用ziplist(一种很省内存的方式实现list),反之使用双向链表(list);
4
、对于一个set对像,使用hash(key存储数据,内容为空)
5
、对于一个zset对像,使用跳表(skip list),关于跳表的相关内容可以查看本blog的跳表学习笔记;

下面正式进入源代码的分析
1
、首先是初始化配置,initServerConfig(redis.c:759)
void initServerConfig() {
server.port = REDIS_SERVERPORT;
server.bindaddr = NULL;
server.unixsocket = NULL;
server.ipfd = -1;
server.sofd = -1;
2.
在初始化配置中调用了populateCommandTable(redis.c:925)函数,该函数的目地是将命令集分布到一个hash table,大家可以看到每一个命令都对应一个处理函数,因为redis支持的命令集还是蛮多,所以如果要靠if分支来做命令处理的话即繁琐效率还底, 因此放到hash table中,在理想的情况下只需一次就能定位命令的处理函数。
void populateCommandTable(void) {
int j;
int numcommands = sizeof(readonlyCommandTable)/sizeof(struct redisCommand);

for (j = 0; j < numcommands; j++) {
struct redisCommand *c = readonlyCommandTable+j;
int retval;

retval = dictAdd(server.commands, sdsnew(c->name), c);
assert(retval == DICT_OK);
}
}

3、对参数的解析,redis-server有一个参数(可以不需要),这个参数是指定配置文件路径,然后由函数loadServerConfig(config.c:28)加载所有配置
if (argc == 2) {
if (strcmp(argv[1], “-v”) == 0 ||
strcmp(argv[1], “–version”) == 0) version();
if (strcmp(argv[1], “–help”) == 0) usage();
resetServerSaveParams();
loadServerConfig(argv[1]);

4、初始化服务器initServer(redis.c:836), 该函数初始化一些服务器信息,包括创建事件处理对像、dbsocket、客户端链表、公共字符串等。
void initServer() {
int j;

signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();//
设置信号处理

if (server.syslog_enabled) {
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
server.syslog_facility);
}
5
、在上面初始化服务器中有一段代码是创建事件驱动,aeCreateTimeEvent是创建一个定时器,下面创建的定时器将会每毫秒调用 serverCron函数,而aeCreateFileEvent是创建网络事件驱动,当server.ipfdserver.sofd有数据可读的情 况将会分别调用函数acceptTcpHandleracceptUnixHandler
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);
if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR) oom(“creating file event”);
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) oom(“creating file event”);

6、接下来就是初始化数据,如果开启了AOF,那么会调用loadAppendOnlyFile(aof.c:216)去加载AOF文件,在AOF 文件中存放了客户端的命令,函数将数据读取出来然后依次去调用命令集去处理,当AOF文件很大的时候势必为影响客户端的请求,所以每处理1000条命令就 会去尝试接受和处理客户端的请求,其代码在aof.c250但是如果没有开启AOF并且有rdb的情况,会调用rdbLoad(redis.c:873)尝试去加载rdb文件,理所当然的在加载rdb文件的内部也 会考虑文件太大而影响客户端请求,所以跟AOF一样,每处理1000条也会尝试去接受和处理客户端请求。

7、当所有初始化工作做完之后,服务端就开始正式工作了
aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);

8、大家都知道redis是单线程模式,所有的请求、处理都是在同一个线程里面进行,也就是一个无限循环,在这个无限循环的内部有两件事要做,第一 件就是调用通过aeSetBeforeSleepProc函数设置的回调函数,第二件就是开始接受客户端的请求和处理,所以我们可以在第7节看到设置了回 调函数为beforeSleep,但是这个beforeSleep到底有什么作用呢?我们在第9节再详细讲述。对于aeMain(ae.c:375)就是 整个程序的主要循环。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}
9
、在beforeSleep内部一共有三部分,第一部分对vm进行处理(即第一个if),这里我们略过;第二部分是释放客户端的阻塞操作,在 redis里有两个命令BLPOPBRPOP需要使用这些操作(弹出列表头或者尾,实现方式见t_list.c:862行的 blockingPopGenericCommand函数),当指定的key不存在或者列表为空的情况下,那么客户端会一直阻塞,直到列表有数据时,服务 端就会去执行lpop或者rpop并返回给客户端,那么什么时候需要用到BLPOPBRPOP呢?大家平时肯定用redis做过队列,最常见的处理方式 就是使用llen去判断队列有没有数据,如果有数据就去取N条,然后处理,如果没有就sleep(3),然后继续循环,其实这里就可以使用BLPOP或者 BRPOP来轻松实现,而且可以减少请求,具体怎么实现留给大家思考;第三部分就是flushAppendOnlyFile(aof.c:60),这个函 数主要目的是将aofbuf的数据写到文件,那aofbuf是什么呢?他是AOF的一个缓冲区,所以客户端的命令都会在处理完后把这些命令追加到这个缓冲 区中,然后待一轮数据处理完之后统一写到文件(所以aof也是不能100%保证数据不丢失的,因为如果当redis正在处理这些命令的情况下服务就挂掉, 那么这部分的数据是没有保存到硬盘的),大家都知道写数据到文件并不是立即写到硬盘,只是保存到一个文件缓冲区中,什么情况下会把缓冲区的数据转到硬盘 呢?只要满足如下三种条件的一种就能将数据真正存到硬盘:1、手动调用刷新缓冲区;2、缓冲区已满;3、程序正常退出。因此redis将数据写到文件缓冲 区之后会判断是否需要刷到硬盘,server.appendfsync有两种方式,第一种(APPENDFSYNC_ALWAYS):无条件刷新,即每次 写文件都会保存到硬盘,第二种(APPENDFSYNC_EVERYSEC):每隔一秒保存到硬盘。

10、接下来我们开始讲解aeProcessEvents(ae.c:275)的处理流程,首先我们来回顾一下第5节设置的定时器和监听 socket事件处理,其中socket事件处理会回调acceptTcpHandler(networking.c:410)和定时器回调函数 serverCron(redis.c:519),在aeProcessEvents的内部有两部分需要处理,第一部分是调用aeApiPoll判断 socket是否有数据可读,整个服务端的socket里面要分监听socket和客户端socket,当有客户端链接服务器时,会触发监听socket 的事件处理函数,也就是acceptTcpHandler,而acceptTcpHandler会去调用 createClient(networking.c:13)创建客户端对像,然后为这个客户端设置事件处理函数 readQueryFromClient(networking.c:827),所以当客户端有消息时就会触发客户端socket 事件处理函数,处理数据部分讲在后面详细讲解,接下来的第二部分就是定时器,每次在socket部分处理完后就用调用 processTimeEvents(ae.c:212)来处理定时器,那么内部实现也很简单,当设置定时器的时候就会计算好应该触发的时间,所以这里就 只需要判断当前时间是否大于或者等于应该触发的时间即可。那么这个定时器到底做了什么呢?请继续第11节。

11、我们继续跟踪源代码serverCron(redis.c:519),整个函数分为七个部分,第一部分:在服务端打印一些关于DB的信息(包 括key数量,内存使用量等);第二部分:判断DBhash table是否需要扩展大小tryResizeHashTables(redis.c:432);第三部分:关闭太长时间没有通信的链接closeTimedoutClients(networking.c:629);第四部分:保存rdb文件 rdbSaveBackground(rdb.c:507),当然也是在需要保存的情况才会保存,即设置save参数;第五部分:清除过期的key,当然 这里不是清除全部,他只是随机取出一些activeExpireCycle(redic.c:477);第六部分:虚拟内存交换部分,将一部分key转到 虚拟内存中,这里的key也是随机抽取的, vmSwapOneObjectBlocking(vm.c:521);第七部分:主从同 步,replicationCron(replication.c:500)

12、在第10节中我们讲到客户端socket处理函数readQueryFromClient,这里我们一层层分析,首先是从客户端读取数据,然 后调用processInputBuffer,在内部先是判断类型,然后调用processInlineBuffer或者 processMultibulkBuffer解析参数,解析后的参数由argv存储参数,其类型是一个指向指针的指针,其中argv[0]是命令名称, 后面就是命令参数,argc存储参数数量;然后调用processCommand(redis.c:979)处理命令,在内部调用 lookupCommand(redis.c:940)获取命令对应的函数,然后调用freeMemoryIfNeeded(redis.c:1385) 判断是否需要释放一些内存,接下来就是调用call(redis.c:954)去执行命令,执行命令后会调用 feedAppendOnlyFile(aof.c:137)把命令行保存到aofbuf中,然后判断是否需要同步数据到slave,如果需要则调用 replicationFeedSlaves(replication.c:10),接下来就是判断是否需要将数据发送到监控端,如果需要则调用replicationFeedMonitors(replication.c:82),到这里整个服务流程就结束了。至于每条命令如何执行,大家可以去 查看以t_开头的几个文件。下面是一张整个服务的流程图。

注:redis源代码为2.2.8,希望大家看文章的时候配合源代码一起看,更容易理解


转自:http://blog.chinaunix.net/uid-790245-id-3766842.html

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 学员练车撞死人怎么办 三胎意外来了该怎么办 因为飞机延误耽误行程怎么办 伤了朋友的心怎么办 羊蹄被绳子缠肿了怎么办 新老师教的不好怎么办 跟老公三观不合怎么办 突然有社保补扣怎么办 街头篮球篮板反应慢怎么办 换水了龙鱼顶缸怎么办 压着眼睛睡觉醒来模糊怎么办 天热眼睛有眼屎怎么办 眼睛里膜起来了怎么办 眼睛一边大一边小怎么办 眼白膜鼓起来了怎么办 主持问答环节没人提问怎么办 转学原学校不给怎么办 村长借东西不还怎么办 村长不上报建房申请怎么办 村长不上报建房手续申请怎么办 村长不给村民盖章怎么办? 找村干部办事难怎么办 洪洞县村长不给我盖章怎么办 睾丸穿刺取精只配到6个胚胎怎么办 孩子一只耳朵听不到声音怎么办 孕早期孕囊生长慢怎么办 试管2次不着床怎么办? pescm球员年龄大了怎么办 实况足球俱乐部经理球员老了怎么办 你不是我的菜怎么办 苹果平板电脑耳机有回音怎么办 obs直播有电流音怎么办 语音里网吧很吵怎么办 电脑k歌有延迟怎么办 想开个跆拳道馆怎么办营业执照? 壶嘴拐弯处漏水怎么办 裂纹茶壶嘴坏了怎么办 小孩刚上学怕她上火怎么办 在幼儿园小朋友不愿叠衣服怎么办 孩子在家听话幼儿园不听话怎么办 变魔术时观众说看过这个怎么办