[Redis]1-高效的數據結構P2-Set

按照慣例,先丟一個官網文檔鏈接。

上篇我們已經了解了高效的數據結構P1-String與Hash。
這篇,我們繼續來了解Redis的 Set 與 Sorted set。

目錄

  • 有序集合 Sorted set
    • 底層實現
  • 集合 Set
  • 總結
  • 資料引用

有序集合 Sorted set

Redis 有序集合是一組唯一的字符串(成員)集合,這些字符串根據一個關聯的分數進行排序。

這種有序、元素唯一且根據關聯的分數進行排序的高效操作的數據結構,簡稱ZSET。
可以用于:

  • 動態排序:比如排行榜,每個元素可以代表一個實體(如用戶、商品),分數表示排序依據(如積分、銷量)。由于ZSET自動維護排序,你可以輕松獲取排名靠前的成員、某個成員的排名,或者按分數范圍查詢。
# 添加
> zadd prices 8 sandwich 
(integer) 1> zadd prices 100000 car 
(integer) 1> zadd prices 6300 iphone 8900 iphonepro
(integer) 2# 結果展示
> zrange prices 0 9 withscores
1) "sandwich"
2) "8"
3) "iphone"
4) "6300"
5) "iphonepro"
6) "8900"
7) "car"
8) "100000"
  • 速率限制器:ZSET可以實現一種基于滑動窗口的速率限制器,利用時間戳作為分數,成員記錄請求標識,自動移除過期的請求。

底層實現

ZSET通過包含跳表和哈希表的二端口數據結構實現。每個ZSET對象包含一個哈希表和一個跳表,成員和分數在兩邊各存一份,哈希表存member -> score,跳表存score -> member的排序關系。

typedef struct zset {dict *dict;zskiplist *zsl;
} zset;

其中哈希結構上章已經了解過了。ZSET的唯一性便是通過Hash Table實現的。總體結構也同Hash類似。

跳表用于維護成員的按分數排序,支持高效的插入、刪除、排名查詢和范圍查詢。
本篇進行跳表skiplist的介紹,并了解skiplist是如何設計以支持排序的。

源碼地址點這,其結構由redis.h/zskiplistNode和redis.h/zskiplist兩個結構定義。

zskiplist和zskiplistNode結構如下:

typedef struct zskiplist {struct zskiplistNode *header, *tail; //頭、尾節點unsigned long length; // 長度int level;//記錄目前跳躍表內,層數最大的那個節點的層數(表頭節點的層數不計算在內)
} zskiplist;typedef struct zskiplistNode {sds ele;              // 成員double score;         // 分數struct zskiplistNode *backward;// 后退指針struct zskiplistLevel {struct zskiplistNode *forward;// 前進指針unsigned long span;// 跨度,記錄跳過的節點數(前進指針所指向節點和當前節點的距離)} level[];
};

zskiplistNode用于表示跳躍表節點,zskiplist結構用于保存跳躍表節點的相關信息。
zskiplistNode點的level數組可以包含多個元素,每個元素都包含一個指向其他節點的指針,程序可以通過這些層來加快訪問其他節點的速度,一般來說,層的數量越多,訪問其他節點的速度就越快。

