《Redis源码学习笔记》RDB

来源:互联网 发布:淘宝消保怎么交 编辑:程序博客网 时间:2024/05/17 22:40

http://diaocow.iteye.com/blog/1946444


主从复制过程中,我们提到过RDB文件,作为Redis持久化方式之一,RDB把数据库某一时刻的内容,也就是快照,以二进制的方式记录到文件,并且在下次启动的时候可以用来初始化Redis; 

RDB最重要的两个过程是rdbSave和rdbLoad,下面我就这两个过程分别叙述; 

rdbSave 
在具体看rdbSave过程之前,我们先看下RDB文件格式: 



我们可以简单思考下,RDB文件协议为什么会那样设计? 
1. RDB文件以"REDIS"开头是为了区分普通文件,这样redis在加载的时候,读取前五个字节就可以判断该文件是否为一个合法的RDB文件; 
2. 写明RDB文件版本是因为不同版本之间有一定差异性,根据版本号需要保持向下兼容; 
3. 以数据库为单位,存入键值对,因为一个redis实例中,可以包含16个数据库,所以一定要标明该键值对属于哪个数据库,否则无法还原到相应位置; 
4. 在写入键值对信息时,超时时间是可选的,标明值类型(value-type)是为了在加载的时候知道这是一个string,还是list或者hashtable; 

rdbSave过程就是把redis每一个数据库中的键值对按照上面定义的格式写入文件,伪代码: 

Python代码  收藏代码
  1. def rdbSave(filename):  
  2.     # 创建临时文件,用于保存rdb数据  
  3.     tempFile = createTempFile()  
  4.     rio = rioInitWithFile(tempFile)  
  5.     # ----------------------------------  
  6.     # 1.保存RDB文件版本  
  7.     rdbSaveVersion(rio, "REDIS" + REDIS_RDB_VERSION)  
  8.     # 2.保存redis所有数据库中的键值对  
  9.     for db in redisServer.dbs:  
  10.         # 记录键值对所在数据库  
  11.         rdbSaveSelectDB(rio, db.num)  
  12.         # 保存该数据库中所有键值对  
  13.         for key, val in db.getKeyValuePairs():  
  14.             expired_time = getExpiredTime(key)  
  15.             rdbSaveKeyValuePair(rio, key, val, expired_time)  
  16.     # 3.写入RDB文件结束符  
  17.     rdbSaveEOF(rio, REDIS_RDB_OPCODE_EOF)  
  18.     # 4.写入校验和  
  19.     rdbSaveCkSum(rio)      
  20.     # ----------------------------------  
  21.     # 确认数据都被flush到磁盘  
  22.     fsync()  
  23.     rename(tempFile, filename)      
  24.   
  25. def rdbSaveKeyValuePair(rio, key, val, expired_time):  
  26.     if expired_time != -1:  
  27.         # 过滤过期键  
  28.         if expired_time < now_time:  
  29.             return  
  30.         rdbSaveMillisecondTime(rio, expired_time)  
  31.     # 保存值类型(list? string? hashtable?)  
  32.     rdbSaveValueType(rio, val)  
  33.     # 保存键  
  34.     rdbSaveKey(rio, key)  
  35.     # 保存值  
  36.     rdbSaveValue(rio, val)  

更多细节请看:rdb.c/rdbSave函数 

触发rdbSave过程,主要有4种方式: 
1. SAVE命令 
2. BGSAVE命令 
3. master接收到slave发来的sync命令 
4. 定时save(配置文件中制定) 

第一种情况,Redis保存RDB文件是在主进程中进行,所以在这其间,Redis无法响应客户端请求(再次强调:Redis是单线程Server);第二种情况,Redis fork出一个子进程,然后在子进程中进行rdbSave,因此也就不会阻塞主进程对客户端请求的处理;第三种情况和第四种情况同第二种情况,也是在子进程中进行; 

rdbLoad 
rdbload过程就比较简单了,它会按照RDB文件协议,把键值对还原到相应的数据库,伪代码: 
Python代码  收藏代码
  1. def rdbLoad(filename):  
  2.     rio =  rioInitWithFile(filename);  
  3.     # 设置标记:  
  4.     # a. 服务器状态:rdb_loading = 1  
  5.     # b. 载入时间:loading_start_time = now_time  
  6.     # c. 载入大小:loading_total_bytes = filename.size  
  7.     startLoading(rio)  
  8.     # ------------------------------------------  
  9.     # 1.检查该文件是否为RDB文件(即文件开头前5个字符是否为"REDIS")  
  10.     if !checkRDBHeader(rio):  
  11.         redislog("error, Wrong signature trying to load DB from file")   
  12.         return  
  13.     # 2.检查当前RDB文件版本是否兼容(向下兼容)  
  14.     if !checkRDBVersion(rio):   
  15.         redislog("error, Can't handle RDB format version")   
  16.         return  
  17.     # 3.读取文件内容,加载键值对  
  18.     while not end_of_file:  
  19.         # 每循环一千次就处理一下客户端请求  
  20.         if loops % 1000  == 0:  
  21.             processClientRequest()  
  22.   
  23.         key, val, expired_time, dbnum = rdbLoadKeyValuePair()  
  24.         # 过滤过期键  
  25.         if expired_time != -1 and expired_time <= now_time:   
  26.             continue  
  27.   
  28.         redisServer.db[dbnum].dict.add(key,value)  
  29.         if expired_time > now_time  
  30.             redisServer.db[dbnum].expires.add(key, expired_time)  
  31.         loops = loops + 1  
  32.   
  33.     # 4. 校验和  
  34.     if (!checkCkSum()): return  
  35.     # ------------------------------------------  
  36.     # 更新标记:rdb_loading = 0  
  37.     stopLoading()  

触发rdbLoad过程,主要有两种方式: 
1. Redis启动时候的初始化; 
2. slave接收到master发来的RDB文件; 

总结: 
1. 了解RDB文件格式; 
2. 了解rdbSave和rdbLoad过程以及触发条件; 

0 0
原创粉丝点击