《Redis的设计与实现》读书笔记(1)

2019.05.29

一、数据结构与对象

1.SDS(简单字符串)

struct sdsstr{
    int len;
    int free;
    char buf[];
};//sds.h

小结:

  • Redis只会使用C字符串作为字面量,在大多数情况下,使用SDS作为字符串表示
  • SDS的优点:
    • 常数复杂度获取字符串长度
    • 杜绝缓冲区溢出
    • 减少修改字符串长度是所需的内存重分配次数
    • 二进制安全
    • 兼容部分C字符串

2.list(双向链表)

typedef struct listNode{
    struct listNode *orev;
    struct listNode *next;
    void *value;
}listNode; //adlist.h

typedef struct list{
    listNode *head;
    listNode *tail;
    unsigned long len;
    void* (*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr,void *key);
}list;//adlist.h

20190529224855.png

小结:

  • 链表被广泛用于实现Redis的各种功能,如列表键,发布订阅,慢查询,监视器等
  • Redis的链表实现是双端链表
  • Redis的链表无环
  • 因为value是void*类型的,所以Redis的链表可以用于保存各种不同类型的值

3.dict(字典)

/*
 * 哈希表节点
 */
typedef struct dictEntry {

    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 链往后继节点
    struct dictEntry *next; 

} dictEntry;

/*
 * 哈希表
 */
typedef struct dictht {

    // 哈希表节点指针数组(俗称桶,bucket)
    dictEntry **table;      

    // 指针数组的大小
    unsigned long size;     

    // 指针数组的长度掩码,用于计算索引值
    unsigned long sizemask; 

    // 哈希表现有的节点数量
    unsigned long used;     

} dictht;

/*
 * 字典
 *
 * 每个字典使用两个哈希表,用于实现渐进式 rehash
 */
typedef struct dict {

    // 特定于类型的处理函数
    dictType *type;

    // 类型处理函数的私有数据
    void *privdata;

    // 哈希表(2个)
    dictht ht[2];       

    // 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
    int rehashidx;

    // 当前正在运作的安全迭代器数量
    int iterators;      

} dict;

/*
 * 特定于类型的一簇处理函数
 */
typedef struct dictType {
    // 计算键的哈希值函数, 计算key在hash table中的存储位置,不同的dict可以有不同的hash function.
    unsigned int (*hashFunction)(const void *key);
    // 复制键的函数
    void *(*keyDup)(void *privdata, const void *key);
    // 复制值的函数
    void *(*valDup)(void *privdata, const void *obj);
    // 对比两个键的函数
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    // 键的释构函数
    void (*keyDestructor)(void *privdata, void *key);
    // 值的释构函数
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

20190529225746.png

小结:

  • 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键
  • 使用链地址解决冲突
  • 字典中有两个哈希表(ht[0],ht[1]),一个平常使用(ht[0]),一个rehash时使用(ht[1])
    • 为啥rehash:使负载因子在一个合理的范围内,需要对哈希表的大小进行扩展或收缩
      • 扩展时机:执行BGSAVE或BGREWRITEADF时,负载因子大于5;没有正在执行BGSAVE和BGREWRITEAOF时,负载因子大于1.
      • 收缩时机: 负载因子小于0.1
    • rehash步骤:为ht[1]分配空间(扩展时:2倍,收缩时:大于used的2^n)-> 重新计算哈希值和索引值,放置到ht[1]的指定位置 ->释放ht[0],将ht[1]设置为ht[0],并在ht[1]设置一个空白哈希表
    • rehash是渐进式的,通过$rehashidx$字段记录进度

4.skiplist(跳跃表)

/*
 * 跳跃表节点
 */
typedef struct zskiplistNode {

    // 成员对象
    robj *obj;

    // 分值
    double score;

    // 后退指针
    struct zskiplistNode *backward;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode; //redis.h

/*
 * 跳跃表
 */
typedef struct zskiplist {

    // 表头节点和表尾节点
    struct zskiplistNode *header, *tail;

    // 表中节点的数量
    unsigned long length;

    // 表中层数最大的节点的层数
    int level;

} zskiplist;//redis.h

// t_zset.c 跳跃表的增删
/*
 * 创建一个层数为 level 的跳跃表节点,
 * 并将节点的成员对象设置为 obj ,分值设置为 score 。
 *
 * 返回值为新创建的跳跃表节点
 *
 * T = O(1)
 */
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    
    // 分配空间
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));

    // 设置属性
    zn->score = score;
    zn->obj = obj;

    return zn;
}

