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

Grapehtml


官方文档

DUMP key
序列化给定 key ,并返回被序列化的值,使用 RESTORE 命令能够将这个值反序列化为 Redis 键。
序列化生成的值有如下几个特色: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配合使用,序列化与反序列化。若是想更多的了解序列化相关的知识,推荐阅读:序列化理解起来很简单数据结构

源码分析

首先咱们贴上源码:app

/* 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 |
    +-------------+---------------------+———————+ui

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

若是读者有兴趣,必定亲手gdb试一试!!this

拓展阅读

相关文章
相关标签/搜索