7.redis设计与实现学习笔记-数据库&RDB持久化

文章目录

  • 8. 数据库
    • 8.1. 介绍下这个数据库
    • 8.2. 切换数据库
    • 8.3. 数据库键空间
      • 8.3.1. 键的CRUD
      • 8.3.2. 其他键空间的操作
      • 8.3.3. 读写键空间时的维护操作
    • 8.4.设置键的生存时间或过期时间
      • 8.4.1. 保存过期时间
      • 8.4.2. 移除过期时间
      • 8.4.3. 如何判定过期键
    • 8.5. 过期键的删除策略
    • 8.6. Redis 的过期键删除策略
      • 8.6.1. 惰性删除策略的实现
      • 8.6.2. 定期删除策略的实现
    • 8.7. AOF、RDB和复制功能对过期键的处理
      • 8.7.1. 生成RDB文件
      • 8.7.2. 载入RDB文件
      • 8.7.3. AOF文件写入
      • 8.7.4. AOF重写
      • 8.7.5. 复制
    • 8.8. 数据库通知
      • 8.8.1. 发送通知
      • 8.8.2. 发送通知的实现
  • 9. RDB 持久化
    • 9.1. RDB文件的创建和载入
      • 9.1.1. 服务器状态
    • 9.2. 自动间隔性保存
      • 9.2.1. 设置保存条件
      • 9.2.2. dirty计数器和lastsave属性
      • 9.2.3. 检查保存条件是否满足
    • 9.3. RDB文件结构
      • 9.3.1. databases 部分
      • 9.3.2. key_value_pairs 部分
      • 9.3.3. value的编码
    • 9.4. 分析RDB文件

8. 数据库

8.1. 介绍下这个数据库

Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:

struct redisServer{
    // ...
    // 一个数组,保存着服务器中的所有数据库
    redisDb *db;
    // ...
    // 服务器的数据库数量
    int dbnum;
}

在初始化服务器的时候,程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库。而dbnum属性的值由服务器配置的database选项决定,默认情况下,改选项值为16,所有redis默认会创建16个数据库。

8.2. 切换数据库

每个redis客户端都有自己的目标数据库(默认是0号数据库,可以通过SELECT命令来切换数据库),每当客户端执行数据库写命令或者数据库读命令的时候,目标数据库就会成为这些命令的操作对象。

客户端状态redisClient结构的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDbd结构的指针:

typedef struct redisClient{
    // ...
    // 记录客户端当前正在使用的数据库
    redisDb *db;
    // ...
}redisClient;

redisCilent.db指针指向RedisServer.db数组中的其中一个元素,而被指向的元素就是客户端的目标数据库,我们可以通过修改redisCilent.db指针,让它指向服务器中的不同数据库,从而实现切换目标数据库的功能(SELECT实现原理)。

8.3. 数据库键空间

Redis是一个键值对数据库服务器,服务器中的每个数据库都由一个redis.h/redisDb结构表示,其中redisDb中的dict字典保存了数据库中的所有键值对,我们将其称为键空间:

typedef struct redisDb{
    // ...
    // 数据库键空间,保存着数据库中的所有键值对
    dict *dict;
}redisDb;

键空间和用户所见的数据库直接对应:

  • 键空间的键就是数据库的键,每个键都是字符串对象
  • 键空间的值就是数据库的值

8.3.1. 键的CRUD

对键值对的CRUD其实就是,对键空间里面的键或者键所对应的值进行CRUD操作。

8.3.2. 其他键空间的操作

除了CRUD之外,还有很多针对数据库本身的Redis命令,都是通过对键空间进行处理来完成的

eg:

  • 清空数据库的FLUSHDB
  • 随机返回数据库某个键的RANDOMKEY
  • 返回数据库键数量DBSIZE
  • EXITSTS、RENAME、KEYS等等啊,都是通过对键空间进行操作实现的

8.3.3. 读写键空间时的维护操作

