1. redis 由 server.c 的main函数启动

int main(int argc, char **argv) {
...
// 上面的部分为读取配置和启动命令参数解析,看到这一行下面为启动流程
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
...
// 这里对服务进行初始化操作
initServer();
...
aeMain(server.el);
}

main 函数中最重要的几步

1.1 第一步就是执行 initServer

void initServer(void) {
...
// 创建eventLoop, 这里是redis非常关键的一个结构体,在这里监听所有的事件
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
...
/* 这里监听TCP端口,拿到监听的文件描述符ipfd, 监听客户端发过来的命令 */
if (server.port != 0 &&
listenToPort(server.port,&server.ipfd) == C_ERR) {
/* Note: the following log text is matched by the test suite. */
serverLog(LL_WARNING, "Failed listening on port %u (TCP), aborting.", server.port);
exit(1);
}
...
// 创建定时执行的后台操作事件,定时调用serverCron 函数,
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
...
// 绑定ipfd的accept事件的执行函数 acceptTcpHandler
// 实际是监听ipfd的可读事件,一旦可读,证明有连接进来了,立刻ceptTcpHandler
if (createSocketAcceptHandler(&server.ipfd, acceptTcpHandler) != C_OK) {
serverPanic("Unrecoverable error creating TCP socket accept handler.");
}
...
//注册eventLoop循环的执行前和执行后操作函数,类似于spring的环绕增强
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep); }
initServer 中最重要的两步
1. 创建了eventLoop,这是redis的核心
2. 监听了TCP端口,为后续网络操作的入口(select epoll等)
3. 绑定了TCP事件的handler函数

1.2 初始化完server后,执行aeMain, ae.c是redis的核心,是一个事件驱动开发的库

void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}

这里相当于是一个死循环,不停得监听事件并调用绑定的handler函数

int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
...
if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
// 执行调用前函数
if (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)
eventLoop->beforesleep(eventLoop); /* Call the multiplexing API, will return only on timeout or when
* some event fires. */
// 这里根据不同的操作系统执行不同的系统调用,总体就是找到可读或可写的fd
numevents = aeApiPoll(eventLoop, tvp); /* 执行调用后函数. */
if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)
eventLoop->aftersleep(eventLoop); for (j = 0; j < numevents; j++) {
int fd = eventLoop->fired[j].fd;
aeFileEvent *fe = &eventLoop->events[fd];
int mask = eventLoop->fired[j].mask;
int fired = 0; /* Number of events fired for current fd. */
... // aeApiPoll 中已标记好事件的类型,这里通过mask来找到可读事件
if (!invert && fe->mask & mask & AE_READABLE) {
// 执行绑定的可读事件的 handler函数
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
fe = &eventLoop->events[fd]; /* Refresh in case of resize. */
} // aeApiPoll 中已标记好事件的类型,这里通过mask来找到可写事件
if (fe->mask & mask & AE_WRITABLE) {
if (!fired || fe->wfileProc != fe->rfileProc) {
// 执行绑定的可写事件的 handler函数
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
fired++;
}
}
...
processed++;
}
} }

1.2 分析ae_epoll.c,这里通过系统调用epoll,找到可读可写的fd

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata;
int retval, numevents = 0; // epfd = epoll fd, 需要监听的fd都会通过epoll_ctl 提前挂到 epfd上
// 这里等待查看可用的fd
retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
tvp ? (tvp->tv_sec*1000 + (tvp->tv_usec + 999)/1000) : -1);
if (retval > 0) {
int j; numevents = retval;
// 遍历可用的fd,并通过el的filed数组进行标记,为后续执行做准备,
// 这里仅是标记
for (j = 0; j < numevents; j++) {
int mask = 0;
struct epoll_event *e = state->events+j; if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
}
} else if (retval == -1 && errno != EINTR) {
panic("aeApiPoll: epoll_wait, %s", strerror(errno));
} return numevents;
}

1.3 由于之前绑定了acceptTcpHandler, 当ipfd 可读时会调用acceptTcpHandler

