redis中的整数集合

近来在看黄健宏的《Redis设计与实现》,读到整数集合这一节,其中有一些细节问题没有读到。

在互联网上也没有搜到答案,于是看源码有所得,于此记录一下同时也作为分享。

《Redis设计与实现》第一版发布于2014年4月,所用的redis版本是2.9, 比较旧,有些内容与最新版本也有所不同,下文也有提及。

对于书中原文提到的内容可以参考此博客: www.cnblogs.com/panxianhao/..., 与书中分毫不差。

同时在这里感谢黄先生对Redis源码的分享。


整数集合 (intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现。 举个例子,如果我们创建一个只包含五个元素的集合键,并且集合中的所有元素都是整数值,那么这个集合键的底层实现就会是整数集合

书中原文是这样提的,但是下载Redis源码之后,发现在2023年11月30日下载的redis-7.2.3中,intset中可存储数据的大小限制变为1GB:

c 复制代码
// src/t_set.c
/* Return the maximum number of entries to store in an intset. */
static size_t intsetMaxEntries(void) {
    size_t max_entries = server.set_max_intset_entries;
    /* limit to 1G entries due to intset internals. */
    if (max_entries >= 1<<30) max_entries = 1<<30;
    return max_entries;
}

并且在数据过多,超过上限时转换为HT(hashTable)

c 复制代码
src/t_set.c
/* Converts intset to HT if it contains too many entries. */
static void maybeConvertIntset(robj *subject) {
    serverAssert(subject->encoding == OBJ_ENCODING_INTSET);
    if (intsetLen(subject->ptr) > intsetMaxEntries())
        setTypeConvert(subject,OBJ_ENCODING_HT);
}

整数集合的数据结构:

c++ 复制代码
typedef struct intset {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

encoding表示contents中存储数据的类型,有如下定义:

c 复制代码
/* Note that these encodings are ordered, so:
 * INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

contents虽然类型是int8_t,但不会存储int8_t的数据,相当于是字节组成的数组吧 具体赋值插入时的算法如下,会把这个数组转换为具体的类型

c 复制代码
//intset.c
/* Set the value at pos, using the configured encoding. */
static void _intsetSet(intset *is, int pos, int64_t value) {
    uint32_t encoding = intrev32ifbe(is->encoding);

    if (encoding == INTSET_ENC_INT64) {
        ((int64_t*)is->contents)[pos] = value;
        memrev64ifbe(((int64_t*)is->contents)+pos);
    } else if (encoding == INTSET_ENC_INT32) {
        ((int32_t*)is->contents)[pos] = value;
        memrev32ifbe(((int32_t*)is->contents)+pos);
    } else {
        ((int16_t*)is->contents)[pos] = value;
        memrev16ifbe(((int16_t*)is->contents)+pos);
    }
}

升级与扩容:

当新加入的数据的类型占据的内存大小比之前的大时,会升级contents中元素的类型 比如原来有3个int16_t的数据,现在添加了一个int32类型的值,那就会把整个数组中的元素升级为int32_t,然后再插入

扩容原文没有提, 查看源码如下:

c 复制代码
// intset.c
is = intsetResize(is,intrev32ifbe(is->length)+1);
if (pos < intrev32ifbe(is->length)) intsetMoveTail(is,pos,pos+1);

/* Resize the intset */
static intset *intsetResize(intset *is, uint32_t len) {
    uint64_t size = (uint64_t)len*intrev32ifbe(is->encoding);
    assert(size <= SIZE_MAX - sizeof(intset));
    is = zrealloc(is,sizeof(intset)+size);
    return is;
}

每次只新增一个元素大小的空间,然后把原来的数据进行移动(根据新插入元素的pos)

注意: intset不支持降级

添加与删除元素的时间复杂度:

都是O(N), 因为要移动元素,再调整空间大小

c 复制代码
//intset.c
/* Delete integer from intset */
intset *intsetRemove(intset *is, int64_t value, int *success) {
    uint8_t valenc = _intsetValueEncoding(value);
    uint32_t pos;
    if (success) *success = 0;

    if (valenc <= intrev32ifbe(is->encoding) && intsetSearch(is,value,&pos)) {
        uint32_t len = intrev32ifbe(is->length);

        /* We know we can delete */
        if (success) *success = 1;

        /* Overwrite value with tail and update length */
        if (pos < (len-1)) intsetMoveTail(is,pos+1,pos);
        is = intsetResize(is,len-1);
        is->length = intrev32ifbe(len-1);
    }
    return is;
}

书中的重点回顾此作为记录:

  • 整数集合是集合键的底层实现之一
  • 以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型
  • 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存
  • 整数集合只支持升级操作,不支持降级操作
相关推荐
1.01^10004 小时前
[3-02-01].第13节:三方整合 - Jedis客户端操作Redis
redis
开航母的李大8 小时前
【中间件】Web服务、消息队列、缓存与微服务治理:Nginx、Kafka、Redis、Nacos 详解
前端·redis·nginx·缓存·微服务·kafka
langmeng1109 小时前
使用docker在3台服务器上搭建基于版本redis 6.x的一主两从模式
运维·redis·docker·容器·集群
代码老y13 小时前
Redis:现代应用开发的高效内存数据存储利器
数据库·redis·缓存
zz07232013 小时前
第二十周:Redis(二)
数据库·redis·缓存
云动雨颤15 小时前
告别数据僵尸!Redis实现自动清理过期键值对
redis
YUNYINGXIA16 小时前
Redis集群
redis·wpf
泽韦德17 小时前
【Redis】笔记|第10节|京东HotKey实现多级缓存架构
redis·笔记·缓存
麓殇⊙19 小时前
redis--黑马点评--Redisson快速入门
数据库·redis·缓存
whltaoin1 天前
Redis专题-实战篇一-基于Session和Redis实现登录业务
redis·缓存·springboot