• Redis的基础类型(三)


    List列表

    存储类型

    存储有序的字符串(从左到右),元素可以重复。最大存储数量2^32-1(40亿左右)。

    操作命令

    lpush queue a
    lpush queue b c
    lpush queue d e
    lpop queue
    lpop queue
    lindex queue 0
    lrange queue 0 -1
    

    存储原理

    在早期的版本中,数据量较小时用ziplist存储(特殊编码的双向链表),达到临界值时转化为linkedList进行存储,分别对应OBJ_ENCODING_ZIPLIST和OBJ_ENCODING_LINKEDLIST。

    3.2版本之后,统一使用quickList来存储。quickList存储了一个双向链表,每个节点都是一个zipList,所以是zipList和LinkedList的结合体。

    object encoding queue
    

    quickList

    总体结构:

    quciklist.h 105行

    typedef struct quicklist{
    
          quicklistNode *head; /* 指向双向列表的表头 */
          quciklistNode *tail;/* 指向双向列表的表尾 */
          unsigned long count;/* 所有的ziplist中一共存了多少个元素 */
          unsigned long len; /* 双向链表的长度,对应list-compress-depth */
          int fill:QL_FILL_BITS;/* ziplit最大大小,对应list-max-ziplist-depth */
          unsigned int compress:QL_COMP_BIT;/* 压缩深度,对应list-compress-depth */
          unsigned int bookmark_count:QL_BM_BITS;/* 4位,bookmarks数组的大小 */
          quciklistBookmark booksmarks[];/* bookmarks是一个可选字段。quciklist重新分配内存空间时使用,不使用时不占空间 */
    } quicklist;
    

    redis.conf相关参数:

    参数 含义
    List-max-ziplist-size(fill) 正数表示单个ziplist最多所包含的entry个数。负数表示单个ziplist的大小,默认8K。-1: 4KB; -2: 8KB; -3: 16KB; -4: 32KB; -5: 64KB
    List-compress-depth(compress) 压缩深度,默认是0。 1:首尾的ziplist不压缩; 2:首尾第一个第二个ziplist不压缩

    quicklist.h 46行

    
    typedef struct quicklistNode{
          struct quicklistNode *prev; /* 指向前一个节点 */
          struct quicklistNode *next; /* 指向后一个节点 */
          unsigned char *zl; /* 指向实际的ziplist */
          unsigned int sz;  /* 当前ziplist占用了多少字节 */
          unsigned int count: 16; /* 当前ziplist中存储了多少个元素,占16bit,最大65536个 */
          unsigned int encoding: 2; /* 是否采用了LZF压缩算法压缩节点 */ /*RAW==1 or LZF==2 */
          unsigned int container: 2; /* 2:ziplist 是不是已经被解压出来作临时使用 */
          unsigned int attempted_compress: 1; /* 测试用 */
          unsigned int extra: 10; /* 预留给未来使用 */
    } quicklistNode;
    

    ziplist的结构:quicklist是一个数组 + 链表的结构。

    应用场景

    List主要用在存储有序内容的场景。

    列表

    用户的消息列表、网站的公告列表、活动列表、博客的文章列表、评论列表等。顺序储存显示。

    队列/栈

    List还可以当做分布式环境的队列/栈使用。
    List还提供两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间(单位:秒)。

    blpop queue
    brpop queue
    

    BLPOP:BLPOP key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
    BRPOP:BRPOP key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

    队列:先进先出 :rpush blpop , 左头右尾,右边进入队列,左边出队列。
    栈:先进后出:rpush brpop
    总结:

    List存储有序的内容,用quicklist实现,本质上是数组 + 链表。
    hashTable也是数组加链表,只是内部编码结构不一样。

    Set集合

    存储类型

    Set存储String类型的无序集合,最大存储数量2^32-1(40亿左右)。

    操作命令

    //添加一个或者多个元素
    sadd myset a b c d e f g
    //获取所有元素
    smembers myset 
    //统计元素个数
    scard myset
    //随机获取一个元素
    srandmember myset
    //随机弹出一个元素
    spop myset 
    //移除一个或者多个元素
    srem myset d e f
    //查看元素是否存在
    sismember myset a
    

    存储(实现)原理
    Redis用intset或者hashTable存储set。如果元素都是整数类型,就用intset存储。

    insert.h 35行

    typedef struct intset{
          unint32_t encoding; //编码类型 int16_t 、int32_t 、int64_t
          unint32_t length;  //长度 最大长度:2^32
          int8_t contents[]; //用来存储成员的动态数组
    } intset;
    

    如果不是整数类型,就用hashTable(数组 + 链表的存储结构)。
    如果元素个数超过512个,也会用hashTable存储。跟一个配置有关:

    set-max-insert-entries 512
    

    QA: set的key没有value,怎么用hashTable存储? value这里存储的是null。

    应用场景

    抽奖

    随机获取元素: spop myset

    点赞、签到、打卡

    商品标签

    商品筛选

    //获取差集
    sdiff set1 set2
    //获取交集(intersection)
    sinter set1 set2
    //获取并集
    sunion set1 set2
    

    用户关注、推荐模型

    ZSet有序集合

    存储类型

    sorted set 存储有序的元素。每个元素有个score ,按照score从小到大排名。
    score 相同时。按照key的ASCII码排序。

    数据结构对比:

    数据结构 是否允许重复元素 是否有序 有序实现方式
    列表list 索引下标
    集合set
    有序集合zset 分值score

    操作命令

    //添加元素
    zadd myset 10 Java 20 php 30 ruby 40 cpp
    //获取全部元素
    zrange myzset 0 -1 withscore
    zrevrange myzset 0 -1 withscore
    //根据分值区间获取元素
    zrangebyscore myzset 20 30
    //移除元素 也可以根据sore rank 删除
    zrem myzset php cpp 
    //统计元素个数
    zcard myzset
    //分值递增
    zincrby myzset 5 python
    //根据分值统计个数
    zcount myzset 20 60
    //获取元素rank
    zrank myzset python
    //获取元素score 
    zscore myzset python
    

    存储原理

    默认使用ziplist编码(前面多种结构的类型,hash的小编吗,quicklist的node,都是ziplist)。
    在ziplist的内部,按照score排序递增来存储。插入的时候要移动之后的数据。
    如果元素数量大于等于128个,或者任一member长度大于等于64字节使用skiplist+dict存储。

    zset-max-ziplist-entries 128
    zset-max-ziplist-value 64
    

    那么什么是skiplist(跳表)?

    先看一下有序链表:

    在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到一个比给定数据大的节点位置。时间复杂度为O(n)。同样,我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。二分查找法只适用于有序数组,不适用于链表。

    假如我们每相邻两个节点增加一个执政,让指针指向下下个节点(或者理解为有元素进入了第二层)。

    这样所有新增加的链表连成一个新的链表,但它包含的节点个数只有原来的一半。

    那么那些元素会进入到第二层呢?在插入一个数据的时候,决定要放到哪一层,取决于一个算法,源码t_zset.c 122行

    int zslRandomLevel(void){
          int level = 1;
          while((random() && 0xFFFF) < ZSKIPLIST_P * 0xFFFF)
                level += 1;
          return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
    }
    

    现在我们查询数据的时候,可以先沿着这个新的链表进行查找。当碰到比待查数大的节点时,在到下一层进行查找。

    比如,我们想要找15,查找的路径是沿着标红的指针所指向的方向进行的:
    1. 15首先和3比较,比它大继续向后比较。
    2. 但是15和19比较时,要比19小,因此回到下面的链表(原链表),与7在第一层的下一个节点11比较。
    3. 15要比11大,沿下面的指针继续向后和19比较。15比19小,说明待查数据23在原链表中不存在。

    在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。这就是跳跃表。

    这里为什么不用AVL树或者红黑树?因为skiplist更加简洁。
    因为level是随机的,得到的skiplist可能是这样的,有些在第四层,有些在第三层,有的在第二层、第一层。

    我们看一下Redis里面skiplist的实现:

    源码: server.h 904行

    typedef struct zskiplistNode{
          sds ele; /* zset的元素 */
          double score; /* 分值 */
          struct zskiplistNode *backward; /* 后退指针 */
          struct zskiplistLevel{
                struct zskiplistNode *forward; /* 前进指针,对应level的下一个节点 */
                unsigned long span;/* 从当前节点到下一个节点的跨度(跨度的节点数) */
          } level[]; /* 层 */
    } zskiplistNode;
    
    typedef struct zskiplist{
          struct zskiplistNode *header, *tail; /* 指向跳跃表的头结点和尾节点 */
          unsigned long length; /* 跳跃表的节点数 */
          int level; /* 最大的层数 */
    } zskiplist;
    
    typedef struct zset{
          dict *dict;
          zskiplist *zsl;
    } zset;
    

    应用场景(顺序会动态变化的列表)

    排行榜

    • 百度热搜

    • 微博热搜

    id为6001的新闻点击数加1: zincrby hotNews:20251111 1 n6001
    获取今天点击最多的15条: zrevrange hotNews:20251111 0 15 withscores

  • 相关阅读:
    网站数据库
    提笔不知道说啥
    预祝大家新年快乐
    又..
    明日出发
    吸气呼气
    网吧
    光阴似箭
    <转>生活中的各种食品安全问题
    老了吗?
  • 原文地址:https://www.cnblogs.com/snail-gao/p/14288113.html
Copyright © 2020-2023  润新知