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负载,然后传输给客户端。
- 检查key是否存在,主要通过lookupkeyRead来实现,大致就是通过key去redis DB中检查是否存在,若存在则向下执行,否则向客户端发送信息。通常我们在dump一个不存在的key的时候我们会得到的结果是一个nil。
创建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 |
+————-+———————+———————+- 发送信息到客户端,最后一块我们可以认为是消息传递,把序列好的信息传递给客户端。此块在后边我会写一个专题来介绍。
如果读者有兴趣,一定亲手gdb试一试!!