Redis存储原理与数据模型(上)

一、Redis数据模型

1.1、查看Redis数据定义:

typedef struct redisDb {kvstore *keys;              /* The keyspace for this DB 指向键值存储的指针,用于快速访问和修改数据库中的键值对*/kvstore *expires;           /* Timeout of keys with a timeout set 指向存储键过期时间*/ebuckets hexpires;          /* Hash expiration DS. Single TTL per hash (of next min field to expire) 用于存储哈希键的过期时间*/dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) 存储正在等待数据的阻塞键*/dict *blocking_keys_unblock_on_nokey;   /* Keys with clients waiting for* data, and should be unblocked if key is deleted (XREADEDGROUP).* This is a subset of blocking_keys 存储当键被删除时应解除阻塞的键 */dict *ready_keys;           /* Blocked keys that received a PUSH 存储已收到推送数据的阻塞键 */dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS 存储被WATCH命令监控的键 */int id;                     /* Database ID 数据库唯一标识符 */long long avg_ttl;          /* Average TTL, just for stats 键的平均生存时间 */unsigned long expires_cursor; /* Cursor of the active expire cycle. 活动过期循环的游标 */list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually.指向一个列表的指针,该列表存储了稍后尝试进行碎片整理的键名;用于优化内存使用,减少内存碎片。*/
} redisDb;struct dict {dictType *type;     /*使 dict 成为一个泛型数据结构,能够根据不同的数据类型和操作需求进行定制*/dictEntry **ht_table[2];  /*用于哈希表扩容、缩容,如果不扩容就使用ht_table[0],扩容就使用ht_table[1] */unsigned long ht_used[2]; /*记录两个哈希表已使用情况*/long rehashidx; /* rehashing not in progress if rehashidx == -1 用于记录 rehash 操作的进度。当 rehashidx 等于 -1 时,表示没有 rehash 操作正在进行;否则,rehashidx 表示当前 rehash 操作需要处理的桶的索引。控制哈希表的渐进式 rehash 过程,避免一次性 rehash 带来的性能开销。*//* Keep small vars at end for optimal (minimal) struct padding */unsigned pauserehash : 15; /* If >0 rehashing is paused 用于控制 rehash 操作的暂停和恢复。当 pauserehash 大于 0 时,表示 rehash 操作被暂停。 */unsigned useStoredKeyApi : 1; /* See comment of storedHashFunction above 用于指示是否使用存储的键 API */signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) 用于记录两个哈希表的大小指数。哈希表的大小是 2 的 ht_size_exp 次方,快速计算哈希表的大小,便于进行扩容和缩容操作。*/int16_t pauseAutoResize;  /* If >0 automatic resizing is disallowed (<0 indicates coding error) 用于控制是否允许自动调整哈希表的大小。当 pauseAutoResize 大于 0 时,表示不允许自动调整哈希表的大小。*/void *metadata[]; /*用于存储与字典相关的额外元数据,为字典提供额外的扩展能力,以满足不同的使用需求。*/
};

1.2、Redis扩容过程