較傳統Node與List,zskiplistNode多了一個level[]結構,這是一個動態數組。每個元素表示該節點在某一層級的前進指針(指向下一個節點)和跨度(span,表示跳過的節點數)

    struct zskiplistLevel {struct zskiplistNode *forward;// 前進指針unsigned long span;// 跨度,記錄跳過的節點數(前進指針所指向節點和當前節點的距離)} level[];

Redis zskiplistNode這個設計通過level[]存儲的多層索引預計算節點關系(預存關系),讓查找、插入和刪除的復雜度從傳統鏈表的O(N)降到O(log N),接近二分查找的效率。
跳表的高層索引節點稀疏,低層節點密集,類似二分搜索的層次劃分,快速定位目標節點或分數范圍。

level[]有點類似閉包表的核心表設計,存儲節點關系。

![[Pasted image 20250417234839.png]]

  • 核心方法
    閱讀Redis的zskiplist.c,重點看zslInsert(插入)和zslGetRank(排名計算),理解level[]和span的實現。
    zslInsert源碼如下:
/* 插入一個新節點到跳表中,返回新插入的節點指針* 參數:*   zsl: 跳表對象*   score: 新節點的分數*   ele: 新節點的成員(字符串)* 返回:*   新插入的節點指針*/
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; // update數組記錄每層插入位置的前驅節點unsigned long rank[ZSKIPLIST_MAXLEVEL];       // rank數組記錄每層累積的跨度int i, level;serverAssert(!isnan(score)); // 確保分數不是NaN// 步驟1:查找插入位置,記錄前驅節點和跨度x = zsl->header; // 從頭節點開始for (i = zsl->level-1; i >= 0; i--) { // 從最高層逐層向下查找// 初始化rank,繼承上一層的跨度(若非最高層)rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];// 沿當前層前進,直到遇到分數更大或字典序更大的節點while (x->level[i].forward &&(x->level[i].forward->score < score ||(x->level[i].forward->score == score &&sdscmp(x->level[i].forward->ele, ele) < 0))) {// 累加跨度,記錄跳過的節點數rank[i] += x->level[i].span;x = x->level[i].forward; // 前進到下一個節點}// 記錄當前層的前驅節點update[i] = x;}// 步驟2:隨機生成新節點的層級level = zslRandomLevel(); // 隨機層級,概率遞減(通常p=0.25)if (level > zsl->level) { // 如果新層級超過當前最大層級for (i = zsl->level; i < level; i++) {rank[i] = 0; // 新層級的rank初始化為0update[i] = zsl->header; // 新層級的前驅是頭節點update[i]->level[i].span = zsl->length; // 跨度設為跳表總長度}zsl->level = level; // 更新跳表最大層級}// 步驟3:創建新節點x = zslCreateNode(level, score, ele); // 分配新節點內存,初始化分數和成員for (i = 0; i < level; i++) { // 為每層設置指針和跨度// 插入新節點:將新節點的forward指向前驅的forwardx->level[i].forward = update[i]->level[i].forward;update[i]->level[i].forward = x; // 前驅指向新節點// 更新跨度:新節點的跨度 = 前驅原跨度 - 已跳過的節點數x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);update[i]->level[i].span = (rank[0] - rank[i]) + 1; // 前驅的新跨度}// 步驟4:處理更高層的跨度for (i = level; i < zsl->level; i++) {update[i]->level[i].span++; // 未插入新節點的層,跨度+1}// 步驟5:設置后退指針x->backward = (update[0] == zsl->header) ? NULL : update[0];if (x->level[0].forward)x->level[0].forward->backward = x;elsezsl->tail = x;// 步驟6:更新跳表元數據zsl->length++; // 跳表長度+1return x; // 返回新節點
}

集合 Set

Redis 集合是一個無序且唯一的字符串集合(成員)。您可以使用 Redis 集合高效地:

  • 跟蹤唯一的項目(例如,跟蹤訪問特定博客文章的所有唯一 IP 地址。唯一事件ID)
  • 表示關系(例如,具有給定角色的所有用戶的集合)
  • 執行常見的集合操作,如交集、并集和差集

和Java的HashSet一樣,非常適合刪除重復數據的集合。

Redis Set的底層實現主要依賴兩種數據結構:哈希表(Hash Table)和整數集合(Intset)

其中哈希結構上章已經了解過了。唯一性便是通過Hash Table實現的。那么整數集合Intset是干什么的呢?
用來提供動態數據結構選擇的。

當Set包含非整數成員(如字符串)或成員數量較多時,使用哈希表。
當Set的所有成員都是整數(支持int16、int32、int64),且成員數量較少時(受set-max-intset-entries配置控制,默認512),使用整數集合Intset。
數量超過閾值會轉換成Hash Table,且Intset到哈希表的轉換是單向的(不可逆),因為哈希表支持任意字符串,而Intset只支持整數。

即,Redis Set的底層數據結構會根據存儲的成員類型和數量動態選擇。
intset源碼地址。
源碼如下:

typedef struct intset {// 編碼類型(INTSET_ENC_INT16/32/64)uint32_t encoding;// 數組長度uint32_t length;// 保存元素的數組int8_t contents[];
} intset;

Intset是一個緊湊的有序數組,存儲整數值,自動選擇最小編碼類型(int16_t、int32_t、int64_t)以節省內存。
Intset有編碼升級機制:
當插入的整數超出當前編碼范圍(如int16_t溢出),Intset自動升級到更高編碼(如int32_t),并重新分配內存。
且Intset有序。Intset按數值大小排序,插入時使用二分查找定位。