acceptTcpHandler 在 networking.c中

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
char cip[NET_IP_STR_LEN];
UNUSED(el);
UNUSED(mask);
UNUSED(privdata); while(max--) {
// 获取accept client对应的fd,这里由于通过epoll,避免了accept的长期阻塞
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
if (cfd == ANET_ERR) {
if (errno != EWOULDBLOCK)
serverLog(LL_WARNING,
"Accepting client connection: %s", server.neterr);
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
// 1. 根据client fd 创建了socket
// 2. 执行了acceptCommonHandler
acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
}
}

1.3.1 创建socket

connection.c

typedef struct ConnectionType {
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
int (*write)(struct connection *conn, const void *data, size_t data_len);
int (*writev)(struct connection *conn, const struct iovec *iov, int iovcnt);
int (*read)(struct connection *conn, void *buf, size_t buf_len);
void (*close)(struct connection *conn);
int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
const char *(*get_last_error)(struct connection *conn);
int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
int (*get_type)(struct connection *conn);
} ConnectionType; ConnectionType CT_Socket = {
.ae_handler = connSocketEventHandler,
.close = connSocketClose,
.write = connSocketWrite,
.writev = connSocketWritev,
.read = connSocketRead,
.accept = connSocketAccept,
.connect = connSocketConnect,
.set_write_handler = connSocketSetWriteHandler,
.set_read_handler = connSocketSetReadHandler,
.get_last_error = connSocketGetLastError,
.blocking_connect = connSocketBlockingConnect,
.sync_write = connSocketSyncWrite,
.sync_read = connSocketSyncRead,
.sync_readline = connSocketSyncReadLine,
.get_type = connSocketGetType
}; connection *connCreateSocket() {
connection *conn = zcalloc(sizeof(connection));
conn->type = &CT_Socket;
conn->fd = -1; return conn;
} // 该函数创建了一个connection 对象,绑定了fd
connection *connCreateAcceptedSocket(int fd) {
connection *conn = connCreateSocket();
conn->fd = fd;
conn->state = CONN_STATE_ACCEPTING;
return conn;
}

1.3.2 执行 acceptCommonHandler

static void acceptCommonHandler(connection *conn, int flags, char *ip) {
...
// 最关键的一步,创建client
if ((c = createClient(conn)) == NULL) {
serverLog(LL_WARNING,
"Error registering fd event for the new client: %s (conn: %s)",
connGetLastError(conn),
connGetInfo(conn, conninfo, sizeof(conninfo)));
connClose(conn); /* May be already closed, just ignore errors */
return;
} ...
}

1.3.3 创建出client,与客户端一一对应

