Redis版本:Redis 4.0.1
 
  
Redis是一个键值对数据库服务器,存储在内存中,如果重启没进行持久化,数据会丢失。所以需要持久化策略RDB和AOF。
 
  
 
  
前提知识归纳
 
  
RDB用于保存和恢复数据库所有键值对
 
   
使用save或bgsave报错,其中bgsave是fork子进程不会造成阻塞
 
   
rdb文件是一个二进制文件
 
   
不同类型键,会有不同数据结构存储
 
  
 
  
RDB会采用save、bgsave进行rdb保存
 
  
save:生成RDB,会导致Redis阻塞
 
   
bgsave:生成RDB,fork子进程处理
 
  
优先使用AOF文件加载数据。
 
  
 
  
其中是否执行bgsave这些是根据 RedisServer#saveparams 来判断,其数据结构如下:
 
  
struct 
   redisServer 
   {
 
  
....
 
  
    // 
   保存触发
   rdb
   文件存储的参数数组
 
  
    struct 
   saveparam 
   *
   saveparams
   ; 
   /* Save points array for RDB */
 
  
}
 
  
// 在seconds秒内数据库修改changes次就触发RDB文件生成
 
  
struct 
   saveparam 
   {
 
  
    time_t 
   seconds
   ;// 秒数
 
  
    int 
   changes
   ;// 数据库修改次数
 
  
};
 
  
   符合上面的参数生成RDB,举个例子:
  
 
  
• 
     seconds=1, changes=5。即代表1秒内数据库有5次修改就可以重新生成RDB
    
    
  
 
  
bgsave是fork子进程进行处理,其触发方式大概有如下两种:
 
  
    手动触发:执行bgsave命令手动触发
   
 
   
     
   
 
   