Redis通過設計一種轉換機制,使用Intset來專門優化存儲小規模整數集合,達到節省內存(緊湊存儲)的目的,提升內存效率,且支持快速二分查找,適合小集合的查詢。

總結

Redis不負簡單高效的內存數據庫之名,一方面做了大量空間換時間的操作,一方面設計極致壓榨內存、提升內存效率。
比如跳表的預存、hash表的漸進擴容、String sds的預留空間、延遲釋放、intset的極致內存利用、set的動態轉換。

資料引用

《Redis設計與實現》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/77405.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/77405.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/77405.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python + Playwright:使用正則表達式增強自動化測試

Python + Playwright:使用正則表達式增強自動化測試 前言一、 為什么選擇正則表達式?二、 Playwright 中集成正則表達式:途徑與方法三、 實戰應用:正則表達式解決典型測試難題場景 1:定位 ID 或 Class 包含動態部分的元素場景 2:驗證包含可變數字或文本的提示信息場景 3:…

VASP 6.4.1 Ubuntu系統編譯安裝手冊

VASP 6.4.1 Ubuntu系統編譯安裝手冊 &#xff08;基于Ubuntu 22.04 LTS&#xff0c;適用x86_64架構&#xff09; 文章目錄 VASP 6.4.1 Ubuntu系統編譯安裝手冊第一章 系統環境深度配置1.1 硬件兼容性驗證1.2 操作系統環境準備1.3 數學庫深度優化配置 第二章 編譯環境深度調優2…

uniapp h5接入地圖選點組件

uniapp h5接入地圖選點組件 1、申請騰訊地圖key2、代碼接入2.1入口頁面 &#xff08;pages/map/map&#xff09;templatescript 2.2選點頁面&#xff08;pages/map/mapselect/mapselect&#xff09;templatescript 該內容只針對uniapp 打包h5接入地圖選點組件做詳細說明&#x…

java輸出、輸入語句

先創建一個用于測試的java 編寫程序 #java.util使java標準庫的一個包&#xff0c;這里拉取Scanner類 import java.util.Scanner;public class VariableTest {public static void main(String[] args) {#創建一個 Scanner 對象Scanner scanner new Scanner(System.in);System.…

AI Agents系列之構建多智能體系統

&#x1f9e0; 向所有學習者致敬&#xff01; “學習不是裝滿一桶水&#xff0c;而是點燃一把火。” —— 葉芝 我的博客主頁&#xff1a; https://lizheng.blog.csdn.net &#x1f310; 歡迎點擊加入AI人工智能社區&#xff01; &#x1f680; 讓我們一起努力&#xff0c;共創…

04.Spring 框架注解體系詳解

Spring 框架注解體系詳解 本文詳細介紹 Spring、Spring Boot 及 Spring Cloud 中常用注解的用途、生命周期及使用方式&#xff0c;幫助開發者更深入理解 Spring 注解驅動編程模式。 參考來源&#xff1a;Spring、SpringMVC、SpringBoot、SpringCloud 框架常用注解說明 目錄 注…

手撕STL——vector

目錄 引言 1&#xff0c;了解 STL 中的 vector 2&#xff0c;先來一個簡易版跑起來 2_1&#xff0c;構造函數 2_2&#xff0c;擴容reserve&#xff08;&#xff09; 2_3&#xff0c;push_back&#xff08;&#xff09; 2_4&#xff0c;pop_back&#xff08;&#xff09; …

優恩-具備浪涌保護功能的固態繼電器UNRD0610-無觸點開關器件?

MOSFET固態繼電器 : 最高負載電壓&#xff1a;60V 最大負載電流&#xff1a;10A 快速響應時間&#xff1a;≤1ms 低驅動電流&#xff1a;≤10mA 高絕緣性&#xff0c;輸入輸出間隔離電壓&#xff1a;AC3000V 耐脈沖浪涌沖擊能力強 符合IEC 61000-4-2 ESD標準&#xff1a…

Kaamel隱私與安全分析報告:Microsoft Recall功能評估與風險控制

本報告對Microsoft最新推出的Recall功能進行了全面隱私與安全分析。Recall是Windows 11 Copilot電腦的專屬AI功能&#xff0c;允許用戶以自然語言搜索曾在電腦上查看過的內容。該功能在初次發布時因嚴重隱私和安全問題而備受爭議&#xff0c;后經微軟全面重新設計。我們的分析表…

Kotlin協程Semaphore withPermit約束并發任務數量

