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. Oracle process/session/cursor/tx/tm的简单学习

    Oracle process/session/cursor/tx/tm的简单学习 Oracle的部署模式 Oracle安装时有专用模式和共享模式的区别 共享模式(Shared mode): 在共享模式 ...

  2. [转帖]通过配置优化KingbaseES服务器性能

    目录 1. 概述 2. 数据库应用类型 3. 服务器参数 3.1. max_connections 3.2. shared_buffers 3.3. effective_cache_size 3.4. ...

  3. [转帖] Linux命令拾遗-查看系统信息

    https://www.cnblogs.com/codelogs/p/16060714.html 简介# 作为一名程序员,有时需要关注自己的进程运行在什么样的软硬件环境里,比如几核cpu.固态硬盘还是 ...

  4. [转帖]CentOS 7 下用 firewall-cmd / iptables 实现 NAT 转发供内网服务器联网

    https://www.cnblogs.com/hope250/p/8033818.html 自从用 HAProxy 对服务器做了负载均衡以后,感觉后端服务器真的没必要再配置并占用公网IP资源.而且由 ...

  5. 【转帖】route命令详解大全(route命令使用实例)

    https://www.cxdtop.cn/n/225987.html 在实际的网络应用中,我们可能会遇到这样的网络环境,上外网我们使用的无线网络,内网我们使用的是有限网卡.在设置完成后会出现外网和内 ...

  6. Core 文件的简单学习

    背景 最近公司内经常出现jvm进程宕机的情况. 宕机之后没有产生jvm的dump文件.比如xxx.hprof 但是产生了 core.$pid的文件. 曾经在aarch64架构上宕机时曾经想学习一下co ...

  7. 我们开源了一个轻量的 Web IDE UI 框架

    我们开源了一个轻量的 Web IDE UI 框架 Molecule 一个轻量的 Web IDE UI 框架 简介 Molecule 是一个受 VS Code 启发,使用 React.js 构建的 We ...

  8. 如何在centos7中完全卸载Python3

    如何在centos7中完全卸载Python3?根据查到的资料,主要就是卸载,然后删除一些软连接删除干净,逻辑很简单,贴一些具体的操作代码,记录下来 . 卸载Python3的步骤 #卸载python3 ...

  9. 多智能体强化学习算法【三】【QMIX、MADDPG、MAPPO】

    相关文章: 常见多智能体强化学习仿真环境介绍[一]{推荐收藏,真的牛} 多智能体强化学习算法[一][MAPPO.MADDPG.QMIX] 多智能体强化学习算法[二][MADDPG.QMIX.MAPPO ...

  10. 关于 React 的作业(未完结)

    一.输出 Hello React 信息到网页的程序,非JSX的写法 代码实现如下: <!DOCTYPE html> <html lang="en"> < ...