当执行Redis对数据库进行读写操作的命令时,服务器在执行的时候,还会执行一些额外的维护操作:

  • 读取一个键时,服务器根据键是否存在来更新服务器的键空间命中(hit)次数或不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits属性和keyspace_misses属性中查看。
  • 读取一个键时,服务器会更新键的LRU(最后一次使用)时间,这值用于计算键的空置时间,可以使用OBJECT idletime 命令查看
  • 如果读取时,发现键已经过期了,服务器会先删除这个键,再执行其余操作
  • 如果使用WATCH命令监视某个键,服务器会在这个键被修改之后,将这个键标记为脏(dirty),从而让食物程序注意到这个键被修改过。
  • 服务器每修改一个键之后,都会对脏(dirty)键计数器进行+1操作,这个计数器会触发服务器的持久化以及复制操作
  • 如果服务器开启了数据库通知功能,那在键修改之后,服务器会按配置发送相应的数据库通知。

8.4.设置键的生存时间或过期时间

通过EXPIRE命令或者PEXPIRE命令,客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间,再进过指定时间之后,服务器就会自动删除生存时间为0的键。

eg:

127.0.0.1:6379> SET key value
OK
127.0.0.1:6379> EXPIRE key 5
(integer) 1
127.0.0.1:6379> GET key   // 5秒之内
"value"
127.0.0.1:6379> GET key		// 5秒之后
(nil)

注:

SETEX命令可以在设置一个字符串键的同时为键设置过期时间,但是这个命令只适用于字符串键。

用户可以通过EXPIREAT或者PEXPIREAT命令,设置过期时间(秒或毫秒为精度),用法和上面类似。过期时间是一个UNIX时间戳,当键的过期时间到了,服务器就会自动从数据库中删除这个键。

TTLPTTL接受一个带有生存时间胡总恶化过期时间点额键,返回这个键的剩余生存时间,即返回这个键距离被删除还有多长时间。eg:TTL key

解释下命令吧:

P的精度都是毫秒,不带P的精度是秒

8.4.1. 保存过期时间

redisDb结构中的expires字典保存了数据库中所有键的过期时间,我们称之为过期字典

  • 过期字典的键是一个指针,这指针指向键空间中的某个键对象(也就是某个数据库键)
  • 过期字典的值是一个long kong类型的整数,这个整数保存了键所指向的数据库键的过期时间(以毫秒为精度的UNIX时间戳)。
typedef struct redisDb{
    // . . .
    // 过期字典,保存着键的过期时间
    dict *expires;
    // . . .
}redisDb;

当我们设置了过期时间,服务器会自动往过期字典中添加数据。

8.4.2. 移除过期时间

PERSIST命令可以移除一个键的过期时间。它就是在过期字典中找到给定的键,然后解除键和值(过期时间)在过期字典中的关联。 命令格式:PERSIST key

8.4.3. 如何判定过期键

  1. 检查给定键是否存在于过期字典中:如果存在:返回键的过期时间
  2. 检查当前UNIX时间戳是否大于键的时间戳:如果是,键过期

8.5. 过期键的删除策略

  1. 定时删除

    使用定时器,定时删除过期键,可以保证尽快删除过期键,释放过期键的内存,对内存友好,对CPU不友好

  2. 惰性删除

    只在取出键时才对键进行过期检查,对CPU非常友好,但是如果过期键太对的话,非常浪费内存。甚至如果我们一直不访问的话,过期键永远不会被删除,占用大量内存。

  3. 定期删除(TRUE)

    每隔一段时间执行一次删除过期键操作,并且限制删除操作执行的时长和频率来减少操作对CPU时间的影响。

8.6. Redis 的过期键删除策略

8.6.1. 惰性删除策略的实现

过期键的惰性删除策略由db.0c/expireIfNeeded函数删除,所有读写数据库的命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:

  • 如果键过期了,那么expireIfNeeded函数回见输入键从数据库中删除
  • 如果没有过期,这个函数不采取任何动作

8.6.2. 定期删除策略的实现

过期键的定期删除有redis.c/activeExpireCycle函数实现,当Redis周期性操作redis.c/serverCron函数执行时,activeExpireCycle函数就会被调用,在规定时间内,多次遍历服务器的各个数据库,从数据库的expires字典中岁间检查出一部分键的过期时间,并删除过期键。

