Redis源码分析03——高级数据结构下

219 阅读4分钟

高级数据结构

)哈希(hash)
  • 数据结构

Redis的hash对象,采用了两种方式来实现的。前面分析过连续内存和非连续内存各自的优缺点,在这里hash表也折中了这两种情况。

1589537859855

1589541714411

123456789
void hashTypeConvert(robj *o, int enc) {    if (o->encoding == OBJ_ENCODING_ZIPLIST) {        hashTypeConvertZiplist(o, enc);    } else if (o->encoding == OBJ_ENCODING_HT) {        serverPanic("Not implemented");    } else {        serverPanic("Unknown hash encoding");    }}
  • 注意点

    • 由ziplist转dict的操作是不可逆的。
    • 尽可能的使用ziplist来作为hash底层实现。长度尽量控制在1000以内,否则由于存取操作时间复杂度在O(n)到O(n^2)之间,长列表会导致CPU消耗严重。使用1KB以上的对象,hash-ziplist结构控制键数量反而得不偿失。
    • 两个参数【hash-max-ziplist-entries和hash-max-ziplist-value】可在配置文件中进行修改
    • ziplist作为底层对象时,其查找的时间复杂度为O(n)
  • 使用场景

    • 存储对象(定长或者不定长属性)

      • 原生字符串

        123
        set user:1:name tomset user:1:age 23set user:1:city beijing
        • 优点:简单直观,每个属性都支持单独更新
        • 缺点:占用过多的键,内存占用量大,同时用户信息内聚性差,生产环境比较少用
      • 序列化字符串

        1
        set user:1 serialize(userInfo)
        • 优点:简化编程,如果合理利用序列化,可以提高内存的使用率
        • 缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据都提取出来进行反序列化,更新后在重新序列化
      • 哈希类型

        1
        hmset user:1 name tom age 23 city beijing
        • 优点:简化编程,如果合理利用序列化,可以提高内存的使用率。每个用户属性使用一对field-value,但只用一个键保存。
        • 缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
)集合(set)
  • 数据结构

Redis的set对象,也是采用了两种方式来实现的。

1589543448120

123456789
robj *setTypeCreate(sds value) {    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)        return createIntsetObject();    return createSetObject();}//是否能转成llint isSdsRepresentableAsLongLong(sds s, long long *llval) {    return string2ll(s,sdslen(s),llval) ? C_OK : C_ERR;}

1589543517021

  • 注意点

    • 由intset转dict的操作是不可逆的。
    • set是不允许重复的。
    • 支持交并补
    • 参数【set-max-intset-entries】可在配置文件中进行修改
    • dict作为底层对象时,value值为null
    • intset作为底层对象时,其查找的时间复杂度为O(logN)
  • 使用场景

    • 标签:主要是用于社交里面感兴趣的内容(注意尽量保证一个事务下完成)

      • 用户添加标签

        1234
        sadd user:1:tags tag1 tag2 tag5sadd user:2:tags tag2 tag3 tag5...sadd user:k:tags tag1 tag2 tag4
      • 给标签标记用户

        12345
        sadd tag1:users user:1 user:3sadd tag2:users user:1 user:2 user:3...sadd tagk:users user:1 user:2...
      • 计算用户共同感兴趣的标签

        1
        sinter user:1:tags user:2:tags
    • 抽奖中随机数

      1
      spop/srandmember
)有序集合(zset)
  • 数据结构

    1234
    typedef struct zset {    dict *dict;//字段    zskiplist *zsl;//线段跳表} zset;

1589543884734

  • 注意点

    • 在skiplist的基础上,还需要创建dict的原因是当需要获取某个元素的score时,skplist的时间复杂度为O(n),而dict时间复杂度为O(1).(见zsetAdd)
    • skiplist和dict共享元素和分值(指针复制)。
    • 由ziplist转skiplist的操作是不可逆的。
    • 两个参数【zset-max-ziplist-entries和zset-max-ziplist-value 】可在配置文件中进行修改。
    • zset也不允许重复。
  • 使用场景

    • 优先队列

    • 排行榜系统:主要是视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。

      • 添加用户赞数

        1
        zadd user:ranking:2016_03_15 3 mike
      • 有人点赞

        1
        zincrby user:ranking:2016_03_15  1 mike
      • 取消点赞

        1
        zrem user:ranking:2016_03_15 mike
      • 展示获取赞数最多的十个用户

        1
        zrevrange user:ranking:2016_03_15 0 9
      • 展示用户信息以及用户分数和排名

        123
        hgetall user:info:tomzscore user:ranking:2016_03_15 mikezrank user:ranking:2016_03_15 mike
小小总结
数据类型适用场景备注
字符串(string)缓存;计算器简单型的。如set stunum studentInfo。计数器如限流
列表(list)lpush+lpop=Stack(栈) lpush+rpop=Queue(队列) lpush+ltrim=Capped Collection(有限集合) lpush+brpop=Message Queue(消息队列)如阻塞队列,关注列表
哈希(hash)对象属性(尤其不定长的)如缓存studentInfo,hmset stunum stunum 1 stuname dinghaha age 18
集合(set)适用社交场景赞/踩、粉丝、共同好友/喜好、推送
有序集合(set)排行榜;优先队列;缓存相关的元数据(比如按照排序的挑战)

稿定设计导出-20200516-182631