Redis(二). 内存映射数据结构

本文介绍了Redis中两种内存映射数据结构——整数集合(intset)和压缩列表(ziplist),它们如何通过节省内存和自适应存储来提高效率,以及在实际场景中的应用和实现原理。升级机制和遍历操作也被详细阐述。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis(二). 内存映射数据结构

上一章节介绍的几个数据机构功能强大,但是缺点是比较消耗内存,也就是内存利用率不高,为了解决这个问题,Redis 设计了内存映射数据结构来在一定的情况下代替内部数据结构

内存映射数据结构特殊编码的字节序列,内存相比内部数据结构少很多,可以节约大量内存,但是缺点就是需要CPU 资源去按照规则解析这些字节序列;

1. 整数集合

intset 有序去重的保存整数值;根据大小,自动调整什么长度的整数类型来保存元素;

比如 在一个intset,保存的是int16_t 类型,新元素插入,新元素的类型是int32_t,intset 就会自动进行“升级”,所有元素升级为int32_t

1.1 应用

Redis 就会使用 intset 来保存集合元素 场景是:如果一个集合:

  1. 只保存着整数元素;

  2. 元素的数量不多

1.2 实现

intset 类型的定义

typedef struct intset {
// 保存元素所使用的类型的长度
uint32_t encoding;
// 元素个数
uint32_t length;
// 保存元素的数组 
int8_t contents[];  
} intset;

encoding 可以是下面其中之一 注意 encoding 使用 INTSET_ENC_INT16 作为初始值。
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))

1.3 运行过程

API 添加新元素(升级) intsetUpgradeAndAdd

添加新元素到 intset 的工作由 intset.c/intsetAdd 函数完成,它需要处理以下三种情况:

  1. 元素已存在于集合,不做动作;

  2. 元素不存在于集合,并且添加新元素并不需要升级;

  3. 元素不存在于集合,但是要在升级之后,才能添加新元素;

并且,intsetAdd 需要维持 intset->contents 的以下性质:

  1. 确保数组中没有重复元素;

  2. 确保数组中的元素按从小到大排序;

在这里插入图片描述

插入过程

intset *is = intsetNew();
intsetAdd(is, 10, NULL);
// is->encoding = INTSET_ENC_INT16;
// is->length = 1;
// is->contents = [10];
intsetAdd(is, 5, NULL);
// is->encoding = INTSET_ENC_INT16;
// is->length = 2;
// is->contents = [5, 10];

##### 升级
intsetAdd(is, 65535, NULL);
// is->encoding = INTSET_ENC_INT32; // 升级
// is->length = 3;
// is->contents = [5, 10, 65535]; // 所有值使用 int32_t 保存

intsetAdd(is, 4294967295, NULL);
// is->encoding = INTSET_ENC_INT64; // 升级
// is->length = 4;
// is->contents = [5, 10, 65535, 4294967295];

升级实例 :升级的过程就是程序控制content 字节序列的位数挪动达到每个元素占用一样的位长

1.4 总结

• Intset 用于有序、无重复,根据元素的值,自动选择该用什么长度的整数类型。
• 长度更长的整数值添加到 intset 时,需要对 intset 进行升级,新 intset 中每个元素的位长度都等于新添加值的位长度,但原有元素的值不变。
• 升级会引起整个 intset 进行内存重分配,并移动集合中的所有元素,这个操作的复杂度为 O(N)
• Intset 只支持升级,不支持降级
• Intset 是有序的,程序使用二分查找算法来实现查找操作,复杂度为 O(lg N) 。

2.压缩列表

Ziplist 是由一系列特殊编码的内存块构成的列表,一个 Ziplist 可以包含多个节点(entry),每个节点可以保存一个长度受限的字符数组(不以 \0 结尾的 char 数组)或者整数,包括:

• 字符数组
• 整数

2.1 实现

ziplist

在这里插入图片描述

参数长度值含义
zlbytesuint32_tzlbytes总字节,内存重新分配时使用
zltailuint32_t尾结点偏移量
zllenuint16_tziplist 中节点的数量
entryX?每个节点长度不一致 都加起来的长度
zlenduint8_t255 的二进制值 1111 1111 表示结束

ziplist 内的 entry

在这里插入图片描述

参数长度值含义
pre_entry_length1/5 byte上个节点的字节长度,用于跳转到上一个节点,
前一个小于254 就使用 1byte保存
前一个大于254 就5 byte 第一个设置为254 后四byte保存长度
encoding2bit00 、01 和 10,11
length?ziplist 中节点的数量
content?每个节点长度不一致 都加起来的长度

encoding + length

encoding 00 2bit 加上 length 6 bit (63) == 》 1 byte ==》content 长度小于等于 63 字节的字符数组

encoding 01 2bit 加上 length 14 bit (16383) == 》 2 byte==》content 长度小于等于 4294967295字节的字符数组

encoding 10_ _____ 2bit 加上 length 32 bit (4294967295) == 》 5 byte==》content 长度小于等于 4294967295字节的字符数组

11 比较复杂

编码长度保存的值
110000001 byteint16_t
110100001 byteint32_t
111000001 byteint64_t
111100001 byte24 bit 有符号整数
111111101 byte8 bit 有符号整数
1111xxxx1 byte4 bit 无符号整数,介于 0 至 12 之间

示例 hello world

在这里插入图片描述

保存整数10086

在这里插入图片描述

2.2 创建ziplist

创建一个新的ziplist

在这里插入图片描述

2.3 添加元素到末端

  • 定位 ziplist 的末端
  • 程序需要计算新节点所需的空间
  • 更新新节点的各项属性

2.4 将节点添加到某个**/**某些节点的前面

  1. next 的 pre_entry_length 域的长度正好能够编码 new 的长度(都是 1 字节或者都是 5

字节)

  1. next 的 pre_entry_length 只有 1 字节长,但编码 new 的长度需要 5 字节

  2. next 的 pre_entry_length 有 5 字节长,但编码 new 的长度只需要 1 字节

主要就是检测pre_entry_length ,因为这个不是固定长度导致的 长度

程序必须沿着路径一个个检查后续的节点是否满足新长度的编码要求,直到遇到一个能满足要求的节点

2.5 删除节点

  • 定位目标节点,并计算节点的空间长度
  • 进行内存移位,覆盖 target 原本的数据,然后通过内存重分配,收缩多余空间
  • 检查 next 、next+1 等后续节点能否满足新前驱节点的编码,和添加操作一样,删除操作也可能会引起连锁更新。

2.6 遍历

可以对 ziplist 进行从前向后的遍历,或者从后先前的遍历;直接通过前面说元素内部占用位数大小直接加减既可以指针移动遍历数据

2.7 总结

  • ziplist 是由一系列特殊编码的内存块构成的列表,它可以保存字符数组或整数值,它还是哈希键、列表键和有序集合键的底层实现之一。
  • 添加和删除 ziplist 节点有可能会引起连锁更新 ,主要就是pre_entry_length ,因为这个不是固定长度导致的后面每一个entry都需要校验前一个;(可能+4byte,可能-4Byte)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值