/* Returning DICT_OK indicates a successful expand or the dictionary is undergoing rehashing,* and there is nothing else we need to do about this dictionary currently. While DICT_ERR indicates* that expand has not been triggered (may be try shrinking?)* 用于检查并可能扩展字典哈希表*/
int dictExpandIfNeeded(dict *d) {/* Incremental rehashing already in progress. Return. */if (dictIsRehashing(d)) return DICT_OK;/* If the hash table is empty expand it to the initial size. */if (DICTHT_SIZE(d->ht_size_exp[0]) == 0) {dictExpand(d, DICT_HT_INITIAL_SIZE);return DICT_OK;}/* If we reached the 1:1 ratio, and we are allowed to resize the hash* table (global setting) or we should avoid it but the ratio between* elements/buckets is over the "safe" threshold, we resize doubling* the number of buckets. * 检查是否需要扩容,如果负载因子达到或超过 1,或者超过了一个安全阈值(由 dict_force_resize_ratio 控制),并且字典类型允许扩容,则调用 dictExpand 函数进行扩容*/if ((dict_can_resize == DICT_RESIZE_ENABLE &&   //d->ht_used[0] >= DICTHT_SIZE(d->ht_size_exp[0])) ||(dict_can_resize != DICT_RESIZE_FORBID &&d->ht_used[0] >= dict_force_resize_ratio * DICTHT_SIZE(d->ht_size_exp[0]))){if (dictTypeResizeAllowed(d, d->ht_used[0] + 1))dictExpand(d, d->ht_used[0] + 1);return DICT_OK;}return DICT_ERR;
}

渐进式rehash

当 hashtable 中的元素过多的时候,不能一次性 rehash 到
ht[1] ;这样会长期占用 redis,其他命令得不到响应;所以需要使用渐进式 rehash.

  • 先将 ht[0] 中的元素重新经过 hash 函数生成 64 位整数
  • 再对ht[1] 长度进行取余,从而映射到ht[1]


    渐进式规则:
    1. 采用分治的思想,将rehash分到之后的每步增删改查的操作中
    1. 在定时器中,最大执行一毫秒 rehash ;每次步长 100 个数组槽位;

在这里插入图片描述

冲突,负载因子

  • 负载因子 = used / size; used是数组存储元素的个数,size是数组的长度
  • 负载因子越小,冲突越小;负载因子越大,冲突越大
  • redis的负载因子是1

扩容模拟

  • 如果负载因子 > 1,则会发生扩容,扩容的规则是翻倍
  • 如果正在fork(在rdb,aof复写以及rdb-aof混用情况下)时,会阻止扩容;但是此时若负载因子 > 5,索引效率大大降低,则马上扩容;这里涉及到写时复制原理(通过延迟复制资源直到需要修改它们时,才真正完成复制操作)

在这里插入图片描述

1.3、Redis缩容过程

/* Returning DICT_OK indicates a successful shrinking or the dictionary is undergoing rehashing,* and there is nothing else we need to do about this dictionary currently. While DICT_ERR indicates* that shrinking has not been triggered (may be try expanding?)* 用于检查并可能缩小字典哈希表 */
int dictShrinkIfNeeded(dict *d) {/* Incremental rehashing already in progress. Return. */if (dictIsRehashing(d)) return DICT_OK;/* If the size of hash table is DICT_HT_INITIAL_SIZE, don't shrink it. */if (DICTHT_SIZE(d->ht_size_exp[0]) <= DICT_HT_INITIAL_SIZE) return DICT_OK;/* If we reached below 1:8 elements/buckets ratio, and we are allowed to resize* the hash table (global setting) or we should avoid it but the ratio is below 1:32,* we'll trigger a resize of the hash table. *根据哈希表的负载因子(已使用桶的数量与哈希表大小的比值)来决定是否需要缩容。如果负载因子低于某个阈值(默认为 1/8,但可以通过 *dict_force_resize_ratio 进行调整),并且字典类型允许缩容,则调用 dictShrink 函数进行缩容 */if ((dict_can_resize == DICT_RESIZE_ENABLE &&d->ht_used[0] * HASHTABLE_MIN_FILL <= DICTHT_SIZE(d->ht_size_exp[0])) ||(dict_can_resize != DICT_RESIZE_FORBID &&d->ht_used[0] * HASHTABLE_MIN_FILL * dict_force_resize_ratio <= DICTHT_SIZE(d->ht_size_exp[0]))){if (dictTypeResizeAllowed(d, d->ht_used[0]))dictShrink(d, d->ht_used[0]);return DICT_OK;}return DICT_ERR;
}

缩容模拟

  • 如果负载因子 < 1/8,则会发生缩容;
  • 缩容的规则是恰好包含used的2^n
  • 恰好的理解:加入此时数组存储元素个数为9,恰好包含该元素的就是2^4,即16;
    在这里插入图片描述

拓展

SCAN cursor [MATCH pattern] [COUNT count]
  • 工作原理:SCAN 命令通过维护一个内部游标来迭代键空间。每次调用 SCAN 命令时,它都会返回当前游标位置的一批键以及更新后的游标值。客户端应该使用返回的游标值作为下一次迭代的输入,直到游标值变为 0,表示迭代完成。
  • 优点:
    • 非阻塞:与 KEYS 命令不同,SCAN 命令不会阻塞服务器,因为它是以增量方式迭代键空间的。
    • 灵活性:SCAN 命令支持模式匹配和每次迭代返回键的最大数量,提供了更大的灵活性。
    • 资源友好:由于 SCAN 命令不会一次性加载所有键到内存中,因此它对服务器资源的消耗更小。

在这里插入图片描述
在这里插入图片描述

二、高效的数据结构

2.1、Redis是基于K-V存储的,K-V是如何实现的?

Redis 使用字典(dict)来存储键值对。字典节点结构体 dictEntry 定义了键值对的存储方式:

/* 在redis\src\dict.c中定义存储k-v的字典节点结构体 */
/* -------------------------- types ----------------------------------------- */
struct dictEntry {void *key;                  // 一般指向string对象union {void *val;              // 指向redisObject对象uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next;     /* Next entry in the same hash bucket. */
};typedef struct {void *key;dictEntry *next;
} dictEntryNoValue;

每个键值对都会有一个dictEntry节点,但是这部分不是用户直接操作的,而是通过Redis对象来实现的。

/* -------------------------------------------------------------------------------- */
/* Redis对象结构体,在redis\src\server.h中 */
struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr;              // 指向底层实现数据结构,比如哈希表、双向链表等
};

实质如下图所示:

在这里插入图片描述

用户并不关心底层具体的数据结构实现,只管上层的提供的API能否实现用户想要的功能,如zset,hash等, 所以统一redisObject封装了底层的数据结构,对外提供统一的接口。

  • 这些键值对是如何保存进Redis并进行读取操作的?
    在这里插入图片描述
    0vice·GitHub

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/91500.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91500.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频生成模型蒸馏的方法

1.fastvideo https://github.com/hao-ai-lab/FastVideohttps://github.com/hao-ai-lab/FastVideo Distillation support Recipes for video DiT, based on PCM. Support distilling/finetuning/inferencing state-of-the-art open video DiTs: 1. Mochi 2. Hunyuan. 2.l

【mysql】—— mysql中的timestamp 和 datetime(6) 有什么区别,为什么有的地方不建议使用timestamp

在 MySQL 中,TIMESTAMP 和 DATETIME(6) 都是用于存储日期和时间的数据类型,但它们在存储范围、时区处理、存储方式等方面有显著区别。 1. 核心区别对比 特性 TIMESTAMP DATETIME(6) 存储范围 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC(受限于 32 位时间戳) 1000…

前端下载文件相关

1、下载 ‘Content-Type‘: ‘application/octet-stream‘ 的文件 当后端返回的响应头中 Content-Type 为 application/octet-stream 时&#xff0c;表示这是一个二进制流文件&#xff0c;浏览器无法直接展示&#xff0c;需要前端处理后下载到本地。 通过请求获取二进制数据…

代码随想录算法训练营第五十六天|动态规划part6

108.冗余连接 题目链接&#xff1a;108. 冗余的边 文章讲解&#xff1a;代码随想录 思路&#xff1a; 题意隐含 只有一个冗余边 #include <iostream> #include <vector> using namespace std; int n1001; vector<int>father(n,0);void init(){for(int i0;…

智能体通信协议

智能体通信协议A2AACPANPAgoraagents.jsonLMOSAITPA2A A2A官方文档&#xff1a;https://www.a2aprotocol.net/docs/introduction 开源代码和详细规范&#xff1a;https://github.com/google/A2A ACP ACP官方文档&#xff1a;https://acp.agentunion.cn ANP ANP官方文档&am…

QT交叉编译环境配置

QT交叉编译环境配置1 配置交叉编译工具链1.1 解压 放到/opt中1.2 使用环境变量1.2.1 设置成永久的环境变量1.2.2 临时环境变量1.3 安装编译需要的软件2 编译tslib库&#xff08;如果不需要触摸屏直接跳过&#xff09;3. 编译qt3.1 编译源码3.2 设置QCreator4 说明4.1 关于编译器…

【Android】【Java】一款简单的文本/图像加解密APP

写在前面 之前写过一篇博客,名为《【Java编程】【计算机视觉】一种简单的图片加/解密算法》,介绍了用Java在电脑上对图片进行简单的加密和解密操作,见链接: 文章链接 但是,文中所描述的算法在实际操作当中,存在严重的噪音(图像失真)的问题(且原因不明),本次经笔者研…

技术笔记 | Ubuntu 系统 OTA 升级全流程详解

前言&#xff1a;在嵌入式系统设备管理中&#xff0c;OTA&#xff08;Over-The-Air&#xff09;升级是实现设备远程维护、功能迭代的核心能力。本文基于 Ubuntu 系统环境&#xff0c;详细拆解 updateEngine 工具的 OTA 升级方案&#xff0c;从配置开启、命令使用到实战案例与问…

重复请求问题

重复请求问题 使用Promise和AbortController来实现思路是&#xff1a;通过在会话缓存中存储和比较请求信息&#xff0c;来防止用户在短时间内重复提交相同的请求。 具体思路如下&#xff1a; 存储请求信息&#xff1a;每次请求时&#xff0c;将请求的相关信息&#xff08;如URL…

CentOS7 Docker安装RocketMQ完整教程

目录 前言 环境准备 系统要求 检查Docker状态 创建网络和目录 创建Docker网络 创建数据目录 安装NameServer 启动NameServer容器 参数说明 验证NameServer启动 安装Broker 创建Broker配置文件 启动Broker容器 参数说明 验证Broker启动 安装管理控制台 启动控制…

main函数,常量指针与指针常量,野指针等,void与void的区别

指针&#xff08;续&#xff09; main函数原型 定义 main函数有多种定义格式&#xff0c;main函数也是函数&#xff0c;函数相关的结论对main函数也有效。 main函数的完整写法&#xff1a;int main(int argc, char *argv[]){..}int main(int argc, char **argv){..}扩展写法&am…

Mac m系列芯片安装node14版本使用nvm + Rosetta 2

由于苹果 M 系列芯片&#xff08;包括 M4&#xff09;使用的是 ARM 架构&#xff0c;而 Node.js 14 是在英特尔 x86 架构时代发布的&#xff0c;因此在 M 系列 Mac 上安装 Node.js 14 可能会遇到兼容性问题 解决方法&#xff1a;使用 nvm Rosetta 2右键点击「终端」→「显示简…

前端基础之《Vue(26)—Vue3两种语法范式》

一、选项式1、HTML写法<!-- 跟 Vue 说 Hello World&#xff01; --><script type"module"> import { createApp } from vuecreateApp({data() {return {message: Hello World!}} }).mount(#app) </script><div id"app"><h1>…

题目:BUUCTF之rip(pwn)

网址 BUUCTF在线评测https://buuoj.cn/challenges#rip打开&#xff0c;如图所示 提示&#xff1a;先别启动靶机&#xff0c;靶机可以最后在启动&#xff0c;先分析下载的附件pwn1。 点击下载&#xff0c;下载完成之后&#xff0c;该文件后缀类型改为exe&#xff08;就是将pwn…

el-button长按触发事件(含未响应的解决方案)

参考代码实现按钮长按触发逻辑 <template><el-button mousedown"handleMouseDown" mouseup"handleMouseUp">长按我</el-button> </template>data(){return{isPressed: false,timer: null,}},methods:{handleMouseDown() {this.isP…

List和 ObservableCollection 的区别

1. 变更通知机制​​ ​​ObservableCollection<T>​​ 实现了INotifyCollectionChanged和INotifyPropertyChanged接口&#xff0c;当集合元素被添加、删除、替换或重置时&#xff0c;会自动触发CollectionChanged事件&#xff0c;通知绑定的UI控件更新&#xff08;如WPF…

支付宝沙箱(白屏,用户订单参数错误等)

情况&#xff1a;Laravel项目的line 对接 支付宝沙箱测试 手机网站支付 1&#xff1a;沙箱地址&#xff0c;小到我找不到&#xff1a;沙箱应用 - 开放平台 2&#xff1a;虽然提供了系统密钥&#xff0c;但是只是测API链接的&#xff0c;要沙箱测试转账什么的&#xff0c;得用…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博评论IP地图可视化分析实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解微博评论IP地图可视化分析实现 视频在线地…

【代码随想录】刷题笔记——二叉树篇

目录 144. 二叉树的前序遍历 94. 二叉树的中序遍历 145. 二叉树的后序遍历 102. 二叉树的层序遍历 226. 翻转二叉树 101. 对称二叉树 104. 二叉树的最大深度 111. 二叉树的最小深度 222. 完全二叉树的节点个数 110. 平衡二叉树 257. 二叉树的所有路径 404. 左叶子之…

基于deepseek的文本解析 - 超长文本的md结构化

pdf超长合同或其他超100页非结构化文档&#xff0c;很难全量提交deepseek进行分析&#xff0c;一般需要先进行分割。然而&#xff0c;不管是langchain还是llamaindex提供的文本分割工具&#xff0c;很难直接对非结构化文本进行准确的内容分割&#xff0c;很多原始整体段落被划分…