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;
	}