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;
}

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

  • 整数集合是集合键的底层实现之一
  • 以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型
  • 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存
  • 整数集合只支持升级操作,不支持降级操作
相关推荐
sam-1237 小时前
k8s上部署redis高可用集群
redis·docker·k8s
看山还是山,看水还是。8 小时前
Redis 配置
运维·数据库·redis·安全·缓存·测试覆盖率
谷新龙0018 小时前
Redis运行时的10大重要指标
数据库·redis·缓存
精进攻城狮@8 小时前
Redis缓存雪崩、缓存击穿、缓存穿透
数据库·redis·缓存
avenue轩13 小时前
gdb调试redis。sudo
c++·redis
不惑_13 小时前
Redis:发布(pub)与订阅(sub)实战
前端·redis·bootstrap
cui_win13 小时前
Redis高可用-Sentinel(哨兵)
redis·bootstrap·sentinel
cui_win16 小时前
Redis高可用-主从复制
redis·git·github·主从复制·哨兵
三杯温开水18 小时前
基于 CentOS7.6 的 Docker 下载常用的容器(MySQL&Redis&MongoDB),解决拉取容器镜像失败问题
redis·mysql·docker
袁庭新18 小时前
LuaRocks如何安装数据库驱动?
java·数据库·redis·lua·luarocks·袁庭新