【Redis5源码学习】浅析redis命令之dump篇

Grape

官方文档

DUMP key
序列化给定 key ,并返回被序列化的值,使用 RESTORE 命令可以将这个值反序列化为 Redis 键。
序列化生成的值有以下几个特点:

  • 它带有 64 位的校验和,用于检测错误, RESTORE 在进行反序列化之前会先检查校验和。
  • 值的编码格式和 RDB 文件保持一致。
  • RDB 版本会被编码在序列化值当中,如果因为 Redis 的版本不同造成 RDB 格式不兼容,那么 Redis 会拒绝对这个值进行反序列化操作。

序列化的值不包括任何生存时间信息。
可用版本:>= 2.6.0
时间复杂度:
查找给定键的复杂度为 O(1) ,对键进行序列化的复杂度为 O(N*M) ,其中 N 是构成 key 的 Redis 对象的数量,而 M 则是这些对象的平均大小。
如果序列化的对象是比较小的字符串,那么复杂度为 O(1) 。
返回值:如果 key 不存在,那么返回 nil。否则,返回序列化之后的值。

redis> SET greeting "hello, dumping world!"
OK
redis> DUMP greeting
"\x00\x15hello, dumping world!\x06\x00E\xa0Z\x82\xd8r\xc1\xde"
redis> DUMP not-exists-key
(nil)

我们可以看到,dump命令就是为了序列化给定的key。那么什么是序列化呢?我们看下序列化的定义:序列化(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。目的是为了对象可以跨平台存储,和进行网络传输。
命令的使用很简单,就是dump key,而dump通常和RESTORE配合使用,序列化与反序列化。如果想更多的了解序列化相关的知识,推荐阅读:序列化理解起来很简单

源码分析

首先我们贴上源码:

/* DUMP keyname
 * DUMP is actually not used by Redis Cluster but it is the obvious
 * complement of RESTORE and can be useful for different applications. */
void dumpCommand(client *c) {
    robj *o, *dumpobj;
    rio payload;
    /* 检查key是否存在 */
    if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
    addReply(c,shared.nullbulk);
    return;
    }
    /*创建dump负载. */
    createDumpPayload(&payload,o);
    /* 传输给客户端 */
    dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
    addReplyBulk(c,dumpobj);
    decrRefCount(dumpobj);
    return;
}

接下来我们慢慢分析。
Dump命令的核心是创建dump负载,所以我们的核心就在于这个过程,首先我们简单描述下大致流程:首先检查我们要序列化的key是否存在,若存在则创建dump负载,然后传输给客户端。

  1. 检查key是否存在,主要通过lookupkeyRead来实现,大致就是通过key去redis DB中检查是否存在,若存在则向下执行,否则向客户端发送信息。通常我们在dump一个不存在的key的时候我们会得到的结果是一个nil。
  2. 创建dump负载,这块是dump命令的核心,具体的实现代码如下:

     void createDumpPayload(rio *payload, robj *o) {
     unsigned char buf[2];
     uint64_t crc;
     /* Serialize the object in a RDB-like format. It consist of an object type
        byte followed by the serialized object. This is understood by RESTORE. 
        以类似于rdb的格式序列化对象。它由对象类型字节和序列化对象组成。
     */
     rioInitWithBuffer(payload,sdsempty());
     /*将给定的对象的类型写到rdb中,失败报错*/
     serverAssert(rdbSaveObjectType(payload,o));
     /*将给定的对象写到rdb中,失败报错*/
     serverAssert(rdbSaveObject(payload,o));
     /* Write the footer, this is how it looks like:
     
        ----------------+---------------------+---------------+
        ... RDB payload | 2 bytes RDB version | 8 bytes CRC64 |
        ----------------+---------------------+---------------+
        RDB version and CRC are both in little endian.
     /* RDB版本,被分为两个字节保存,表示为0-65535 */
     buf[0] = RDB_VERSION & 0xff;
     buf[1] = (RDB_VERSION >> 8) & 0xff;
     /*sdscatlen函数是扩展长度,并追加字符串
     payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,buf,2);
     /* 计算CRC校验码,共8个字节 */
     crc = crc64(0,(unsigned char*)payload->io.buffer.ptr,
                 sdslen(payload->io.buffer.ptr));
     
     /*对于目标机是大端字节序的机器,进行字节码的转换,
     提供了16byte、32byte、64byte字节的转换。
     在intset\ziplist\zipmap三种数据结构中使用,
     使得不同字节序机器生成的rdb文件格式都是统一的(小端字节序),便于兼容。*/
     memrev64ifbe(&crc);
     payload->io.buffer.ptr = sdscatlen(payload->io.buffer.ptr,&crc,8);

    }
    最后生成的序列化对象格式就是下边这个格式:
    +————-+———————+—————+
    | RDB payload | 2 bytes RDB version | 8 bytes CRC64 |
    +————-+———————+———————+

  3. 发送信息到客户端,最后一块我们可以认为是消息传递,把序列好的信息传递给客户端。此块在后边我会写一个专题来介绍。

如果读者有兴趣,一定亲手gdb试一试!!

拓展阅读

    原文作者:NoSay
    原文地址: https://segmentfault.com/a/1190000020441163
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