Redis源码简要分析
转载请注明来源:https://www.cnblogs.com/hookjc/
把所有服务端文件列出来,并且标示出其作用:
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 //各种对像的创建与销毁,string、list、set、zset、hash
rdb.c //redis数据文件处理
redis.c //程序主要文件
replication.c //数据同步master-slave
sds.c //字符串处理
sort.c //用于list、set、zset排序
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、redis有N个DB(默认为16个DB),并且每个db有一个hash表负责存放key,同一个DB不能有相同的KEY,但是不同的DB可以相同的KEY;
2、支持的几种数据类型:string、hash、list、set、zset;
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), 该函数初始化一些服务器信息,包括创建事件处理对像、db、socket、客户端链表、公共字符串等。
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.ipfd和server.sofd有数据可读的情 况将会分别调用函数acceptTcpHandler和acceptUnixHandler。
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.c第250行; 但是如果没有开启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里有两个命令BLPOP和BRPOP需要使用这些操作(弹出列表头或者尾,实现方式见t_list.c:862行的 blockingPopGenericCommand函数),当指定的key不存在或者列表为空的情况下,那么客户端会一直阻塞,直到列表有数据时,服务 端就会去执行lpop或者rpop并返回给客户端,那么什么时候需要用到BLPOP和BRPOP呢?大家平时肯定用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数量,内存使用量等);第二部分:判断DB的hash 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,希望大家看文章的时候配合源代码一起看,更容易理解
来源:python脚本自动迁移
Redis源码简要分析的更多相关文章
- Activity源码简要分析总结
Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...
- [Java] LinkedHashMap 源码简要分析
特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...
- [Java] HashMap 源码简要分析
特性 * 允许null作为key/value. * 不保证按照插入的顺序输出.使用hash构造的映射一般来讲是无序的. * 非线程安全. * 内部原理与Hashtable类似. 源码简要分析 pu ...
- [Java] Hashtable 源码简要分析
Hashtable /HashMap / LinkedHashMap 概述 * Hashtable比较早,是线程安全的哈希映射表.内部采用Entry[]数组,每个Entry均可作为链表的头,用来解决冲 ...
- RxJava && Agera 从源码简要分析基本调用流程(2)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...
- RxJava && Agera 从源码简要分析基本调用流程(1)
版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/123 来源:腾云阁 https://www.qclo ...
- Elasticsearch之client源码简要分析
问题 让我们带着问题去学习,效率会更高 1 es集群只配置一个节点,client是否能够自动发现集群中的所有节点?是如何发现的? 2 es client如何做到负载均衡? 3 一个es node ...
- spring mvc 源码简要分析
关于web项目,运用比较多的是过滤器和拦截器 过滤器基于责任链设计模式 创建过滤器链 / Create the filter chain for this requestApplicationFilt ...
- Redis源码分析:serverCron - redis源码笔记
[redis源码分析]http://blog.csdn.net/column/details/redis-source.html Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...
随机推荐
- element菜单刷新后定位问题?
之前这样写不行 <el-menu mode="vertical" theme="dark" ref="navbar" :default ...
- Windows下SQL Server数据库的安装
由于工作需要出一版教程,记录一下安装过程,用到的数据库是SQL Server2014版本 下载地址为:SQL Server2014下载 提取码:6666 下载好安装包,首先右键,装载 装载完, 会有这 ...
- 智慧巨鹿使用Rainbond落地实践,一个平台管理所有应用系统
背景 大家好,我是北京数立通科技有限公司的李栋.最近几年,我一直负责"智慧巨鹿"这一智慧城市项目的运行与维护工作.这个项目涉及到10多家供应商开发的 30 多套智慧城市应用的运维管 ...
- nginx之location、inmp架构详解、BBS项目部署
本期内容概要 location lnmp架构 部署BBS项目 内容详细 1.location 使用Nginx Location可以控制访问网站的路径 但一个server可以有多个location配置 ...
- Rainbond 对接 Istio 原理讲解和代码实现分析
一.背景 现有的 ServiceMesh 框架有很多,如 Istio.linkerd等.对于用户而言,在测试环境下,需要达到的效果是快.开箱即用.但在生产环境下,可能又有熔断.延时注入等需求.那么单一 ...
- Dom 键盘事件以及实战案例
键盘事件 //键盘操作 //1.某键盘按下执行的操作 document是对文档进行触发 document.onkeyup = function(){ console.log('你好') } docum ...
- Selenium_环境安装(1)
Selenium是一个用于Web应用程序自动化测试工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样. Selenium基本上支持主流的浏览器,包括IE,Mozilla Firef ...
- Python2 和 Python3 共存于 Centos7
一.解决Python2 pip问题 centos7自带的是Python2,但是并没有安装pip,我们需要自行安装 包名为 python-pip # yum install epel-release - ...
- spring security 动态 修改当前登录用户的 权限
1.前言 spring security 可以获取当前登录的用户信息,同时提供了接口 来修改权限列表信息 , 使用这个方法 ,可以动态的修改当前登录用户权限. 那么问题来了... 如果我是管理员 ,如 ...
- JQuery iframe 刷新效果
假如有一个选项卡.tab-content,里面有多个iframe 只刷新显示的那个iframe,所以要用到:visible $('.tab-content iframe:visible')[0].co ...