/*
 * 创建并返回一个新的跳跃表
 *
 * T = O(1)
 */
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    // 分配空间
    zsl = zmalloc(sizeof(*zsl));

    // 设置高度和起始层数
    zsl->level = 1;
    zsl->length = 0;

    // 初始化表头节点
    // T = O(1)
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;

    // 设置表尾
    zsl->tail = NULL;

    return zsl;
} 

/*
 * 释放给定的跳跃表节点
 *
 * T = O(1)
 */
void zslFreeNode(zskiplistNode *node) {

    decrRefCount(node->obj);

    zfree(node);
} 

/*
 * 释放给定跳跃表,以及表中的所有节点
 *
 * T = O(N)
 */
void zslFree(zskiplist *zsl) {

    zskiplistNode *node = zsl->header->level[0].forward, *next;

    // 释放表头
    zfree(zsl->header);

    // 释放表中所有节点
    // T = O(N)
    while(node) {

        next = node->level[0].forward;

        zslFreeNode(node);

        node = next;
    }
    
    // 释放跳跃表结构
    zfree(zsl);
}


/*
 * 创建一个成员为 obj ,分值为 score 的新节点,
 * 并将这个新节点插入到跳跃表 zsl 中。
 * 
 * 函数的返回值为新节点。
 *
 * T_wrost = O(N^2), T_avg = O(N log N)
 */
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    redisAssert(!isnan(score));

    // 在各个层查找节点的插入位置
    // T_wrost = O(N^2), T_avg = O(N log N)
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {

        /* store rank that is crossed to reach the insert position */
        // 如果 i 不是 zsl->level-1 层
        // 那么 i 层的起始 rank 值为 i+1 层的 rank 值
        // 各个层的 rank 值一层层累积
        // 最终 rank[0] 的值加一就是新节点的前置节点的排位
        // rank[0] 会在后面成为计算 span 值和 rank 值的基础
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];

        // 沿着前进指针遍历跳跃表
        // T_wrost = O(N^2), T_avg = O(N log N)
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                // 比对分值
                (x->level[i].forward->score == score &&
                // 比对成员, T = O(N)
                compareStringObjects(x->level[i].forward->obj,obj) < 0))) {

            // 记录沿途跨越了多少个节点
            rank[i] += x->level[i].span;

            // 移动至下一指针
            x = x->level[i].forward;
        }
        // 记录将要和新节点相连接的节点
        update[i] = x;
    }

    /* we assume the key is not already inside, since we allow duplicated
     * scores, and the re-insertion of score and redis object should never
     * happen since the caller of zslInsert() should test in the hash table
     * if the element is already inside or not. 
     *
     * zslInsert() 的调用者会确保同分值且同成员的元素不会出现,
     * 所以这里不需要进一步进行检查,可以直接创建新元素。
     */

    // 获取一个随机值作为新节点的层数
    // T = O(N)
    level = zslRandomLevel();

    // 如果新节点的层数比表中其他节点的层数都要大
    // 那么初始化表头节点中未使用的层,并将它们记录到 update 数组中
    // 将来也指向新节点
    if (level > zsl->level) {

        // 初始化未使用层
        // T = O(1)
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            update[i] = zsl->header;
            update[i]->level[i].span = zsl->length;
        }

        // 更新表中节点最大层数
        zsl->level = level;
    }

    // 创建新节点
    x = zslCreateNode(level,score,obj);

    // 将前面记录的指针指向新节点,并做相应的设置
    // T = O(1)
    for (i = 0; i < level; i++) {
        
        // 设置新节点的 forward 指针
        x->level[i].forward = update[i]->level[i].forward;
        
        // 将沿途记录的各个节点的 forward 指针指向新节点
        update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here */
        // 计算新节点跨越的节点数量
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);

        // 更新新节点插入之后,沿途节点的 span 值
        // 其中的 +1 计算的是新节点
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
    // 未接触的节点的 span 值也需要增一,这些节点直接从表头指向新节点
    // T = O(1)
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }

    // 设置新节点的后退指针
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;

    // 跳跃表的节点计数增一
    zsl->length++;

    return x;
}

