【Redis源码】Redis 启动过程分析
简介
由于本人目前是华为FusionInsight HD 中Redis组件的Owner,所以要对Redis进行深入的了解,这对于C语言水平不咋地的我来讲还是有点难度的,于是我决定先从Redis的启动开始看,了解其基本原理。
配置初始化
Redis服务启动首先做的第一步就是初始化配置。Redis初始化配置主要包括初始化命令表和加载配置两部分。
初始化ACL 权限信息
主要是通过加载redis.conf配置文件里面的配置信息,用于控制登录用户执行命令的权限,仔细详见:Redis 6 ACL源码详解
初始化命令表
在函数populateCommandTable将redisCommandTable中的命令加载到字典server.commands当中,用于执行命令的时候使用,并且对于比较常用的命令赋予成员属性,减少查找。redisCommandTable的结构如下:
struct redisCommand redisCommandTable[] = {
{"module",moduleCommand,-2,
"admin no-script",
0,NULL,0,0,0,0,0,0},
{"get",getCommand,2,
"read-only fast @string",
0,NULL,1,1,1,0,0,0},
/* Note that we can't flag set as fast, since it may perform an
* implicit DEL of a large key. */
{"set",setCommand,-3,
"write use-memory @string",
0,NULL,1,1,1,0,0,0},
{"setnx",setnxCommand,3,
"write use-memory fast @string",
0,NULL,1,1,1,0,0,0},
}
- name:命令的名称
- proc:命令对应的函数名。redis-server处理命令时要执行的函数
- arity:命令的参数个数,如果是-N代表大于等于N
- sflags:命令标志,标识命令的类型(read/write/admin...)
- flags:位掩码,由Redis根据sflags计算
- get_keys_proc:可选函数,当下面三个项不能指定哪些参数是key时使用
- first_key_index:第一个是key的参数
- last_key_index:最后一个是key的参数
- key_step:key的“步长”,比如MSET的key_step是2,因为它的参数是key,val,key,val这样的形式
- microseconds:执行命令所需要的微秒数
- calls:该命令被调用总次数
防止查找的命令如下:
server.delCommand = lookupCommandByCString("del");
server.multiCommand = lookupCommandByCString("multi");
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
server.zpopminCommand = lookupCommandByCString("zpopmin");
server.zpopmaxCommand = lookupCommandByCString("zpopmax");
server.sremCommand = lookupCommandByCString("srem");
server.execCommand = lookupCommandByCString("exec");
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
server.xclaimCommand = lookupCommandByCString("xclaim");
server.xgroupCommand = lookupCommandByCString("xgroup");
server.rpoplpushCommand = lookupCommandByCString("rpoplpush");
初始化哨兵模式
当开启哨兵模式时,redis启动就会初始化哨兵模式相关参数等。初始化哨兵模式主要是函数initSentinelConfig() 和initSentinel()两部分。
- initSentinelConfig()主要是初始化哨兵模式的端口等。
- initSentinel()主要删除redis实例基本的命令,初始化哨兵的相关命令。
struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0}
}
检查RDB/AOF文件
启动前会会通过函数redis_check_rdb_main()/redis_check_aof_main()检查RDB/AOF文件的完整性。
命令行参数处理
如果是简单的参数例如-v或--version、-h或--help,就会直接调用相应的方法,打印信息。如果是使用其他配置文件,则修改server.exec_argv。对于其他信息,会将他们转换成字符串,然后添加进配置文件,例如“--port 6380”就会被转换成“port 6380\n”加进配置文件。这时,redis就会调用loadServerConfig()函数来加载配置文件,这个过程会覆盖掉前面初始化默认配置文件的变量的值。
初始化Redis
初始化 Shared object
createSharedObjects()函数会创建一些shared对象保存在全局的shared变量中,对于不同的命令,可能会有相同的返回值(比如报错)。这样在返回时就不必每次都去新增对象了,保存到内存中了。这个设计就是以Redis启动时多消耗一些时间为代价,换取运行的更小的延迟。
初始化监听事件
initServer()函数调用aeCreateEventLoop()函数(ae.c文件)来增加循环事件,并将结果返回给server的el成员。Redis使用不同的函数来兼容各个平台,在Linux平台使用epoll,在BSD使用kqueue,都不是的话,最终会使用select。Redis轮询新的连接以及I/O事件,有新的事件到来时就会及时作出响应。
监听端口
anetUnixServer()函数中使用函数anetListen监听端口。
初始化LRU键池
evictionPoolAlloc函数用于初始化LRU的键池,Redis的key过期策略是近似LRU算法
void evictionPoolAlloc(void) {
struct evictionPoolEntry *ep;
int j;
ep = zmalloc(sizeof(*ep)*EVPOOL_SIZE);
for (j = 0; j < EVPOOL_SIZE; j++) {
ep[j].idle = 0;
ep[j].key = NULL;
ep[j].cached = sdsnewlen(NULL,EVPOOL_CACHED_SDS_SIZE);
ep[j].dbid = 0;
}
EvictionPoolLRU = ep;
}
定时任务
Redis会执行aeCreateTimeEvent()(在ae.c文件中)函数,用来新建一个循环执行serverCron()函数的事件。serverCron()默认每100毫秒执行一次。
/* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed
* expired keys and so forth. */
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
主循环事件
程序调用aeMain()函数,进入主循环,这时其他的一些循环事件也会分别被调用。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}
到此Redis就启动完成了。
相关文章
- 分析Redis性能: 了解你的Redis状态.(redis-stats)
- 本地部署Redis:实现NoSQL快速存取(redis本地配置)
- 实现高性能:Redis架构图分析(redis 架构图)
- 自学研究Redis手写Redis源码的过程(手写redis源码)
- 引用Redis注解,提高数据性能(引入redis注解)
- 学会看懂Redis源码 尽享学习好收获(看懂redis源码好处)
- 查看本机Redis版本 一键式轻松解决方案(查看本机redis版本)
- 电脑与Redis涉外一个突破性案例分析(电脑给redis涉外例)
- 一步步指引源码包安装Redis(源码包安装redis)
- 无奈无法远程访问Redis服务器(无法远程访问redis)
- 实现Java Swing应用程序与Redis无缝整合(swing整合redis)
- 字节跳动系列Redis 面试题分析(字节跳动redis面试题)
- 比较内存缓存与Redis的优劣(内存缓存和redis区别)
- 如何面对Redis源码分层理解精益求精(如何学习redis源码)
- Redis项目应用分析(redis项目使用情况)
- 精准调整Redis集群配置参数分析(redis集群配置参数)
- 三从架构完美,Redis集群模式三主三从实现高可用(redis集群模式三主)
- Redis集群解决高可用性问题(redis集群是干嘛用的)
- 深入探索Redis集群的源码分析(redis集群分析源码)
- Redis队列实现批量处理(redis 队列 批量)
- Redis vs 源码分析比较为何(vs redis源码分析)
- Redis一种高效存储技术(redis通俗含义)
- Redis过期时间设置之EXPIRE指令(redis 过期时间命令)
- 查询次数猛增Redis新机遇新挑战(redis查询次数多)