美文网首页
Redis底层原理学习笔记

Redis底层原理学习笔记

作者: Liuzz25 | 来源:发表于2020-04-30 17:59 被阅读0次

    说明:本学习笔记为学习https://www.cnblogs.com/ysocean/的Redis详解系列教程后,总结的一些自认为重点的知识点。

    PS:最近公司想招JAVA工程师,被抓去面试别人,搜了一些面试题,发现自己很多知识点也掌握的不牢固,所以准备补补课。目前还没学完,后续会断断续续更新。


    1、Redis五大数据类型

    (1)string

    string 类型是二进制安全的,可以包含任何数据,比如图片或者序列化的对象。
    一个 redis 中字符串 value 最多可以是 512M。

    (2)hash

    hash 是一个键值对集合,是一个 string 类型的 key和 value 的映射表。
    value是一个键值对(key-value)

    (3)list

    list 列表,它是简单的字符串列表,底层实际上是个链表。
    特点是:有序、可以重复。

    (4)set

    set 是 string 类型的无序集合。
    特点是:无序、不可以重复。

    (5)zset

    zset是string 类型的有序集合。
    特点是:有序、不可以重复。

    2、Redis六大底层数据结构

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

    (2)链表

    (3)字典

    (4)跳跃表

    (5)整数集合

    (6)压缩列表

    3、Redis的五大数据类型实现原理

    (1)对象的类型与编码

    Redis使用前面说的五大数据类型来表示键和值,每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象,而Redis中的每个对象都是由 redisObject 结构来表示:

    typedef struct redisObject{
         //类型
         unsigned type:4;
         //编码
         unsigned encoding:4;
         //指向底层数据结构的指针
         void *ptr;
         //引用计数
         int refcount;
         //记录最后一次被程序访问的时间
         unsigned lru:22;
    }
    
    ① type属性

    对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:


    type属性
    ② *prt 指针

    对象的*prt 指针指向对象底层的数据结构。


    *prt 指针
    ③ encoding 属性

    数据结构由 encoding 属性来决定,每种类型的对象都至少使用了两种不同的编码:


    encoding 属性

    (2)string

    字符串对象的编码可以是int,raw或者embstr。
    1、int 编码:保存的是可以用 long 类型表示的整数值。
    2、raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
    3、embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。


    raw和embstr编码的区别

    embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。

    ps:Redis中对于浮点数类型也是作为字符串保存的,在需要的时候再将其转换成浮点数类型。

    (3)list

    列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)。


    ziplist和linkedlist编码的区别

    当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
      1、列表保存元素个数小于512个
      2、每个元素长度小于64字节
    不能满足这两个条件的时候使用 linkedlist 编码。
    ps:上面两个条件可以在redis.conf 配置文件中的 list-max-ziplist-value选项和 list-max-ziplist-entries 选项进行配置。

    (4)hash

    哈希对象的编码可以是ziplist(压缩列表)或者hashtable(哈希表)。


    ziplist和hashtable编码的区别

    和上面列表对象使用 ziplist 编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
      1、列表保存元素个数小于512个
      2、每个元素长度小于64字节
    不能满足这两个条件的时候使用 hashtable 编码。
    ps:第一个条件可以通过配置文件中的 set-max-intset-entries 进行修改。

    (5)set

    集合对象的编码可以是 intset 或者 hashtable。
    intset 编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合中。
    hashtable 编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,这里的每个字符串对象就是一个集合中的元素,而字典的值则全部设置为null。


    intset和hashtable编码的区别

    当集合同时满足以下两个条件时,使用 intset 编码:
      1、集合对象中所有元素都是整数
      2、集合对象所有元素数量不超过512
    不能满足这两个条件的就使用 hashtable 编码。
    ps:第二个条件可以通过配置文件的 set-max-intset-entries 进行配置。

    (6)zset

    有序集合的编码可以是 ziplist 或者 skiplist,为每个元素设置一个分数(score)作为排序依据。
    ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列。


    ziplist编码

    skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:

    typedef struct zset{
         //跳跃表
         zskiplist *zsl;
         //字典
         dict *dice;
    } zset;
    
    skiplist编码

    这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。
    当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
      1、保存的元素数量小于128;
      2、保存的所有元素长度都小于64字节。
    不能满足上面两个条件的使用 skiplist 编码。
    ps:以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改。

    4、内存回收和内存共享

    (1)内存回收

    Redis的内存回收基于redisObject 结构中的 refcount 属性实现,具体变化如下:
      1、创建一个新对象,属性 refcount 初始化为1
      2、对象被一个新程序使用,属性 refcount 加 1
      3、对象不再被一个程序使用,属性 refcount 减 1
      4、当对象的引用计数值变为 0 时,对象所占用的内存就会被释放。


    Redis计数API

    在redis.conf 配置文件中,在 MEMORY MANAGEMENT下有个 maxmemory-policy 配置,当内存使用达到最大值时,redis使用的清除策略。有以下几种可以选择:
        1)volatile-lru 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used) 。
        2)allkeys-lru 利用LRU算法移除任何key 。
        3)volatile-random 移除设置过过期时间的随机key 。
        4)allkeys-random 移除随机key。
        5)volatile-ttl 移除即将过期的key(minor TTL) 。
        6)noeviction noeviction 不移除任何key,只是返回一个写错误 ,默认选项。
    通过这种配置,也可以对内存进行回收。

    (2)内存共享

    refcount 属性除了能实现内存回收以外,还能用于内存共享。
    如通过如下命令set k1 100,创建一个键为k1,值为100的字符串对象,接着通过如下命令set k2 100 ,创建一个键为k2,值为100 的字符串对象,Redis会将数据库键的值指针指向一个现有值的对象,将被共享的值对象引用refcount加1。


    内存共享

    ps:Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。

    虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。

    相关文章

      网友评论

          本文标题:Redis底层原理学习笔记

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