/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank 
 * 
 * 内部删除函数,
 * 被 zslDelete 、 zslDeleteRangeByScore 和 zslDeleteByRank 等函数调用。
 *
 * T = O(1)
 */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;

    // 更新所有和被删除节点 x 有关的节点的指针,解除它们之间的关系
    // T = O(1)
    for (i = 0; i < zsl->level; i++) {
        if (update[i]->level[i].forward == x) {
            update[i]->level[i].span += x->level[i].span - 1;
            update[i]->level[i].forward = x->level[i].forward;
        } else {
            update[i]->level[i].span -= 1;
        }
    }

    // 更新被删除节点 x 的前进和后退指针
    if (x->level[0].forward) {
        x->level[0].forward->backward = x->backward;
    } else {
        zsl->tail = x->backward;
    }

    // 更新跳跃表最大层数(只在被删除节点是跳跃表中最高的节点时才执行)
    // T = O(1)
    while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
        zsl->level--;

    // 跳跃表节点计数器减一
    zsl->length--;
}

/* Delete an element with matching score/object from the skiplist. 
 *
 * 从跳跃表 zsl 中删除包含给定节点 score 并且带有指定对象 obj 的节点。
 *
 * T_wrost = O(N^2), T_avg = O(N log N)
 */
int zslDelete(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    // 遍历跳跃表,查找目标节点,并记录所有沿途节点
    // T_wrost = O(N^2), T_avg = O(N log N)
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {

        // 遍历跳跃表的复杂度为 T_wrost = O(N), T_avg = O(log N)
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                // 比对分值
                (x->level[i].forward->score == score &&
                // 比对对象,T = O(N)
                compareStringObjects(x->level[i].forward->obj,obj) < 0)))

            // 沿着前进指针移动
            x = x->level[i].forward;

        // 记录沿途节点
        update[i] = x;
    }

    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. 
     *
     * 检查找到的元素 x ,只有在它的分值和对象都相同时,才将它删除。
     */
    x = x->level[0].forward;
    if (x && score == x->score && equalStringObjects(x->obj,obj)) {
        // T = O(1)
        zslDeleteNode(zsl, x, update);
        // T = O(1)
        zslFreeNode(x);
        return 1;
    } else {
        return 0; /* not found */
    }

    return 0; /* not found */
}

20190530172928.png

小结:

  • 跳跃表是zset的底层实现之一
  • 每个跳跃表节点的层高都是1~32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序

5.intset(整数集合)

