《Redis设计与实现》读书笔记——数据结构与对象

1、数据结构

    (1)简单动态字符串(SDS)

struct sdshrd{
    // 记录buff数组中已使用字节的数量
    int len;

    // 记录未使用字节的数量
    int free;

    // 字节数组,用于保存字符串
    char buf[];
}

    与C字符串相比,可以O(1)获取长度、杜绝缓冲区溢出、减少修改字符串时的内存重分配次数、二进制安全、兼容部分C字符串函数;

    (2)链表

    用双向链表实现,维护头尾节点和节点数,可以保存不同类型的值;

    (3)字典

    Redis的数据库就是使用字典作为底层实现的。和HashMap类似,大小是2的幂次,负载因子≥1时扩容,≤0.1时收缩,如果正在执行BGSAVE则负载因子≥5时扩容,用分离链接法解决散列冲突,维护两张散列表,用于渐进式rehash。

    渐进式rehash:维护int trehashidx,未rehash时为-1,为正数时表示正在rehash。为0时开始rehash,维护旧表和新表,add进新表,查找、修改、删除时从两个表里查询key,并将entry从旧表移入新表,trehashidx++,直至旧表为空,rehash完成,trehashidx = - 1;

    (4)跳跃表

    Redis只在两个地方用到了跳跃表,zset和集群节点中用作内部数据结构。

// 跳跃表节点
typedef struct zskiplistNode{
    // 后退指针
    struct zskiplistNode *backward;

    // 分值
    double score;

    // 成员对象
    robj *obj;

    // 层
    struct zskiplistLevel{

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度,计算rank
        unsigned int span;

    } level[];

} zskiplistNode;
// 跳跃表
typedef struct zskiplist{
    // 表头节点和表尾节点
    structz skiplistNode *header, *tail;

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

    // 表中最大层数
    int level;

} zskiplist;

    每次创建一个新跳跃表节点时,程序根据幂次定律,越大的数出现的概率越小,随机生成一个介于1和32之间的值作为level数组的大小,就是层的高度;分值相同的对象按照成员对象的字典序排序;平均log(N),最坏O(N)查找,类似平衡树,实现简单,方便计算rank;

    (5)整数集合

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

    // 元素数量
    uint32_t length;

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

} intset;

    用数组实现,有序、无重复得保存元素。添加新整数(O(N))时如果长度大于集合内所有整数,升级整数类型(提高位数),不支持降级;

    (6)压缩列表

    为了节约内存,由一系列特殊编码的连续内存块组成的顺序性数据结构,可以包含任意多个entry,每个entry保存一个字节数组或者一个整数;

    过期策略:https://blog.csdn.net/yangtuogege/article/details/77970896

 

2、对象

    Redis中每个对象都由一个redisObject结构表示。

typedef struct redisObject{

    // 类型
    unsigned type:4;

    // 编码,即底层使用了什么数据结构
    unsigned encoding:4;

    // 指向底层数据结构的指针
    void *ptr;

    // 引用计数
    int refcount;

    // 记录了最后一次被访问的时间
    unsigned lru:22;

} robj;

    (1)字符串对象,编码可以是int,raw,embstr

    int保存整数值;embstr保存≤39的字符串,调用一次内存分配函数分配一块连续空间,redisObject和sdshdr连续;raw调用两次内存分配函数,,不连续。embstr是只读的,append会变成raw类型。浮点型也是作为字符串存储的,用raw或embstr编码;

    (2)列表对象,编码是ziplist或linkedlist

    每个节点保存字符串对象,节点小而少时使用ziplist;

    (3)哈希对象,编码是ziplist或hashtable

    用ziplist实现时,添加时将两个节点加入表尾,entry变大或者变多时转化为hashtable;

    (4)集合对象,编码是intset或hashtable

    (5)有序集合对象,编码是ziplist或skiplist

    ziplist内的集合元素按分值从小到大进行排序;skiplist编码的有序集合对象使用zset结构作为底层实现,包含一个字典和跳跃表。跳跃表有序,可以进行范围操作,ZRANK,ZRANGE等;字典用于O(1)查找给定成员的分值,ZSCORE命令,两者通过指针共享成员和分值,不会浪费内存;

 

3、命令

    对任意键执行的命令:DEL,EXPIRE,RENAME,TYPE,OBJECT等;

    对字符串键:SET,GET,APPEND,STRLEN等;

    对哈希键:HDEL,HSET,HGET,HLEN等;

    对列表键:RPUSH,LPOP,LINSERT,LLEN等;

    对集合键:SADD,SPOP(移除随机元素),SINTER(返回交集),SCARD(返回大小)等;

    对有序集合键:ZADD,ZSCARD,ZRANK,ZSCORE,ZREM等;

    类型特定命令的类型检查通过redisObject结构的type属性来实现;

    除了根据值类型,还会根据值编码使用不同的函数来实现命令。举个例子,如果执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN的命令实现;

    Redis初始化服务器时,创建0-9999所有整数值的字符串对象。Redis只对整数值字符串对象进行共享,因为验证整数相等O(1),验证字符串相等O(N);    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值