redis

作者: sizuoyi00 | 来源:发表于2022-06-19 18:15 被阅读0次
    类型 底层 应用场景 编码类型
    String sds 帖子、评论、热点数据、输入缓冲 RAW << EMBSTR << INT
    List ziplist quickList 评论列表、商品列表、发布与订阅、慢查询、监视器 LINKEDLIST << ZIPLIST
    Set intset hashtable 适合交集、并集、查集操作,例如朋友关系 HT << INSET
    Zset ziplist skiplist 去重后排序,适合排名场景 SKIPLIST << ZIPLIST
    Hash ziplist hashtable 结构化数据,比如存储对象 HT << ZIPLIST

    sds
    场景:string结构使用
    特点:预分配内存--减少内存的频繁分配
    结构:capacity+lenth+array,类似ArrayList

    struct SDS<T> {
    T capacity; // 数组容量
    T len; // 数组长度
    byte flags; // 特殊标识位,不理睬它
    byte[] content; // 数组内容
    }

    embstr vs raw
    Redis 的字符串有两种存储方式,在长度特别短时,使用 emb 形式存储 (embeded),当长度超过 44 时,使用 raw 形式存储。

    redis对象头(16)+SDS(3)

    //4+4+8=16
    struct RedisObject {
        int4 type; // 4bits
        int4 encoding; // 4bits
        int24 lru; // 24bits
        int32 refcount; // 4bytes
        void *ptr; // 8bytes,64-bit system
    } robj;
    
    //1+1+1=3
    struct SDS {
        int8 capacity; // 1byte
        int8 len; // 1byte
        int8 flags; // 1byte
        byte[] content; // 内联数组,长度为 capacity
    }
    
    

    这两种类型有什么区别呢?为什么分界线是 44 呢?



    embstr 存储形式是这样一种存储形式,它将 RedisObject 对象头和 SDS 对象连续存在一起,使用 malloc 方法一次分配。而 raw 存储形式不一样,它需要两次 malloc,两个对象头在内存地址上一般是不连续的。

    而内存分配器 jemalloc/tcmalloc 等分配内存大小的单位都是 2、4、8、16、32、64 等等,为了能容纳一个完整的 embstr 对象,jemalloc 最少会分配 32 字节的空间,如果字符串再稍微长一点,那就是 64 字节的空间。如果总体超出了 64 字节,Redis 认为它是一个大字符串,不再使用 emdstr 形式存储,而该用 raw 形式。

    当内存分配器分配了 64 空间时,那这个字符串的长度最大可以是多少呢?这个长度就是 44。


    64-16-3-1(以字节\0结尾) = 44

    dict
    场景:hash结构使用
    结构:包含两个hashtable,复制算法,只有一个hashtable使用,扩容时使用另一个
    hashtable结构几乎同hashmap,数组+链表,同样有对应的扩容缩容、渐进式rehash、hash优化等

    struct dictEntry {
    void* key;
    void* val;
    dictEntry* next; // 链接下一个 entry
    }
    struct dictht {
    dictEntry** table; // 二维
    long size; // 第一维数组的长度
    long used; // hash 表中的元素个数
    ...
    }

    ziplist
    场景:zset 和 hash 元素个数少使用,set 集合容纳元素都是整数并且元素个数较小时
    特点:不合适存储大对象,多元素对象。紧凑存储没有冗余空间(对比string),插入新元素需要重新分配内存并拷贝。
    结构:ziplist共用一块连续内存空间,通过ztail_offset快速定位最后一个元素,然后倒序遍历
    hash-ziplist=ziplist<Entry> 通过ziplist.ztail_offset+entry.prevlen倒序实现双链

    struct ziplist<T> {
    int32 zlbytes; // 整个压缩列表占用字节数
    int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    int16 zllength; // 元素个数
    T[] entries; // 元素内容列表,挨个挨个紧凑存储
    int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
    }

    struct entry {
    int<var> prevlen; // 前一个 entry 的字节长度
    int<var> encoding; // 元素类型编码
    optional byte[] content; // 元素内容
    }

    quicklist
    场景:list结构元素多时使用
    特点:考虑到链表linkedlist的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率。后续版本对列表数据结构进行了改造,使用 quicklist 代替了 ziplist 和 linkedlist。
    结构:ziplist+quicklistnode

    单个 ziplist 长度为 8k 字节

    struct quicklistNode {
    quicklistNode* prev;
    quicklistNode* next;
    ziplist* zl; // 指向压缩列表
    int32 size; // ziplist 的字节总数
    int16 count; // ziplist 中的元素数量
    int2 encoding; // 存储形式 2bit,原生字节数组还是 LZF 压缩存储
    ...
    }
    struct quicklist {
    quicklistNode* head;
    quicklistNode* tail;
    long count; // 元素总数
    int nodes; // ziplist 节点的个数
    int compressDepth; // LZF 算法压缩深度
    ...
    }

    skiplist
    使用场景:zset,zset通过skiplist+hash复合结构
    基于链表,增加一个指向后边其他节点的指针(不同层数--通过随机层数算法),先延着高层数据找,找不到再沿着低层数据找,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。


    redis为什么选择了跳跃表而不是红黑树
    1.跳表操作时间复杂度和红黑树相同
    2.跳表代码实现更简单易读(平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速)
    3.跳表区间查找效率更高(在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。)

    跳跃表多少层
    1-32,Level[0] 有 264 个元素时

    http://zhangtielei.com/posts/blog-redis-skiplist.html

    相关文章

      网友评论

          本文标题:redis

          本文链接:https://www.haomeiwen.com/subject/gaqiyrtx.html