sync指令或psync(这个做主从分析的时候再解析)
 
   
     
   
 
   
下面举个定时触发的例子源码(serverCron定时触发,然后扫描saveparams参数)
 
  
/**
 
  
* 
   作为时间事件运行
 
  
*/
 
  
int 
   serverCron
   (
   struct 
   aeEventLoop 
   *eventLoop
   , long long 
   id
   , void 
   *clientData) {
 
  
    ....
 
  
    // 
   单位时间内修改次数达到上限
 
  
    if 
   (server.
   dirty 
   >= sp->
   changes 
   &&
 
  
        server.unixtime-server.lastsave > sp->seconds && 
   (server.unixtime-server.lastbgsave_try > 
   CONFIG_BGSAVE_RETRY_DELAY || 
   server.
   lastbgsave_status 
   == C_OK)) 
   {
 
  
        // 
   打印保存进行中的日志
 
  
        serverLog(LL_NOTICE
   ,
   "%d changes in %d seconds. Saving..."
   ,
 
  
        sp->
   changes
   , 
   (
   int
   )sp->seconds)
   ;
 
  
        rdbSaveInfo 
   rsi
   , 
   *rsiptr
   ;
 
  
        rsiptr = rdbPopulateSaveInfo(&rsi)
   ;
 
  
        // 
   进行
   bgsave
 
  
        rdbSaveBackground(server.
   rdb_filename
   ,
   rsiptr)
   ;
 
  
        break;
 
  
    }
 
  
    ....
 
  
}
 
  
    
  
 
  
/**
 
  
* 
   rdbbgsave
   ,会
   fork
   一个子进程处理
 
  
**/
 
  
int 
   rdbSaveBackground
   (
   char 
   *filename
   , 
   rdbSaveInfo 
   *rsi) {
 
  
    pid_t childpid
   ;
   // 
   子进程
 
  
    // 
   判断是否有活跃的子进程在拷贝,如果有则终止 
   server.child_pid != -1
 
  
    if 
   (hasActiveChildProcess()) 
   return 
   C_ERR
   ;
 
  
    
   // 
   记录当前
   bgsave
   前的数据库被修改次数 和 最后修改的时间
  
 
  
    server.
   dirty_before_bgsave 
   = server.
   dirty
   ;
 
  
    server.lastbgsave_try = time(NULL)
   ;
 
  
    // fork
   子进程
 
  
    if 
   ((childpid = redisFork(CHILD_TYPE_RDB)) == 
   0
   ) {
 
  
        int 
   retval
   ;
 
  
    
  
 
  
        /* Child */
 
  
        redisSetProcTitle(
   "redis-rdb-bgsave"
   )
   ;
   // 
   设置标题
 
  
        redisSetCpuAffinity(server.
   bgsave_cpulist
   )
   ;
   // 
   设置
   CPU
   亲缘属性
 
  
        retval = rdbSave(filename
   ,
   rsi)
   ;
   // 
   开始保存
 
  
        if 
   (retval == C_OK) {
   // 
   发送保存数据
 
  
            sendChildCowInfo(
   CHILD_INFO_TYPE_RDB_COW_SIZE
   , 
   "RDB"
   )
   ;
 
  
        }
 
  
        exitFromChild((retval == C_OK) ? 
   0 
   : 
   1
   )
   ;
   // 
   结束子进程保存
 
  
    } 
   else 
   {
 
  
        /* Parent */
 
  
        // fork
   子进程失败
 
  
        if 
   (childpid == -
   1
   ) {
 
  
            server.
   lastbgsave_status 
   = C_ERR
   ;
 
  
            serverLog(LL_WARNING
   ,
   "Can't save in background: fork: %s"
   ,
 
  
            strerror(errno))
   ;
 
  
            return 
   C_ERR
   ;
 
  
        }
 
  
        // 
   保存状态
 
  
        serverLog(LL_NOTICE
   ,
   "Background saving started by pid %ld"
   ,
   (
   long
   ) childpid)
   ;
 
  
        server.rdb_save_time_start = time(NULL)
   ;
 
  
        server.
   rdb_child_type 
   = RDB_CHILD_TYPE_DISK
   ;
 
  
        return 
   C_OK
   ;
 
  
    }
 
  
    return 
   C_OK
   ; 
   /* unreached */
 
  
}
 
  
上面解释了RDB是如何装载进来的,那下面来讲解一下RDB的文件结构。如何存储?如何压缩?
 
  
RDB文件大致组成结构
 
  
- REDIS:标识RDB文件开头的字符'REDIS'
 
   
- db_version:4字节。记录RDB文件版本号
 
   
- database:包含0-N个数据库
 
   
- EOF:标记RDB结束
 
   
- check_sum:保存前面四个的校验和,用于做签名校验
 
  
   

   
   
其中database存储结构如下
   
- SELECTDB:database存储的开始标识
    
- db_number:1/2/5字节。数据库号码
    
key_value_pairs:存储当前数据库中所有的键值对数据, 根据类型不同而结构不同
   
   

   
   

   
   
key_value_pairs结构为
   
不带过期时间的键值对
    
- TYPE:记录value类型,主要含string、list、zset、hash、list_zipskiplist等
    
- key:redis key,总是string类型
    
- value:redis对应的值
   
   

   
带过期时间的键值对
    
- EXPIRETIME_MS:1字节。开始标识
    
- ms:8字节。UNIX时间戳
    
- TYPE:记录value类型,主要含string、list、zset、hash、list_zipskiplist等
    
- key:redis key,总是string类型
    
- value:redis对应的值
   
   

   
   
value是怎么根据TYPE编码来生成RDB的对应值数据(大致讲解几个即可,知道什么回事,没必要纠结)
   
String字符串
   
字符串<=20字节:len记录原来长度
   
   

   
字符串>20字节:origin_len记录原来长度,compresesed_len记录压缩后长度
   
   

   
   
List列表
   
记录长度,然后item一个接一个拼起来(每个元素记录长度)
   
   

   
   

   
   
set集合
   
跟list一样
   
   
hash哈希
   
记录有多少个键值对,比如下面:2个键值对,"a"->"apple","b"->"banana"
   
   

   
   

   
   
zset有序集合
   
记录分数和item
   
   

   
   

   
   
  