typedef struct intset {
    
    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

20190530174545.png 20190530175946.png

小结:

  • 整数集合是集合键的底层实现之一,contents数组按值从小到大排序,并且数组中不包含任何重复项
  • 升级操作
    • 什么时候升级:当添加新元素到intset中时,新元素的类型比所有现有元素的类型要长时
    • 升级步骤:
      • 根据新元素的类型,扩展整数集合底层数组的空间大小(在原数组的基础上),并为新元素分配中间
      • 将contents数组现有的所有元素转换成与新元素相同的类型,并将类型转换后的元素放在正确的位置上,维持数组的有序性质不变(先放置排名大的)
      • 将新元素添加到底层数组(新元素要么大于所有元素(插到末尾),要么小于所有元素(吵到开头))
    • 升级的好处:带来的操作上的灵活性,尽可能节约内存
  • 整数集合只支持升级操作,不支持降级操作

6.ziplist(压缩列表)

/* The ziplist is a specially encoded dually linked list that is designed
 * to be very memory efficient. 
 *
 * Ziplist 是为了尽可能地节约内存而设计的特殊编码双端链表。
 *
 * It stores both strings and integer values,
 * where integers are encoded as actual integers instead of a series of
 * characters. 
 *
 * Ziplist 可以储存字符串值和整数值,
 * 其中,整数值被保存为实际的整数,而不是字符数组。
 *
 * It allows push and pop operations on either side of the list
 * in O(1) time. However, because every operation requires a reallocation of
 * the memory used by the ziplist, the actual complexity is related to the
 * amount of memory used by the ziplist.
 *
 * Ziplist 允许在列表的两端进行 O(1) 复杂度的 push 和 pop 操作。
 * 但是,因为这些操作都需要对整个 ziplist 进行内存重分配,
 * 所以实际的复杂度和 ziplist 占用的内存大小有关。
 */
 /*
 * ziplist 属性宏
 */
// 定位到 ziplist 的 bytes 属性,该属性记录了整个 ziplist 所占用的内存字节数
// 用于取出 bytes 属性的现有值,或者为 bytes 属性赋予新值
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
// 定位到 ziplist 的 offset 属性,该属性记录了到达表尾节点的偏移量
// 用于取出 offset 属性的现有值,或者为 offset 属性赋予新值
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
// 定位到 ziplist 的 length 属性,该属性记录了 ziplist 包含的节点数量
// 用于取出 length 属性的现有值,或者为 length 属性赋予新值
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
// 返回 ziplist 表头的大小
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
// 返回指向 ziplist 第一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
// 返回指向 ziplist 最后一个节点(的起始位置)的指针
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
// 返回指向 ziplist 末端 ZIP_END (的起始位置)的指针
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

/* 
空白 ziplist 示例图
area        |<---- ziplist header ---->|<-- end -->|
size          4 bytes   4 bytes 2 bytes  1 byte
            +---------+--------+-------+-----------+
component   | zlbytes | zltail | zllen | zlend     |
            |         |        |       |           |
value       |  1011   |  1010  |   0   | 1111 1111 |
            +---------+--------+-------+-----------+
                                       ^
                                       |
                               ZIPLIST_ENTRY_HEAD
                                       &
address                        ZIPLIST_ENTRY_TAIL
                                       &
                               ZIPLIST_ENTRY_END
非空 ziplist 示例图
area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|
size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte
            +---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |
            +---------+--------+-------+--------+--------+--------+--------+-------+
                                       ^                          ^        ^
address                                |                          |        |
                                ZIPLIST_ENTRY_HEAD                |   ZIPLIST_ENTRY_END
                                                                  |
                                                        ZIPLIST_ENTRY_TAIL
*/
/*
 * 保存 ziplist 节点信息的结构
 */
typedef struct zlentry {

    // prevrawlen :前置节点的长度
    // prevrawlensize :编码 prevrawlen 所需的字节大小
    unsigned int prevrawlensize, prevrawlen;

    // len :当前节点值的长度
    // lensize :编码 len 所需的字节大小
    unsigned int lensize, len;

    // 当前节点 header 的大小
    // 等于 prevrawlensize + lensize
    unsigned int headersize;

    // 当前节点值所使用的编码类型
    unsigned char encoding;

    // 指向当前节点的指针
    unsigned char *p;

} zlentry;

20190530181754.png 20190530182510.png

小结:

  • 压缩列表作为一种节约内存而开发的顺序型数据结构,被用作列表键和哈希键的底层实现
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值
  • 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作
    • 场景:在一个ziplist中,有多个连续的,长度介于250-253字节的节点e1至eN,因为e1至eN的所有节点的长度都小于254字节,所以记录这些节点长度只需1字节的previous_entry_length属性,即e1至eN的所有节点的previous_entry_length属性都是1字节长的。当插入一个长度大于等于254字节的新节点到list头,e1的previous_entry_length需要扩展至5字节,以保存new节点的长度,因此e1节点的长度也大于254字节,e2的previous_entry_length也需要扩展。。。直到eN节点
    • 连锁更新的最坏时间复杂度为$O(N^2)$
    • 尽管连锁更新的复杂度较高,但它真正造成性能问题的几率是很低的,push,Insert等操作的平均时间复杂度是$O(N)$

7.对象

typedef struct redisObject {

    // 类型
    /*
     * 位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体* (联合体)称为位段结构。
     */
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向实际值的指针
    void *ptr;

} robj;

/* Object types */
// 对象类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// 对象编码
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

type命令:查看数据库键对象类型

object encoding:查看数据库键的值对象编码

20190530193615.png

7.1 字符串对象

字符串对象的编码可以是int,raw或者embstr 使用embstr编码的好处:

  • embstr编码创建字符串对象所需的内存分配次数从raw编码的两次降为一次
  • 释放符串对象所需的内存分配次数从raw编码的两次降为一次
  • embstr编码的字符串对象的所有数据都保存在一块连续的内存里面 20190530195111.png 20190530194946.png 20190530195049.png

编码转换: int->raw:使保存的整数值变为字符串值时

embstr->raw:Redis没有为embster编码的字符串编写相应的修改函数,embstr是只读的,当对embstr编码的对象执行修改命令时,程序会先将embstr转换为raw,再修改

7.2 列表对象

列表对象的编码可以是ziplist或者linkedlist 20190530194902.png 20190530194916.png

编码转换: 当列表对象可以同时满足一下两个条件时,列表对象使用ziplist编码:

  • 列表对象保存的所有字符串元素的长度都小于64字节
  • 列表对象保存的元素数量小于512个

以上两个条件的上限值是可以修改的,具体可参看配置文件中的list-max-ziplist-value选项和list-max-ziplist-entries选项

7.3 哈希对象

哈希对象的编码可以是ziplist或者hashtable。

20190530194545.png 20190530194558.png

编码转换: 当哈希对象可以同时满足一下两个条件时,哈希对象使用ziplist编码:

  • 哈希对象保存的所有字符串元素的长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

以上两个条件的上限值是可以修改的,具体可参看配置文件中的hash-max-ziplist-value选项和hash-max-ziplist-entries选项

7.4 集合对象

集合对象的编码可以是intset或者hashtable 20190530195911.png

编码转换: 当集合对象可以同时满足一下两个条件时,集合对象使用intset编码:

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量小于512个

7.5 有序集合对象

有序集合的编码可以是ziplist或者skiplist 20190530200321.png

/*
 * 有序集合
 */
typedef struct zset {

    // 字典,键为成员,值为分值
    // 用于支持 O(1) 复杂度的按成员取分值操作
    dict *dict;

    // 跳跃表,按分值排序成员
    // 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
    // 以及范围操作
    zskiplist *zsl;

} zset;

20190530200353.png 20190530200453.png

编码转换: 当有序集合对象可以同时满足一下两个条件时,集合对象使用ziplist编码:

  • 有序集合对象保存的所有元素成员的长度都小于64字节
  • 有序集合对象保存的元素数量小于128个

7.6 类型的检查和多态

Redis用于操作键的命令基本可以分为两种类型。其中一种可以对任何类型的键执行,如$DEL$命令,$EXPIRE$命令,$RENAME$命令,$TYPE$命令,$OBJECT$命令等。另一种智能对特定类型的键造作,如$SET$,$GET$只能对字符串键执行,$HDEL$,$HSET$命令只能对哈希键执行。

7.6.1 类型检查的实现

根据redisObject结构的type属性实现。

7.6.2多态命令的实现

根据redisObject结构的encoding来选择正确的命令代码执行命令。

7.7内存回收

通过redisObject的refcount实现引用计数。

  • 当创建一个新对象时,引用计数的值会被初始化为1
  • 对象被一个新程序使用时,引用计数加1
  • 对象不再一个程序使用时,引用计数减1
  • 引用计数为0时,对象所占用的内存被释放

7.8对象共享

对象的引用计数还有对象共享的属性。

在Redis中让多个键对象共享一个值对象的步骤:

  • 将数据库键的值指针指向一个现有的值对象
  • 将被共享的值对象的引用计数加1 20190530201957.png

object refcount命令:查看值对象引用计数

redis共享0-9999的字符串对象,创建字符串共享对象的数量可以通过redis.h/REDIS_SHARED_INTEGERS常量修改

为什么Redis不共享包含字符串的对象 20190530202411.png

7.9对象的空转时长

redisObject结构的lru属性记录了对象最后一次被程序访问的时间

object idletime命令:可以打印给定键的空转时长,是根据当前时间减去lru得出的

发表评论