背景

redis是当下比较流行的KV数据库之一,是抵御高并发的一把利器,本着知其然还要知其所以然的目的,我决定花一点时间来研究其源码,希望最后能向自己解释清楚“redis为什么这么快”这个疑惑,第一篇主要介绍环境搭建和redis工作流程初探,后期会陆续献上其他有意思的章节。

环境准备

我自己的电脑是win10系统,所以我会准备一套适合windows系统的环境来供自己学习,这样方便调试分析。

下载redis源码

redis本身是不支持windows系统的,但是微软的工程师针对windows平台做了支持,源码放在了github上,有需要的可以自己去下载,我这里下载的是v2.8.9这个tag的源码,下载地址https://github.com/microsoftarchive/redis。这里扯个题外话,学习一个开源软件的时候不要一上来就下载最新版本的源码看,经过的迭代太多代码量就上来了,对于新手来说容易晕,先下一个早期的稳定版本了解其体系结构和工作流程,等待熟悉了以后再循序渐进。

下载Visual Studio

其他软件我没尝试过,这个是官方推荐的ide,一定一定一定下载 Visual Studio 2013 update5这个版本的,否则编译的时候各种报错,下载地址

http://download.microsoft.com/download/9/3/E/93EA27FF-DB02-4822-8771-DCA0238957E9/vs2013.5_ult_chs.iso。这里再扯个题外话,我刚开始下载的是最新版本的Visual Studio,结果编译的时候各种报错,然后就去网络上一顿查一顿试,折腾半天还是没好,最后下载了 Visual Studio 2013 update5这个版本,结果一把就成功,有些牛角尖一定得钻,但是有些牛角尖不值得钻。

Visual Studio打开redis源码

按照下图方式打开下载的redis源码





c程序的入口是main方法,redis main方法的位置在redis.c文件中,下面我们通过main方法来逐步了解redis的工作流程。

启动过程分析

跟着main方法顺序看下去,大概有以下几个关键步骤(略过了sentinel相关逻辑):

1.设置随机数种子、获取当前时间等;

2.初始化服务配置信息,设置默认值(initServerConfig);

3.解析配置文件(loadServerConfig);

4.初始化server对象(initServer);

 4.1创建eventLoop对象;

 4.2创建serverSocket,监听端口;

 4.3添加定时事件到eventLoop对象中;

 4.4将serverSocket文件描述符添加到监视集中,这里借助IO多路复用框架的能力(windows平台使用IOCP,其他平台使用select、epoll、evport等);

5.从磁盘加载数据到内存中(loadDataFromDisk);

6.执行事件循环逻辑(aeMain),这是redis真正挥洒汗水的地方,下一节会单独讲述这块内容。

调用关系图

事件循环分析

我们都知道redis是单线程执行客户端命令的,那究竟是怎样一种设计才能支持高并发的读写呢。

工作模型

1.server启动,创建serverSocket监听端口,将serverSocket对应的FD(文件描述符)简称为FD-Server添加到IO多路复用框架的监视集当中,注册AE_READABLE事件(可读),关联的事件处理器是acceptTcpHandler;

2.client连接server;

3.事件循环开始轮询IO多路复用框架接口aeApiPoll,会得到就绪的FD,执行对应的事件处理器;

4.由第3步事件循环触发FD-Server AE_READABLE事件对应的事件处理器acceptTcpHandler;

 4.1调用accept获得clientSocket对应的FD简称为FD-Client;

 4.2将FD-Client添加到IO多路复用框架的监视集当中,注册AE_READABLE事件(可读),关联的事件处理器是readQueryFromClient;

5.client发送redis命令;

6.由第3步事件循环触发FD-Clien AE_READABLE事件对应的事件处理器readQueryFromClient;

 6.1解析客户端发来的redis命令,找到命令对应的redisCommandProc(命令对应的处理函数);

 6.2执行redisCommandProc;

 6.3prepareClientToWrite准备回写响应信息,为FD-Client注册AE_WRITEABLE事件(可写),关联的事件处理器是sendReplyToClient;

7.执行redis中的定时任务;

8.由第3步事件循环触发FD-Clien AE_WRITEABLE事件对应的事件处理器sendReplyToClient,发送响应内容给client;

代码分析

server启动,创建serverSocket并注册AE_READABLE事件,设置事件处理器为acceptTcpHandler

void initServer() {
//省略部分代码 //初始化eventLoop对象,eventLoop对象里面存储了所有的事件
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR); //创建serverSocket,监听端口
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)
exit(1); //添加定时任务到eventLoop中
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
} //将serverSocket对应的文件描述符添加到监视集中,关联的事件处理器是acceptTcpHandler
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR) }
}

acceptTcpHandler当有连接过来的时候被触发,调用accept得到client socket对应的FD,并将FD添加到监视集中,关联的事件处理器是readQueryFromClient

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
//调用accept获得clientSocket对应的FD
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport); //将clientSocket对应的FD添加到监视集中
acceptCommonHandler(cfd,0);
} static void acceptCommonHandler(int fd, int flags) {
redisClient *c; //调用createClient添加
if ((c = createClient(fd)) == NULL) {
}
} redisClient *createClient(int fd) {
redisClient *c = zmalloc(sizeof(redisClient)); if (fd != -1) {
anetNonBlock(NULL,fd);
anetEnableTcpNoDelay(NULL,fd);
if (server.tcpkeepalive)
anetKeepAlive(NULL,fd,server.tcpkeepalive); //将fd添加到监视集中,关联的事件处理器是readQueryFromClient
if (aeCreateFileEvent(server.el,fd,AE_READABLE,
readQueryFromClient, c) == AE_ERR)
{
}
}
}