8.7. AOF、RDB和复制功能对过期键的处理

8.7.1. 生成RDB文件

执行SAVE命令或者BGSAVE明理创建一个新的RDB文件时,服务器会对数据库中的键进行检查,已过期的键不会被爆粗到新创建的RDB文件中。

8.7.2. 载入RDB文件

在启动Redis服务器是,如果开启了RDB功能的话,那么服务器将对RDB文件进行载入:

  • 如果服务器已主服务器运行,载入RDB时,程序会对文件中的键进行检查,未过期的键会被载入到数据库中,过期的键会被忽略
  • 如果是从服务器启动,那么在载入RDB文件是,文件中保存的所有键(不论过没过期),都会被载入到数据库,不过主从服务器在进行数据同步的时候,从服务器的数据库将会被删除。所以过期键对载入RDB文件的从服务器没有影响。

8.7.3. AOF文件写入

当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生影响。

当过期键被惰性删除或者定期删除之后,程序会想AOF文件追加(append)一条DEL命令,来显示地记录该键已被删除。

8.7.4. AOF重写

和生成的RDB文件类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写的AOF文件中。

8.7.5. 复制

当服务器运行在复制模式下,从服务器的过期删除动作由主服务器控制:

  • 主服务器在删除一个过期键之后,会显示地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键
  • 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将其删除,而是继续像处理未过期的键一样来处理这个键
  • 从服务器只有在接到主服务器的DEL命令之后,才会删除过期键

综上:由主服务器来控制从服务器的删除过期键的动作,这样有利于保证主从服务器的数据一致性。

8.8. 数据库通知

数据库通知是Redis2.8版本新增的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来货值数据库中键的变化,以及数据库中命令的执行情况。

  1. 键空间通知(key-space notification):

    关注某个键执行了什么命令

    格式:SUBSCRIBE _ _keyspace@0_ _:键名

  2. 键事件通知(key-event notification):

    关注某个命令被什么键执行了

    格式:SUBSCRIBE _ _keyevent@0_ _:命令

8.8.1. 发送通知

发送数据库的通知的功能是由notif.c/notifyKeyspaceEvent函数实现的。

函数定义: void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid)

  • type就是想要发送的通知类型
  • event、key和dbid分别是事件的名称、产生事件的键以及产出时间的数据库号码。

举个例子吧 (SADD命令的实现函数saddCommand)

void saddCommand(redisClient *c){
    // ...
    // 如果至少有一个元素被成功添加,那么执行以下程序
    if(added){
        // ...
        // 发送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_SET, "sadd", c->argv[1], c->db->id);
    }
    // ...
}

当SADD命令成功向集合添加了一项或者多项集合元素,命令就会发送通知,改通知的类型为REDIS_NOTIFY_SET(表示是一个集合键通知),名称为sadd(这表示是执行SADD命令所产生的通知)。

8.8.2. 发送通知的实现

notifyKeyspaceEvent函数:

void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
    sds chan;
    robj *chanobj, *eventobj;
    int len = -1;
    char buf[24];
    /* 如果给定的通知不是服务器允许发送的通知,则返回*/
    if (!(server.notify_keyspace_events & type)) return;

    eventobj = createStringObject(event,strlen(event));

    /* __keyspace@__:  notifications. 发送键空间通知*/
    if (server.notify_keyspace_events & NOTIFY_KEYSPACE) {
        chan = sdsnewlen("__keyspace@",11);
        len = ll2string(buf,sizeof(buf),dbid);
        chan = sdscatlen(chan, buf, len);
        chan = sdscatlen(chan, "__:", 3);
        chan = sdscatsds(chan, key->ptr);
        chanobj = createObject(OBJ_STRING, chan);
        pubsubPublishMessage(chanobj, eventobj);//发送通知
        decrRefCount(chanobj);// 对象引用计数+1
    }

    /* __keyevente@__:  notifications. 发送键事件通知*/
    if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {
        chan = sdsnewlen("__keyevent@",11);
        if (len == -1) len = ll2string(buf,sizeof(buf),dbid);
        chan = sdscatlen(chan, buf, len);
        chan = sdscatlen(chan, "__:", 3);
        chan = sdscatsds(chan, eventobj->ptr);
        chanobj = createObject(OBJ_STRING, chan);
        // 这个函数是PUBLISH命令的实现函数,调用这个函数就相当于执行PUBLISH命令
        pubsubPublishMessage(chanobj, key);
        decrRefCount(chanobj);//对象引用计数+1
    }
    decrRefCount(eventobj);
}