client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client)); /* passing NULL as conn it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
if (conn) {
connEnableTcpNoDelay(conn);
if (server.tcpkeepalive)
connKeepAlive(conn,server.tcpkeepalive);
// 关键一步,在这里绑定读取事件 readQueryFromClient
connSetReadHandler(conn, readQueryFromClient);
// 将connection 与 client 绑定
connSetPrivateData(conn, c);
}
... }

1.3.4 读事件绑定,这里是client 发命令到 server 读取的入口

connection.h

/* Register a read handler, to be called when the connection is readable.
* If NULL, the existing handler is removed.
*/
static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
return conn->type->set_read_handler(conn, func);
}
由connection 构造时的逻辑可知, 这里的type 是 CT_Socket,那么 set_read_handler 是 connSocketSetReadHandler
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
if (func == conn->read_handler) return C_OK; conn->read_handler = func;
if (!conn->read_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
else
// 这里注册了一个可读的事件,用来回调read_handler,即 readQueryFromClient
// 这里的fd 是accept 得到的 client fd
if (aeCreateFileEvent(server.el,conn->fd,
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
到这里注册客户端发送来命令的可读事件就注册完成了,启动逻辑就完成了

到这里还是在一条线程中执行的所有指令,那么 redis 是否就是一个线程搞完所有事情呢

其实不是,redis 有两种模式,一种是单线程模式,一种是多线程模式

将在后续的文章中继续分析

redis7源码分析:redis 启动流程的更多相关文章

  1. Elasticsearch源码分析(一)启动流程 ModuleBuilder injector

    http://blog.csdn.net/u010994304/article/details/50452890 es启动脚本是bin目录下的elasticsearch. 脚本内容不再赘述,java主 ...

  2. SpringMVC源码分析和启动流程

    https://yq.aliyun.com/articles/707995 在Spring的web容器启动时会去读取web.xml文件,相关启动顺序为:<context-param> -- ...

  3. Appium Android Bootstrap源码分析之启动运行

    通过前面的两篇文章<Appium Android Bootstrap源码分析之控件AndroidElement>和<Appium Android Bootstrap源码分析之命令解析 ...

  4. Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(6)Image内核启动(do_basic_setup函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://bl ...

  5. u-boot 源码分析(1) 启动过程分析

    u-boot 源码分析(1) 启动过程分析 文章目录 u-boot 源码分析(1) 启动过程分析 前言 配置 源码结构 api arch board common cmd drivers fs Kbu ...

  6. Appium Server 源码分析之启动运行Express http服务器

    通过上一个系列Appium Android Bootstrap源码分析我们了解到了appium在安卓目标机器上是如何通过bootstrap这个服务来接收appium从pc端发送过来的命令,并最终使用u ...

  7. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)

    http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...

  8. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

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

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

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

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

随机推荐

  1. [转帖]关于UNDO

    原文地址:https://www.modb.pro/db/70802?xzs= 一:请描述什么是Oracle Undo. 二:请描述UNDO的作用. 三:请谈谈你对Manual Undo Manage ...

  2. [转帖]TiKV 内存调优

    TiDB试用 来源:TiDB  浏览 87 扫码 分享 2023-05-09 09:02:19 TiKV 内存参数性能调优 参数说明 TiKV 内存使用情况 TiKV 机器配置推荐 TiKV 内存参数 ...

  3. 【转帖】You can now run a GPT-3-level AI model on your laptop, phone, and Raspberry Pi

    https://arstechnica.com/information-technology/2023/03/you-can-now-run-a-gpt-3-level-ai-model-on-you ...

  4. [转帖]Steam内存测试工具【转】

      转自:https://www.cnblogs.com/iouwenbo/p/14377478.html Stream测试是内存测试中业界公认的内存带宽性能测试基准工具. Stream安装 官方源码 ...

  5. [转帖]使用Linux命令快速查看某一行

      原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 当年,我还是Linux菜鸟的时候,就在简历上写着精通Linux命令了,而当面试官问我"如何快速查看 ...

  6. TypeScript工具类 Partial 和 Required 的详细讲解

    场景描述: 场景描述:一个接口(IPerson)有很多个的字段,可能有几百.而且这些字段都是必须的. 我们需要使用这个接口,但是我又不可能使用它的全部.可能只会使用几个. 我还必须要使用这接口.这个时 ...

  7. 通杀无限 debugger,目前只有 1% 的人知道!

    前言 相信很多小伙伴在进行 web 逆向的时候,都遇到过无限 debugger.最简单的方法,在 debugger 位置,点击行号,右键 Never pause here,永远不在此处断下即可.但是这 ...

  8. 人工智能创新挑战赛:海洋气象预测Baseline[4]完整版(TensorFlow、torch版本)含数据转化、模型构建、MLP、TCNN+RNN、LSTM模型训练以及预测

    人工智能创新挑战赛:海洋气象预测Baseline[4]完整版(TensorFlow.torch版本)含数据转化.模型构建.MLP.TCNN+RNN.LSTM模型训练以及预测 1.赛题简介 项目链接以及 ...

  9. C/C++ 使用CRC检测内存映像完整性

    前面的那一篇文章中所使用的技术只能有效抵抗解密者直接修改硬盘文件,当我们使用动态补丁的时候,那么内存中同样不存在校验效果,也就无法抵御对方动态修改机器码了,为了防止解密者直接对内存打补丁,我们需要在硬 ...

  10. HT UI 5.0,前端组件图扑是认真的

    为顺应数字时代的不断发展,图扑 HT UI 5.0 在原有功能强大的界面组件库的基础上进行了全面升级,融入了更先进的技术.创新的设计理念以及更加智能的功能.HT UI 5.0 使用户体验更为直观.个性 ...