Kotlin協程Semaphore withPermit約束并發任務數量 import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.launch import kotlinx.coroutines.runBlockingfun main() {val permits 1 /…

鴻蒙語言基礎

準備工作 去鴻蒙官網下載開發環境 點擊右側預瀏覽&#xff0c;刷新和插銷按鈕&#xff0c;插銷表示熱更新&#xff0c;常用按鈕。 基礎語法 string number boolean const常量 數組 let s : string "1111"; console.log("string", s);let n : number …

C++數據結構與二叉樹詳解

前言&#xff1a; 在C編程的世界里&#xff0c;數據結構是構建高效程序的基石&#xff0c;而二叉樹則是其中最優雅且應用廣泛的數據結構之一。本文將帶你深入理解二叉樹的本質、實現與應用&#xff0c;助你在算法設計中游刃有余。 一、二叉樹的基本概念 1. 什么是二叉樹 二叉樹…

淺析數據庫面試問題

以下是關于數據庫的一些常見面試問題: 一、基礎問題 什么是數據庫? 數據庫是按照數據結構來組織、存儲和管理數據的倉庫。SQL 和 NoSQL 的區別是什么? SQL 是關系型數據庫,使用表結構存儲數據;NoSQL 是非關系型數據庫,支持多種數據模型(如文檔型、鍵值對型等)。什么是…

piamon實戰-- 如何使用 Paimon 的 Java API 實現數據的點查

簡介 Apache Paimon(原 Flink Table Store)是一款基于流批一體架構的 ??高性能數據湖存儲框架??,支持低延遲的數據更新、實時查詢和高效的鍵值點查(Point Lookup)。 本文將深入解析 Paimon 的點查機制,并通過 Java API 代碼案例演示如何實現數據的點查功能。 一、Pai…

社交媒體時代的隱私憂慮:聚焦Facebook

在數字化時代&#xff0c;社交媒體平臺已成為人們日常生活的重要組成部分。Facebook作為全球最大的社交媒體之一&#xff0c;擁有數十億用戶&#xff0c;其對個人隱私的影響和憂慮也日益凸顯。本文將探討社交媒體時代下&#xff0c;尤其是Facebook平臺上的隱私問題。 數據收集…

問題:el-tree點擊某節點的復選框由半選狀態更改為全選狀態以后,點擊該節點展開,懶加載出來子節點數據以后,該節點又變為半選狀態

具體問題場景&#xff1a; 用戶點擊父節點復選框將其從半選變為全選&#xff08;此時子節點尚未加載&#xff09;。 點擊節點展開觸發懶加載&#xff0c;加載子節點。 子節點加載后&#xff0c;組件重新計算父節點狀態&#xff0c;發現并非所有子節點被選中&#xff0c;因此父節…

FastGPT安裝前,系統環境準備工作?

1.啟用適用于 Linux 的 Windows 子系統 方法一&#xff1a;打開控制面板 -> 程序 -> 啟用或關閉Windows功能->勾選 “適用于Linux的Vindows子系統” 方法二&#xff1a;以管理員身份打開 PowerShell&#xff08;“開始”菜單 >“PowerShell” >單擊右鍵 >“…

網頁端調用本地應用打開本地文件(PDF、Word、excel、PPT)

一、背景原因 根據瀏覽器的安全策略&#xff0c;在網頁端無法直接打開本地文件&#xff0c;所以需要開發者曲線救國。 二、實現步驟 前期準備&#xff1a; 確保已安裝好可以打開文件的應用軟件&#xff0c;如&#xff0c;WPS&#xff1b; 把要打開的文件統一放在一個文件夾&am…

EnlightenGAN:低照度圖像增強

簡介 簡介:記錄如何使用EnlightenGAN來做低照度圖像增強。該論文主要是提供了一個高效無監督的生成對抗網絡,通過全球局部歧視器結構,一種自我調節的感知損失融合,以及注意機制來得到無需匹配的圖像增強效果。 論文題目:EnlightenGAN: Deep Light Enhancement Without P…

010數論——算法備賽

數論 模運算 一般求余都是對正整數的操作&#xff0c;如果對負數&#xff0c;不同編程語言結果可能不同。 C/javapythona>m,0<a%m<m-1 a<m,a%ma~5%32~-5%3 -21(-5)%(-3) -2~5%(-3)2-1正數&#xff1a;&#xff08;ab&#xff09;%m((a%m)(b%m))%m~正數&#xff…