9. RDB 持久化

数据库状态:Redis键值对数据库服务器中包含任意多个非空数据库,每个非空数据库又包含任意多个键值对,我们将服务器中的非空数据库以及它们的键值对统称为数据库状态。

Redis是内存数据库,它将数据库状态存在内存里,一旦服务器退出,服务器的数据库状态就会消失,所以就需要将存储在内存中的数据库状态保存到磁盘中,而这就是我们需要的RDB持久化,用于把Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

RDB持久化可以通过手动执行,也能根据服务器配置选项定期执行,将某个时间点上的数据库状态保存到一个RDB文件上。我们可以通过这个文件还原数据库状态。

9.1. RDB文件的创建和载入

两个命令:SAVE、BGSAVE用于生存RDB文件。

  • SVAE

    会阻塞Redis服务器进程,知道RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令

  • BGSAVE

    派生出一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。

创建RDB文件由rdb.c/rdbSave函数实现,SAVEBGSAVE就是以不同的方式去调用这个函数,看看他们的伪代码吧:

def SAVE():
	# 创建RDB文件
	rdbSave();

def BGSAVE():
	# 创建子进程
	pid = fork();

	if(pid == 0):
		# 子进程负责创建RDB文件
		rdbSave();
		# 完成之后向父进程发送信号
		signal_parent();
	else pid > 0:
		# 傅进臣继续处理命令请求,并通过轮询等待子进程信号
	else:
		# 处理出错情况
		handle_fork_error();

**关于RDB文件的载入:**RDB文件的载入时服务器启动的时候就自动执行的,Redis没有专门用于载入RDB文件的命令,当Redis在启动的时候检测到RDB文件存在,就会自动载入RDB文件。

注意:AOF 文件的更新频率通常比RDB文件高,所以

  • 如果服务器开启了AOF持久化功能的话,那么服务器会优先使用AOF文件来还原数据库状态
  • 只有在AOF持久化功能关闭的情况下,服务器才会使用RDB文件来还原数据库状态

载入RDB文件由rdb.c/rdbLoad函数完成,

7.redis设计与实现学习笔记-数据库&RDB持久化_第1张图片

9.1.1. 服务器状态

  • SAVE

    会阻塞Redis服务器进程,知道RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令。只有在服务器执行完SAVE命令、重新开始接收命令请求之后,客户端发送的命令才会被处理。

  • BGSAVE

    派生出一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求。在命令执行期间,服务器处理SAVEBGSAVEBGREWRITEAOF三个命令的处理方式和平时不同:

    • BGSAVE执行期间,SAVE命令会被服务器拒绝,防止父进程(服务器进程)和子进程同时执行两个rdbSave调用,发生竞争。
    • BGSAVE执行期间,BGSAVE也会被拒绝,原因和上述差不多
    • BGSAVEBGREWRITEAOF不能同时执行
      • BGSAVE执行时,BGREWRITEAOF会被延迟到BGSAVE执行结束后执行
      • BGREWRITEAOF执行时,BGSAVE会被拒绝
  • 服务器在载入RDB文件时,会一直处于阻塞状态,知道载入工作完成为止

9.2. 自动间隔性保存

Redis 可以根据save选项设置的保存条件,自动执行BGSAVE命令

9.2.1. 设置保存条件

当Redis服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save选项,如果用户没有主动设置save选项,那么服务器会为save设置默认条件:

save 900 1
save 300 10
save 60 10000
# 这个表示当满足下列条件 BGSAVE就会被执行
    # 服务器在900秒之内,对数据库进行了至少一次修改
    # 服务器在300秒内,对数据库进行了至少10次修改
    # 服务器在60秒内,对数据库进行了至少10000次修改

