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
英文地址,請注明出處:http://en.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;很多原始整體段落被劃分…