redis 用redisServer 表示server端
struct redisServer {
/* General */
// 配置文件的绝对路径
char *configfile; /* Absolute config file path, or NULL */
// serverCron() 每秒调用的次数
int hz; /* serverCron() calls frequency in hertz */
// 数据库
redisDb *db;
int dbnum; 表示有几个数据库,默认有16个数据库,实际使用过程中可以用select 来选择哪个数据库
}
这里的redisDb是一个数组保存着服务器中的所有数据库,每个redisDb 代表一个数据库
与之相对的,用
typedef struct redisClient {
// 记录当前正在使用的数据库,这个会指向redisServer.db 16个数据库中的一个
redisDb *db;
}
用redisClient来表示一个redis客户端。
下来我们看看redisDb
typedef struct redisDb {
// redis是一种key-value的数据库,这个dict中就是保存的键值对
dict *dict; /* The keyspace for this DB */
// 可以通过expire 或者pexpire 命令来设置键的过期时间,这个过期的键值
dict *expires; /* Timeout of keys with a timeout set */
} redisDb;
setex命令可以在设置一个字符串键的同时同时为键设置过期时间,可以通过ttl或者pttl命令来
返回key的到期时间,persist 可以移除key的过期时间
redis的过期键删除策略是惰性删除和定期删除两种组合起来使用,定时删除比较耗cpu 一般不使用.
redis的持久化分为RDB和AOF
其中rdb文件用于保存和还原redis数据库中中的所有数据,
可以通过save和bgsave命令生成rbd文件,其中save 命令是阻塞执行,而bgsave 是非阻塞执行
可以在save 后面设置条件,当条件满足是,自动调用save生成rbd文件,rbd是一种压缩的二进制文件。
可以通过redis-check-dump 来检查rdb文件,或者同od -cx xxx.rdb 命令来dump.
我们来看看redis save命令的最终实现
int rdbSave(char *filename) {
dictIterator *di = NULL;
dictEntry *de;
char tmpfile[256];
char magic[10];
int j;
long long now = mstime();
FILE *fp;
rio rdb;
uint64_t cksum;
// 创建一个临时的rdb文件。以w的方式创建.
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
fp = fopen(tmpfile,"w");
if (!fp) {
redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
strerror(errno));
return REDIS_ERR;
}
// 初始化 I/O
rioInitWithFile(&rdb,fp);
// 设置用于计算校验和的函数指针
if (server.rdb_checksum)
rdb.update_cksum = rioGenericUpdateChecksum;
//原来rdb文件最前面是redis+ 版本号啊
snprintf(magic,sizeof(magic),"REDIS%04d",REDIS_RDB_VERSION);
if (rdbWriteRaw(&rdb,magic,9) == -1) goto werr;
// 遍历所有的数据库,默认是有16个数据库
for (j = 0; j < server.dbnum; j++) {
//16个数据库是通过偏移来访问的
redisDb *db = server.db+j;
// 指向数据库的字典
dict *d = db->dict;
//数据库的size为零,说明是空数据库
if (dictSize(d) == 0) continue;
// 创建键空间迭代器
di = dictGetSafeIterator(d);
if (!di) {
fclose(fp);
return REDIS_ERR;
}
/* Write the SELECT DB opcode
*
* 写入 DB 选择器
*/
if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_SELECTDB) == -1) goto werr;
if (rdbSaveLen(&rdb,j) == -1) goto werr;
// 遍历数据库,并写入每个键值对的数据
while((de = dictNext(di)) != NULL) {
sds keystr = dictGetKey(de);
robj key, *o = dictGetVal(de);
long long expire;
// 根据 keystr ,在栈中创建一个 key 对象
initStaticStringObject(key,keystr);
//获取键的过期时间
expire = getExpire(db,&key);
// 看来rdb中不会保持过期的键
if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
}
dictReleaseIterator(di);
}
di = NULL; /* So that we don't release it again on error. */
// 写入EOP 代表结束
if (rdbSaveType(&rdb,REDIS_RDB_OPCODE_EOF) == -1) goto werr;
//EOF 后保存的校验和
cksum = rdb.cksum;
memrev64ifbe(&cksum);
rioWrite(&rdb,&cksum,8);
/* Make sure data will not remain on the OS's output buffers */
// 将数据写入到磁盘
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
if (fclose(fp) == EOF) goto werr;
//确认数据写入到磁盘后,才将临时的rdb文件改名成正式的rdb
if (rename(tmpfile,filename) == -1) {
redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
unlink(tmpfile);
return REDIS_ERR;
}
// 输出日志表示rdb保存完成
redisLog(REDIS_NOTICE,"DB saved on disk");
server.dirty = 0;
//在server中保存最后一次生成rdb的时间
server.lastsave = time(NULL);
//记录最后一次rdb是否保存成功
server.lastbgsave_status = REDIS_OK;
return REDIS_OK;
werr:
fclose(fp);
unlink(tmpfile);
redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
if (di) dictReleaseIterator(di);
return REDIS_ERR;
}
另一种持久化是AOF,是通过保存所有写命令来记录服务器的数据库状态,所以AOF中的内容都是redis的写
命令。可以通过重写的方式来减少AOF 文件的size.
AOF 最终调用下面的函数来讲AOF文件写到硬盘,从在这个函数可以看出AOF的缓存平时是保存在
server.aof_buf 中的,同时可以知道这个缓存就是一个sds 字符串,因此在下AOF文件时首先检测一下
aof_buf是否为null,为null的话,就不用去写了.
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
int sync_in_progress = 0;
// 缓冲区中没有任何内容,直接返回
if (sdslen(server.aof_buf) == 0) return;
}