接着,服务器程序会根据save选项所设置的保存条件,设置服务器状态redisServer结构的sasveparams属性:

struct redisServer{
    // ...
    // 记录了保存条件的数组
    struct saveparam *saveparams;
    // ...
};

sasveparams属性是一个数组,数组中的每一个元素都是一个sasveparam结构,,每个sasveparam结构都保存了一个save选项设置的保存条件:

struct saveparam{
    // 秒数
    time_t seconds;
    // 修改数
    int change;
};

9.2.2. dirty计数器和lastsave属性

  • dirty 计数器

    记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)

  • lastsave

    一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间

struct redisServer{
    // ...
    // 记录了保存条件的数组
    struct saveparam *saveparams;
    // 修改计数器
    long long dirty;
    // 上一次执行保存的时间
    time_t lashsave;
    // ...
};

当服务器成功执行了一个数据库修改命令之后,程就会对dirty计数器进行更新:命令修改了多少从数据库,dirty计数器的值就增加多少。

9.2.3. 检查保存条件是否满足

Redis 的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,她的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足,就执行BGSAVE命令

瞅瞅伪代码吧:

def serverCron():
	# ...
	# 遍历所有保存条件
	for saveparam in server.saveparams:
		# 计算距离上次执行保存操作有多少秒
		save_initerval = unixtime_now() - server.lastsave;
		# 如果数据库状态的修改次数超过条件所设置的次数
		# 并且距离上次保存的时间超过条件所设置的时间
		# 那么执行保存操作
		if(server.dirty >= saveparam.changes and save_interval > saveparam.seconds):
			BGSAVE();
	# ...

9.3. RDB文件结构

7.redis设计与实现学习笔记-数据库&RDB持久化_第2张图片

  • RDB文件的最开头是REDIS部分,长度5字节,保存着“REDIS”五个字符,用于快速检查所载入的文件是否是RDB文件
  • db_version长度4字节,值是一个字符串表示的整数,这个整数记录了RDB文件的版本号,例如“0006”表示是RDB文件版本为第六版
  • databases包含零个或任意多个数据库,以及数据库中的键值对数据:
    • 如果服务器的数据库状态为空, 那么这个部分也为空,长度0字节
    • 如果服务器的数据库状态为非空(有至少一个数据库非空),那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也不同
  • EOF常量,长度是1字节,这个常量标识这RDB文件正文内容接收,当程序遇到这个值的时候,就表明所有数据库中的键值对都已经载入完毕了。
  • check_sum 是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDIS、db_version、databases、EOF四个部分进行计算得到的。服务器在载入RDB文件时,会将载入数据所计算出的校验和和check_sum所记录的校验和进行对比,以此来检查RDB文件是否有出错或者损坏的情况出现。

9.3.1. databases 部分

databases保存着任意多个非空数据库状态。databases其中的每个非空数据库都可以保存为SELECTDB、db——number、key_value_pairs三个部分。

7.redis设计与实现学习笔记-数据库&RDB持久化_第3张图片

  • SELECTDB常量的长度是1字节,当读入程序遇到这个值时候,它知道接下来要读入的是一个数据库号码
  • db_number保存着一个数据库好嘛,根据数据库的号码的大小不同,这个部分的长度可以是1字节、2字节、5字节,当程序读入db_number 部分只有,服务器会调用SELECT 命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。
  • key_value_pairs部分保存着数据库中的所有键值对数据,如果键值对中带有过期时间,那么过期时间回合键值对保存在一起。根据键值对的数量、类型、内容以及是否有过期时间等条件的不同,key_value_pairs部分的长度也不同

9.3.2. key_value_pairs 部分

RDB中的每个key_value_pairs 部分都保存着一个或以上数量的键值对,以及带有过期时间的键值对的过期时间。

  1. 不带过期时间的键值对在RDB文件中有TYPEkeyvalue三部分组成

7.redis设计与实现学习笔记-数据库&RDB持久化_第4张图片

