面试题:Redis常用数据结构

1 string

基本编码方式,基于简单动态字符串(SDS)实现,存储上线为512mb.

如果存储的SDS长度小于44字节,则会采用EMBSTR编码,此时object head与SDS是一段连续空间。申请内存时只需要调用一次内存分配函数,效率更高。

如果存储的字符串是整数值,并且大小在LONG MAX范围内,则会采用INT编码:直接将数据保存在Redisobjiect的ptr指针位置(刚好8字节),不再需要SDS了。

结论
RAW会有两次内存分配,效率较低
如果sds长大衣小于44字节,则会使用EMBSTR编码方式,只分布一次内存
INT编码方式,则sds中存储的是正数值,且数值范围小于Long.Max

2 List

Redis的List类型可以从首尾操作。

根据List的操作可以使用如下的编码方式。

  • LinkedList:普通链表,可以从双端访问,内存占用较高,内存碎片较多

  • ZipList: 压缩列表,可以从双端访问,内存占用低,存储上限低

  • QuickList : LinkedList + zipList,可以从双端访问,内存占用较低,包含多个ZipList,存储上限高

/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
    pushGenericCommand(c,LIST_HEAD,0);
}

/* RPUSH <key> <element> [<element> ...] */
void rpushCommand(client *c) {
    pushGenericCommand(c,LIST_TAIL,0);
}
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX. 
 * 'xx': push if key exists. */
 // where 表示从头插还是尾插
void pushGenericCommand(client *c, int where, int xx) {
    int j;
        // 将命令存放到argv中 LPUSH key v1 v2
    for (j = 2; j < c->argc; j++) {
        if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
            addReplyError(c, "Element too large");
            return;
        }
    }
    // 找到key对象的list
    robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
    // 类型校验
    if (checkType(c,lobj,OBJ_LIST)) return;
    // 检查是否为空,list不存在
    if (!lobj) {
        if (xx) {
            addReply(c, shared.czero);
            return;
        }
        // 创建quickList
        lobj = createQuicklistObject();
        // ziplist的限制
        quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                            server.list_compress_depth);
        dbAdd(c->db,c->argv[1],lobj);
    }

    for (j = 2; j < c->argc; j++) {
        listTypePush(lobj,c->argv[j],where);
        server.dirty++;
    }

    addReplyLongLong(c, listTypeLength(lobj));

    char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
    signalModifiedKey(c,c->db,c->argv[1]);
    notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}

3 Set

Set是Redis中的单列集合,满足下列特点

  • 不保证有序

  • 元素唯一(可以判断元素是否存在)

  • 求交集、并集和差集

使用IntSet 或者 hash 结构。为了查询效率和唯一性,set采用HT编码 (Dict)。Dict中的key用来存储元素,value统一为null。

当存储的 所有数据都是整数 ,并且元素数量不超过set-max-intset-entries时,Set会采用IntSet编码,以节省内存。

4 ZSet

ZSet也就是SortedSet,其中每一个元素都需要指定一个score值和member值

  • 可以根据score值排序后

  • member必须唯一

  • 可以根据member查询分数

使用 sikpList 或者 (Dict) 结构

  • skipList:可以排序,并且可以同时存储score和ele值 (member)。无法根据member找score

  • HT (Dict):可以键值存储,并且可以根据key找value。无法排序

缺点:数据冗余,内存占用过高。

当元素数量不多时,HT和SkipList的优势不明显,而且更耗内存。因此zset还会采用ZipList结构来节省内存,不过需要同时满足两个条件:

  • 元素数量小于zset max ziplist entries,默认值128

  • 每个元素都小于zset max ziplist value字节,默认值64

插入元素时

ziplist本身没有排序功能,而且没有键值对的概念,因此需要有zset通过编码实现:

  • ZipList是连续内存,因此score和element是紧挨在一起的两个entry,element在前,score在后

  • score越小越接近队首,score越大越接近队尾,按照score值升序排列。

5 Hash

Hash结构与Redis中的Zset非常类似

  • 都是键值存储

  • 都需求根据键获取值

  • 键必须唯一

  • Hash结构默认采用ZipList编码,用以节省内存。ZipList中相邻的两个entry 分别保存field和value。

  • 当数据量较大时Hash结构会转为HT编码,也就是Dict,触发条件有两个:

  • ZipList中的元素数量超过了hash-max-ziplist-entries(默认512)

  • ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节)

在不满足上述要求时会转换为HT

总结
  • String 底层采用RAW、 EMBSTRINT类型的编码。当SDS字节数不超过44字节时采用 EMBSTR,当为整数值时采用INT类型编码

  • List 底层采用quickList

  • Set 底层采用 使用IntSet 或者 hashtable 结构,当值都为整数时采用IntSet,一般采用hash,其value为null.

  • Zset 底层采用 SkipList +hashtable 或者 ziplist 结构

  • hash 底层采用zipList 和 hashtable 。

  • 采用ziplist的原因是在小数据量情况下,效率高而且内存占用少。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兜兜转转m

一毛钱助力博主实现愿望

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值