对象共享

除了用于实现引用计数内存回收机制之外, 对象的引用计数属性还带有对象共享的作用。

举个例子, 假设键 A 创建了一个包含整数值 100 的字符串对象作为值对象, 如图 8-20 所示。

digraph {

    label = "\n 图 8-20    未被共享的字符串对象";

    rankdir = LR;

    key_a [label = "键 A", shape = box, width = 1.5];

    redisObject [label = " <head> redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | refcount \n 1 | ... ", shape = record];

    node [shape = plaintext];

    number [label = "100"]

    redisObject:ptr -> number;

    key_a -> redisObject:head;

}

如果这时键 B 也要创建一个同样保存了整数值 100 的字符串对象作为值对象, 那么服务器有以下两种做法:

  1. 为键 B 新创建一个包含整数值 100 的字符串对象;

  2. 让键 A 和键 B 共享同一个字符串对象;

以上两种方法很明显是第二种方法更节约内存。

在 Redis 中, 让多个键共享同一个值对象需要执行以下两个步骤:

  1. 将数据库键的值指针指向一个现有的值对象;

  2. 将被共享的值对象的引用计数增一。

举个例子, 图 8-21 就展示了包含整数值 100 的字符串对象同时被键 A 和键 B 共享之后的样子, 可以看到, 除了对象的引用计数从之前的 1 变成了 2 之外, 其他属性都没有变化。

digraph {

    label = "\n 图 8-21    被共享的字符串对象";

    rankdir = LR;

    key_a [label = "键 A", shape = box, width = 1.5];
    key_b [label = "键 B", shape = box, width = 1.5];

    redisObject [label = " <head> redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | refcount \n 2 | ... ", shape = record];

    node [shape = plaintext];

    number [label = "100"]

    redisObject:ptr -> number;

    key_a -> redisObject:head;
    key_b -> redisObject:head;

}

共享对象机制对于节约内存非常有帮助, 数据库中保存的相同值对象越多, 对象共享机制就能节约越多的内存。

比如说, 假设数据库中保存了整数值 100 的键不只有键 A 和键 B 两个, 而是有一百个, 那么服务器只需要用一个字符串对象的内存就可以保存原本需要使用一百个字符串对象的内存才能保存的数据。

目前来说, Redis 会在初始化服务器时, 创建一万个字符串对象, 这些对象包含了从 09999 的所有整数值, 当服务器需要用到值为 09999 的字符串对象时, 服务器就会使用这些共享对象, 而不是新创建对象。

举个例子, 如果我们创建一个值为 100 的键 A , 并使用 OBJECT REFCOUNT 命令查看键 A 的值对象的引用计数, 我们会发现值对象的引用计数为 2

redis> SET A 100
OK

redis> OBJECT REFCOUNT A
(integer) 2

引用这个值对象的两个程序分别是持有这个值对象的服务器程序, 以及共享这个值对象的键 A , 如图 8-22 所示。

digraph {

    label = "\n 图 8-22    引用数为 2 的共享对象";

    rankdir = LR;

    server [label = "服务器程序", shape = box, width = 1.5];
    key_a [label = "键 A", shape = box, width = 1.5];

    redisObject [label = " <head> redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | refcount \n 2 | ... ", shape = record];

    node [shape = plaintext];

    number [label = "100"]

    redisObject:ptr -> number;

    server -> redisObject:head;
    key_a -> redisObject:head;

}

如果这时我们再创建一个值为 100 的键 B , 那么键 B 也会指向包含整数值 100 的共享对象, 使得共享对象的引用计数值变为 3

redis> SET B 100
OK

redis> OBJECT REFCOUNT A
(integer) 3

redis> OBJECT REFCOUNT B
(integer) 3

图 8-23 展示了共享值对象的三个程序。

digraph {

    label = "\n 图 8-23    引用数为 3 的共享对象";

    rankdir = LR;

    server [label = "服务器程序", shape = box, width = 1.5];
    key_a [label = "键 A", shape = box, width = 1.5];
    key_b [label = "键 B", shape = box, width = 1.5];

    redisObject [label = " <head> redisObject | type \n REDIS_STRING | encoding \n REDIS_ENCODING_INT | <ptr> ptr | refcount \n 3 | ... ", shape = record];

    node [shape = plaintext];

    number [label = "100"]

    redisObject:ptr -> number;

    key_a -> redisObject:head;
    key_b -> redisObject:head;
    server -> redisObject:head;

}

另外, 这些共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(linkedlist 编码的列表对象、 hashtable 编码的哈希对象、 hashtable 编码的集合对象、以及 zset 编码的有序集合对象)都可以使用这些共享对象。