TYPE常量代表一种对象类型或者底层编码,当服务器读入RDB文件中的键值对数据是,程序或根据TYPE的值来决定如何读入和解释value的数据`

keyvalue分别存着键值对的键对象和值对象:

  • key始终是一个字符串对象,编码方式和REDIS_RDB_TYPE_STRING类型一样,长度由内容而改变

  • 根据TYPE类型的不同,以及保存内容的长度的不同,保存value的结构和长度也会有所不同。

  1. 带过期时间的键值对在RDB文件中的结构

    7.redis设计与实现学习笔记-数据库&RDB持久化_第5张图片

    TYPEkeyvalue和上面一样,不同的是EXPIRETIME_MSms 属性:

    • EXPIRETIME_MS常量的长度为1字节,它告知读入程序,接下来读入的是一个以毫秒为单位的过期时间
    • ms是一个8字节长的带符号整数,记录着一个以毫秒为单位的UNIX时间戳,这个时间戳就是键值对的过期时间

9.3.3. value的编码

value保存着一个值对象,每个值对象的类型都由与之对应的TYPE记录,根据类型的不同,长度也在改变。

  1. 字符串对象

    TYPE的值为 REDIS_RDB_TYPE_STRING,那么value保存的值对象就是一个字符串对象,字符串对象的编码可以是REDIS_ENCODING_INT或者REDIS_ENCODING_RAW.

    如果字符串对象的编码为REDIS_ENCODING_INT,那么说明对象中保存的是长度不超过32位的整数,由下面的结构保存。其中ENCODING的值可以是REDIS_RDB_ENC_INTREDIS_RDB_ENC_INT16或者REDIS_RDB_ENC_INT32三个常量中其中一个,他们分别表示RDB文件使用8位、16位或者32位来保存整数值integer。

    在这里插入图片描述

    如果字符串对象的编码是REDIS_ENCODING_RAW,那么说明对象所保存的是一个字符串值,根据字符串的长度的不同,有压缩和不压缩两种方法来保存这个字符串。

    • 如果字符串的长度小于等于20字节,这个字符串原样保存
    • 如果字符串的长度大于20字节,那么这个字符串会被压缩之后再保存

    注:前提是服务器打开了RDB文件压缩功能的情况下才进行,如果没开,那就是全是无压缩。

    没压缩的字符串的存放方式如下图:

    7.redis设计与实现学习笔记-数据库&RDB持久化_第6张图片

    string 保存的是字符串值本身,len保存字符串长度。

    压缩了的字符串的存放方式如下图:

    在这里插入图片描述

    • REDIS_RDB_ENC_LZF常量标志着字符串已经被LZF算法压缩过了,读入程序碰到这个常量时,会根据之后的compressed_lenorigin_lencompress_string三部分吗,对字符串进行解压缩:其中compressed_len记录的是字符串被压缩后的长度,origin_len记录的是字符串原长度,compressed_string记录的是被压缩之后的字符串。
  2. 列表对象(TYPE = REDIS_RDB_TYPE_LIST 保存的是REDIS_ENCODING_LINKEDLIST编码)

    7.redis设计与实现学习笔记-数据库&RDB持久化_第7张图片

  3. 集合对象(TYPE = REDIS_RDB_TYPE_SET 保存的是REDIS_ENCODING_HT编码)

    7.redis设计与实现学习笔记-数据库&RDB持久化_第8张图片

  4. 哈希表对象(TYPE = REDIS_RDB_TYPE_HASH 保存的是REDIS_ENCODING_HT编码)

    7.redis设计与实现学习笔记-数据库&RDB持久化_第9张图片

  5. 有序集合(TYPE = REDIS_RDB_TYPE_ZSET 保存的是REDIS_ENCODING_SKIPLIST编码)

    如果 TYPE 的值为 REDIS_RDB_TYPE_ZSET , 那么 value 保存的就是一个 REDIS_ENCODING_SKIPLIST 编码的有序集合对象, RDB 文件保存这种对象的结构如图 IMAGE_SKIPLIST_ZSET 所示。

    7.redis设计与实现学习笔记-数据库&RDB持久化_第10张图片

    sorted_set_size 记录了有序集合的大小, 也即是这个有序集合保存了多少元素, 读入程序需要根据这个值来决定应该读入多少有序集合元素。

    element 开头的部分代表有序集合中的元素, 每个元素又分为成员(member)和分值(score)两部分, 成员是一个字符串对象, 分值则是一个 double 类型的浮点数, 程序在保存 RDB 文件时会先将分值转换成字符串对象, 然后再用保存字符串对象的方法将分值保存起来。

    有序集合中的每个元素都以成员紧挨着分值的方式排列, 如图 IMAGE_MEMBER_AND_SCORE_OF_ZSET 所示。

    7.redis设计与实现学习笔记-数据库&RDB持久化_第11张图片

    因此, 从更详细的角度看, 图 IMAGE_SKIPLIST_ZSET 所展示的结构可以进一步修改为图 IMAGE_DETIAL_SKIPLIST_ZSET 。

    7.redis设计与实现学习笔记-数据库&RDB持久化_第12张图片

    作为示例, 图 IMAGE_EXAMPLE_OF_SKIPLIST_ZSET 展示了一个带有两个元素的有序集合。

    7.redis设计与实现学习笔记-数据库&RDB持久化_第13张图片

    在这个示例结构中, 第一个数字 2 记录了有序集合的元素数量, 之后跟着的是两个有序集合元素:

    • 第一个元素的成员是长度为 2 的字符串 "pi" , 分值被转换成字符串之后变成了长度为 4 的字符串 "3.14"
    • 第二个元素的成员是长度为 1 的字符串 "e" , 分值被转换成字符串之后变成了长度为 3 的字符串 "2.7"
  6. INTSET编码的集合(TYPE = REDIS_RDB_TYPE_SET_INTSET 保存的是整数集合对象)

    如果 TYPE 的值为 REDIS_RDB_TYPE_SET_INTSET , 那么 value 保存的就是一个整数集合对象, RDB 文件保存这种对象的方法是, 先将整数集合转换为字符串对象, 然后将这个字符串对象保存到 RDB 文件里面。

    如果程序在读入 RDB 文件的过程中, 碰到由整数集合对象转换成的字符串对象, 那么程序会根据 TYPE 值的指示, 先读入字符串对象, 再将这个字符串对象转换成原来的整数集合对象。

  7. ZIPLIST编码的列表、哈希列表或者有序集合

    如果 TYPE 的值为 REDIS_RDB_TYPE_LIST_ZIPLISTREDIS_RDB_TYPE_HASH_ZIPLIST 或者 REDIS_RDB_TYPE_ZSET_ZIPLIST , 那么 value 保存的就是一个压缩列表对象, RDB 文件保存这种对象的方法是:

    1. 将压缩列表转换成一个字符串对象。
    2. 将转换所得的字符串对象保存到 RDB 文件。

    如果程序在读入 RDB 文件的过程中, 碰到由压缩列表对象转换成的字符串对象, 那么程序会根据 TYPE 值的指示, 执行以下操作:

    1. 读入字符串对象,并将它转换成原来的压缩列表对象。
    2. 根据 TYPE 的值,设置压缩列表对象的类型: 如果 TYPE 的值为 REDIS_RDB_TYPE_LIST_ZIPLIST , 那么压缩列表对象的类型为列表; 如果 TYPE 的值为 REDIS_RDB_TYPE_HASH_ZIPLIST , 那么压缩列表对象的类型为哈希表; 如果 TYPE 的值为 REDIS_RDB_TYPE_ZSET_ZIPLIST , 那么压缩列表对象的类型为有序集合。

    从步骤 2 可以看出, 由于 TYPE 的存在, 即使列表、哈希表和有序集合三种类型都使用压缩列表来保存, RDB 读入程序也总可以将读入并转换之后得出的压缩列表设置成原来的类型。

9.4. 分析RDB文件

介绍od命令:

  • 可以解析Redis服务器产生的RDB文件
  • 该命令用给定的格式转存(dump)并打印输入文件

剩下的百度吧,人工分析RDB文件不是必须的,网上都有很多RDB处理文件 Redis也自带有RDB文件检查工具。

你可能感兴趣的:(redis)