aeMain就是跑一个循环,一直去调用aeProcessEvents

void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
}

aeProcessEvents会调用aeApiPoll方法来获得就绪的文件描述符,然后执行文件描述符关联的的事件处理器

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents; #ifdef _WIN32
if (ServiceStopIssued() == TRUE)
aeStop(eventLoop);
#endif /* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; /* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp; if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms; /* Calculate the time missing for the nearest
* timer to fire. */
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
if (tvp->tv_sec < 0) tvp->tv_sec = 0;
if (tvp->tv_usec < 0) tvp->tv_usec = 0;
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
} numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
aeFileEvent *fe;
int mask = eventLoop->fired[j].mask;
int fd = eventLoop->fired[j].fd;
int rfired = 0; fe = &eventLoop->events[eventLoop->fired[j].fd]; /* note the fe->mask & mask & ... code: maybe an already processed
* event removed an element that fired and we still didn't
* processed, so we check if the event is still valid. */
if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
processed++;
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);//处理延迟任务 return processed; /* return the number of processed file/time events */
}

动画演示

做了一个动画帮助理解工作过程(redis启动之后使用命令行telnet到6379端口,然后执行keys *命令,最终拿到结果)

网络模块

IO多路复用

这部分内容网络上精彩的内容太多,这里把我认为比较经典的一些内容贴出来供大家品读(建议从上往下顺序阅读)

The C10K problem

socket阻塞非阻塞等头疼问题解释

LINUX – IO MULTIPLEXING – SELECT VS POLL VS EPOLL

poll vs select vs event-based

redis事件驱动

redis源码学习之工作流程初探的更多相关文章

  1. redis源码学习之slowlog

    目录 背景 环境说明 redis执行命令流程 记录slowlog源码分析 制造一条slowlog slowlog分析 1.slowlog如何开启 2.slowlog数量限制 3.slowlog中的耗时 ...

  2. Redis源码学习:字符串

    Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...

  3. Redis源码学习:Lua脚本

    Redis源码学习:Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开 ...

  4. openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

    这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...

  5. 柔性数组(Redis源码学习)

    柔性数组(Redis源码学习) 1. 问题背景 在阅读Redis源码中的字符串有如下结构,在sizeof(struct sdshdr)得到结果为8,在后续内存申请和计算中也用到.其实在工作中有遇到过这 ...

  6. nodejs的Express框架源码分析、工作流程分析

    nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...

  7. __sync_fetch_and_add函数(Redis源码学习)

    __sync_fetch_and_add函数(Redis源码学习) 在学习redis-3.0源码中的sds文件时,看到里面有如下的C代码,之前从未接触过,所以为了全面学习redis源码,追根溯源,学习 ...

  8. redis源码学习之lua执行原理

    聊聊redis执行lua原理 从一次面试场景说起   "看你简历上写的精通redis" "额,还可以啦" "那你说说redis执行lua脚本的原理&q ...

  9. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

随机推荐

  1. vue2.0+Element UI 实现动态表单(点击按钮增删一排输入框)

    对于动态增减表单项,Element UI 官方文档表单那一节已经介绍得很清楚了,我之前没有看见,绕了很多弯路,这里针对点击按钮增删一排输入框的问题做一个总结. 效果图如下 存在一排必填的姓名与手机号, ...

  2. <VCC笔记> 关于Assertion

    这篇博客开始介绍VCC的用法,先用简单的例子介绍VCC的基本语法,当然面对更复杂的程序时,VCC也是将他简化然后分析的. 1.Assertion #include <vcc.h> int ...

  3. 自动完成 APP【字典树(Trie树)+dfs】

    自动完成 APP 传送门  来源:upc12786 题目描述 奶牛 Bessie 很喜欢用手机上网聊天,但她的蹄子太大,经常会按到好几个键造成不必要的麻烦(丢死人了,你下辈子还是不要当奶牛了).于是 ...

  4. (一)Log4j使用

    原文链接:https://www.jianshu.com/p/eb4ac2571c94?tdsourcetag=s_pctim_aiomsg 1.先创建个maven项目,在我们项目的pom文件中导入l ...

  5. mysql索引小总结

    MySql 1.索引 mysql索引默认使用的是B+Tree(B-树的变种版).也可以使用HASH表. 二叉树: 二叉树又称二叉搜索树,二叉排序树,特点如下: 左子树上所有结点值均小于根结点 右子树上 ...

  6. 精美图文讲解Java AQS 共享式获取同步状态以及Semaphore的应用

    | 好看请赞,养成习惯 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it ...

  7. Windows程序设计(2) - API-02 文件系统

    一.磁盘分区的基本概念 1.磁盘分区(Patitions): 分区就是物理存储设备分割成多个不同的逻辑上的存储设备.分区从实质上说就是对硬盘的一种格式化.当我们创建分区时,就已经设置好了硬盘的各项物理 ...

  8. 使用vscode 开发go项目的最新姿势. go版本1.14.2

    使用了go 1.14.2. 版本, 再也不用建src, pkg, bin 目录了,   以及再也不用强制配置GOPATH了 前提条件: 必须是 go mod 项目. 在工程目录下, 执行这样的命令生成 ...

  9. Mysql表结构转成Oracle

    Navicat数据库连接工具

  10. C#数据结构与算法系列(九):栈实现综合计算器(中缀表达式)

    1.问题介绍 2.实现思路 3.代码实现 第一个版本(采用这个) public class ArrayStack { private int _maxSize; private int[] _arr; ...