预成大事者,必先劳其筋骨,饿其体肤,空乏其身,行扶乱其所为也。
编程之道,写了不少的代码,但是往更高层次的发展,必需要付出更多的艰辛;
写了多年的Java和JavaScript,Oracle也用了不少,静下心来,看看一个高效的Key-value Store是怎么实现的。
源码获取
通过git从官方网址下载redis-2.6.14.tar.gz,解压缩有4个子目录
deps 依赖资源
src 源代码
tests 测试
utils工具
当然还有一些文件,需要关注
Makefile make文件
redis.conf 配置文件
sentinel.conf
主要内容分析
一:deps目录
下面包含
hiredis 作为Redis的一个mini C客户端库数据库
jemalloc 通用的内存分配工具 所用版本为3.2.0
linenoise 一个零配置的BSD Licensed的readline replacement,也是Android的一部分
lua 由巴西人写的一个脚本语言引擎 所用版本为5.1,官方最新为5.5版本 http://www.lua.org/
二:src目录
52个c文件
29个h文件
关注makefile,几个主要的编译输出
# redis-server
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS)
# redis-sentinel
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME)
# redis-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS)
# redis-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS)
# redis-check-dump
$(REDIS_CHECK_DUMP_NAME): $(REDIS_CHECK_DUMP_OBJ)
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
# redis-check-aof
$(REDIS_CHECK_AOF_NAME): $(REDIS_CHECK_AOF_OBJ)
$(REDIS_LD) -o $@ $^ $(FINAL_LIBS)
根据安装文件会包括如下几个部分
redis-server :Redis服务器的daemon启动程序
redis-cli 命令行操作工具
redis-benchmark 性能测试工具
redis-check-aof 数据修改
redis-check-dump 检查和数据导出工具
所以源头分析源码从redis.c, 总2727行
redis-benchmark.c, 总682行
redis-check-aof.c,总218行
redis-check-dump.c, 总771行
redis-cli.c 总1556行
3.1 redis.c
包含关联头文件
#include "redis.h"
#include "slowlog.h"
#include "bio.h"
以及linux下的time.h,signal.h,sys/wait.h,assert.h,ctype.h,stdarg.h,arpa/inet.h,fcntl.h,sys/time.h,sys/resource.h,sys/uioh,limits.h,float.h,mach.h
sys/resource.h,sys/utsname.h
所以需要读懂redis.c必须要先了解这些头文件的用处
首先拜读一下redis.h吧,1226行
有包含 "fmacros.h" 和"config.h"
同时引用其他头文件stdio.h,stdlib.h,string.h,time.h,limits.h,unistd.h,errno.h,inttypes.h,pthread.h,syslog.h,netinet/in.h,lua.h,signal.h
这些头文件都跟unix或者linux的c编程分不开的
#include "ae.h" /* Event driven programming library */
#include "sds.h" /* Dynamic safe strings */
#include "dict.h" /* Hash tables */
#include "adlist.h" /* Linked lists */
#include "zmalloc.h" /* total memory usage aware version of malloc/free */
#include "anet.h" /* Networking the easy way */
#include "ziplist.h" /* Compact list data structure */
#include "intset.h" /* Compact integer set structure */
#include "version.h" /* Version macro */
#include "util.h" /* Misc functions useful in many places */
redis.h内部内容包括如下几个部分
常量定义
结构体定义【redisObject,redisDb,redisClient,sharedObjectsStruct,zskiplistNode,zskiplist,zset,redisOp,redisOpArray,
redisServer,pubsubPattern,redisCommand,_redisSortObject,_redisSortOperation,】
方法定义,主要涵盖如下几类
utils
long long ustime(void);
long long mstime(void);
void getRandomHexChars(char *p, unsigned int len);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
void exitFromChild(int retcode);
/* networking.c -- Networking and Client related operations */
/* List data type */
/* MULTI/EXEC/WATCH... */
/* Redis object implementation */
/* Synchronous I/O with timeout */
/* Replication */
/* Generic persistence functions */
/* RDB persistence *
/* AOF persistence */
/* Sorted sets data type */
/* Core functions */
/* Set data type */
/* Hash data type */
/* Pub / Sub */
/* Configuration */
/* db.c -- Keyspace access API */
/* API to get key arguments from commands */
/* Sentinel */
/* Scripting */
/* Git SHA1 */
/* Commands prototypes */
/* Debugging stuff */
接下来看看redis.c中的main方法
int main(int argc, char **argv) {
struct timeval tv;
/* We need to initialize our libraries, and the server configuration. */
zmalloc_enable_thread_safeness();
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
/* We need to init sentinel right now as parsing the configuration file
* in sentinel mode will have the effect of populating the sentinel
* data structures with master nodes to monitor. */
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
if (argc >= 2) {
int j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL;
/* Handle special options --help and --version */
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
if (strcmp(argv[1], "--test-memory") == 0) {
if (argc == 3) {
memtest(atoi(argv[2]),50);
exit(0);
} else {
fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
exit(1);
}
}
/* First argument is the config file name? */
if (argv[j][0] != '-' || argv[j][1] != '-')
configfile = argv[j++];
/* All the other options are parsed and conceptually appended to the
* configuration file. For instance --port 6380 will generate the
* string "port 6380\n" to be parsed after the actual file name
* is parsed, if any. */
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
} else {
redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
}
if (server.daemonize) daemonize();
initServer();
if (server.daemonize) createPidFile();
redisAsciiArt();
if (!server.sentinel_mode) {
/* Things only needed when not running in Sentinel mode. */
redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
linuxOvercommitMemoryWarning();
#endif
loadDataFromDisk();
if (server.ipfd > 0)
redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);
if (server.sofd > 0)
redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
}
/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}
aeSetBeforeSleepProc(server.el,beforeSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}