当前位置: 首页 > news >正文

手撕Redis源码1-数据结构实现

1.SDS动态字符串:

Redis中key保存的是字符串,value也往往是字符串或者字符串的集合,不过,Redis并未直接使用C语言中的字符串,因为C语言中的字符串存在一些问题,比如获取字符串长度需要通过运算且字符串不可修改,字符串是非二进制安全的,由此,Redis基于C语言构建了简单动态字符串SDS结构。

1.1 SDS结构实现

SDS是基于C语言中的结构体实现的,每个SDS有四个属性,buf[]为数据存储数组,len为buf中已保存的数据字节数(不包含结束标识),alloc表示buf申请的总字节数(包含结束标识),flags表示不同SDS类型,Redis中定义了多种类型的SDS,不同类型的SDS存储不同大小的头部和数据存储能力。

1.2 SDS动态扩容

SDS具备动态扩容的能力,例如,我们将一个内容为“hi”的SDS追加一段字符串",Amy",首先会申请新内存空间,进行内存预分配操作:

如果新字符串小于1M,则新空间为扩展后字符串长度的两倍+1

如果新字符串大于1M,则新空间为扩展后字符串长度+1M+1

基于SDS,我们实现了在O(1)时间复杂度获取字符串长度,实现了字符串的动态扩容,减少了内存分配次数,以及实现了二进制安全。

2.Inset结构

2.1 InSet结构实现

inset结构是Redis的Set集合的一种实现方式(当数据量较少并且只包含整数元素时Set结构采用Inset),也是基于结构体实现,具备长度可变,有序等特征,每个结构体有三个属性:content[]为整数数组,保存集合数据;length表示元素个数;encoding表示编码方式(即content数组中存储的具体数据类型),encoding有三种模式。

下图中的int8_t contents[] 是 C 语言里的柔性数组成员,它本身不占用结构体固定内存,只是一个 “占位符”。编译时,结构体实际大小不包含这个数组,运行时会动态分配内存。

为了提高查找效率,Redis会将数据按升序依次放到content数组中,如图所示:encodeing占4字节,length占4字节,contents占2*3=6字节

2.2 Inset升级流程

此时,我们向inset中添加一个数字:50000,这个数字超出了2字节的存储范围,因此,inset会自动升级为合适的编码方式。

首先,encoding会升级为INTSET_ENC_INT32,每个整数占4字节,并按照新的编码方式扩容数组,扩容过程中倒序依次将数组中的元素拷贝到扩容后的位置:

之后,将待添加的元素加入到数组末尾,最后,inset的encoding属性改为INTSET_ENC_INT32,length属性改为4


升级过程源码:

Inset可以看作特殊类型的整数数组,会确保元素的唯一性和有序性,具备类型升级机制,可以节省内存空间,并且底层采用二分查找的方式来查找元素

3.Dict数据结构

3.1 Dict结构实现

Redis是一个键值型数据库,可以通过键对数据进行CRUD操作,而键与值的映射关系通过Dict实现,Dict由三部分构成:哈希表(DictHashTable),哈希节点(DictEntry),字典(Dict)

当向Dict添加键值对时,Redis会首先根据Key计算出hash值,然后利用h & sizemask计算元素应存储到数组中哪个索引位置。

3.2 Dict扩容机制

Dict中的HashTable使用的是数组加链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过长,查询效率大大降低。

Dict在每次新增键值对时会监测负载因子(Load=userd/size),即哈希表中使用的节点个数除以哈希表大小,如果负载因子满足以下两种情况,会触发哈希表扩容机制:
load>=1并且服务器没有执行 BGSAVE 或者 BGREWRITEAOF 等后台进程;

load>5;

同样,每次删除元素时,都会对负载因子做检查,当load<0.1时,会做收缩操作。

3.3 Rehash操作

不管是扩容还是收缩,必然会创建新哈希表,导致哈希表的size和sizemask变化,而key查询又依赖于sizemask,因此,必须对哈希表中每一个key重新计算索引,插入到新的哈希表,此过程称为rehash,过程如下:

计算新哈希表的realSize,值取决于当前要做扩容还是收缩操作:

如果是扩容,则新size为第一个大于等于dict.ht[0].used + 1的2^n;

如果是收缩,则新size为第一个大于等于dict.ht[0].used的2^n (不得小于4);

根据新的realSize创建申请新的内存空间,创建dicthet,并赋值给dict.ht[1]

设置rehashidx=0,表示开始进行rehash

将dict.ht[0]中的每一个dictEntry都rehash到dict.ht[1]

将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表,释放原来dict.ht[0]的内存

扩容操作:

Dict的rehash并不是一次性完成的,如果数据量非常大,要在一次rehash完成可能会导致主线程阻塞,因此rehash是渐进式完成的,每次执行增删改查操作时,都会检查dict.rehashidx是否大于-1,如果是,则将dict.ht[0].table[rehashidx]的entry链表rehash到dict.ht[1],并且将rehashidx++,直到dict.ht[0]所有数据都rehash到dict.ht[1],rehash完成,将rehashidx赋值为-1。相当于每次只rehash一个节点的链表,进行删改查操作时,两个哈希表都去找,找到数据后进行操作。进行新增操作时,直接写入ht[1]。

http://www.dtcms.com/a/309902.html

相关文章:

  • SpringBoot3.x入门到精通系列:1.5 配置文件详解
  • 2025 数字经济就业方向及前景【一文说清楚】
  • Spark Shuffle性能优化实践指南:提升大数据处理效率
  • 【数据分享】中国27省乡镇(街道)级人口密度数据集(2000年)
  • 设计模式1:创建型模式
  • AI在安全方面的十个应用场景
  • 分布式弹幕系统设计
  • Vue.set 响应式原理详解:源码级逐行带入实战解析
  • 【go】slice元素去重
  • MonoGame游戏开发框架日记 -07
  • 【Go】P1 GoLang 语言简介与起源
  • iPhone 恢复出厂设置是否会删除所有内容?
  • 充电桩车位占用识别准确率↑32%:陌讯动态特征融合算法实战解析
  • STM32 使用 RTC 实现实时时钟功能
  • tauri实用教程:项目打包为安装包时如何包含其他文件
  • InfluxDB 与 Golang 框架集成:Gin 实战指南(一)
  • 噪声对比估计(NCE):原理、演进与跨领域应用
  • 第一个大语言模型的微调
  • 电路基础学习
  • 字节跳动招机器人数据算法研究员-Top Seed
  • 开源医院信息管理系统:基于若依框架的智慧医疗解决方案
  • Chrontel【CH7219A-BF】CH7219A USB-C和DP 1.4至HDMI 2.1协议转换器,带DSC解码功能
  • [2025CVPR-图象生成方向]ODA-GAN:由弱监督学习辅助的正交解耦比对GAN 虚拟免疫组织化学染色
  • 【Mysql】联合索引生效分析案例
  • 新手小白如何快速检测IP 的好坏?
  • AI有限元、聚合物复合材料多尺度建模材料性能预测及大模型应用实践,打破传统研发模式!
  • 【跨国数仓迁移最佳实践4】MaxCompute 企业级能力升级:跨域访问控制与数据安全特性增强
  • Apache RocketMQ中 Normal Message(普通消息)的说明
  • LRU缓存淘汰算法的详细介绍与具体实现
  • 智能体之外部工具篇(2)