Redis常用數據結構以及多并發場景下的使用分析:Set類型

文章目錄

  • 前言
  • redis中的set結構
    • 疑問1 :為什么使用數組后 整體時間復雜度還是O(1)
    • 疑問2: set特性是無序的那為什么當元素少的時候 用連續數組 去存儲呢?
    • 疑問3:當元素少于512的時候即使用intset存儲的時候 是如何維護唯一性的?
    • 疑問4: set底層的hashtable 如何處理hash沖突的 對比redis 的Hash類型的處理策略
    • 實際看看 intset 和hashtable 的底層存儲數據區別
  • 使用例子
    • 在線用戶統計(去重)
    • 相似度計算(用戶標簽交集)
    • 秒殺活動參與者去重
  • 總結

前言

我們已經分析了三種不同數據類型 在redis中的使用 以及底層的數據結構

Redis常用數據結構以及多并發場景下的使用分析:String類型
Redis常用數據結構以及多并發場景下的使用分析:Hash類型
Redis常用數據結構以及多并發場景下的使用分析:List類型

接下來 我們要來分析下 set的基本使用

redis中的set結構

按照慣例首先還是需要搞清楚 redis中的 set 結構是怎樣去處理的

Redis 中 Set 類型的兩種底層實現方式:intset(整數集合) 和 hashtable(哈希表)
查找、插入、刪除時間復雜度為O(1)

intset(整數集合)

所有元素都是整數(可以轉為 long 類型);元素個數小于 set-max-intset-entries(默認 512);

特點:

連續內存:底層是一個連續的數組(有序數組 優勢利用上了 內存的局部性 所以是連續內存 )
查找:使用二分查找(因為數組是有序的)
插入:時保持有序(需要移動數組)

hashtable(哈希表)

元素中存在非整數(如字符串、UUID、中文等);或者元素數量超過 set-max-intset-entries(默認512);或者手動添加一個大字符串,也會觸發升級。

特點:

哈希表結構:底層是一個 dict;插入查找效率 O(1):基于哈希函數定位;空間占用較大:因為要維護哈希桶和沖突處理結構;支持任意類型元素(比如 "abc", "hello", "uuid");

看完基本介紹 不知道你是否有以下的疑問?

疑問1 :為什么使用數組后 整體時間復雜度還是O(1)

前面說了intset是一個內存連續 并且內部integer 連續的數組 采用二分查找 我們都只帶二分查找時間復雜度是O(logn)而不是O(1) 為什么還是O(1)呢?

解答:

那就是因為 集合較小(只有512個元素),在實踐中表現為“常數時間”—— 所以平均復雜度可認為是 O(1)。

疑問2: set特性是無序的那為什么當元素少的時候 用連續數組 去存儲呢?

解答:

高效性 + 空間緊湊性。

內存連續性 為了 利用上 CPU Cache 緩存的機制 盡管查找用的是 二分查找 O(log?n),但數據小、內存局部性好,速度非常快

疑問3:當元素少于512的時候即使用intset存儲的時候 是如何維護唯一性的?

解答:

先二分查找,發現已經存在元素就不插入

疑問4: set底層的hashtable 如何處理hash沖突的 對比redis 的Hash類型的處理策略

Set 類型:

Set 的底層是 dict,插入元素之前 Redis 會先判斷:

當前 key 是否已經存在于某個 hash 桶的鏈表中(即元素是否存在)

插入邏輯如下:

if (dictFind(set, value) == NULL) {dictAdd(set, value); // 插入
}

如果哈希沖突,但值不相等(鏈表中找不到值),會繼續插入;

如果值已經存在(即鏈表中有相同元素),則不插入,保持唯一性。

結論:

Set 類型允許哈希沖突,只要值不同就能插入;不允許值重復,即使哈希值一樣,Redis 也會用 memcmp 判斷值是否相等

Hash 類型(field-value 映射):
Hash 類型本質是:

dict<field, value>

與 Set 類似:

沖突時使用鏈式哈希解決

同一個 field 不允許重復,如果 field 已存在,則更新 value

插入邏輯:

dictReplace(hash, field, value);  // 如果存在就覆蓋

總結:

Set:
bucket[5] --> ["apple"] --> ["banana"]      // 值不能重復,但可以 hash 沖突Hash:
bucket[5] --> ["name" => "Tom"] --> ["age" => 20] // key 沖突則更新 value

實際看看 intset 和hashtable 的底層存儲數據區別

設計一個實驗去看插入519個數據之前 和 插入520個數據的區別

@Service
@RequiredArgsConstructor
@Slf4j
public class SetEncodingDemoService {private final RedisTemplate<String, String> redisTemplate;// key for testingprivate static final String SET_KEY = "demo:set:encoding";// 清空 & 初始化測試public void runEncodingTest() {redisTemplate.delete(SET_KEY);log.info("開始添加整數元素,觸發 intset -> hashtable 升級演示");// 1. 添加 500 個整數(仍為 intset 編碼)IntStream.range(0, 500).forEach(i -> {redisTemplate.opsForSet().add(SET_KEY, String.valueOf(i));});log.info("添加 500 個整數完成,請用命令 `debug object demo:set:encoding` 查看編碼,預計為 intset");// 2. 繼續添加超過限制,觸發升級IntStream.range(500, 520).forEach(i -> {redisTemplate.opsForSet().add(SET_KEY, String.valueOf(i));});log.info( 添加第 520 個整數完成,預計編碼變為 hashtable");}
}

測試類

@SpringBootTest
class SetEncodingDemoServiceTest {@Autowiredprivate SetEncodingDemoService setEncodingDemoService;@Testpublic void testEncodingChange() {setEncodingDemoService.runEncodingTest();}
}

分別執行步驟 1 和步驟 2:
在這里插入圖片描述
在這里插入圖片描述

在這里插入圖片描述

可以非常詳細的看到 內部數據結構由intset 變成了hashtable

使用例子

okok 我們已經了解了 set底層為整數數組+hashtable 那么接下來我們就來看看實際的使用例子
wait 稍等一下 set 有哪些常見的使用場景

特異元素都多少個? 求交并集?元素是否存在?

ok 那不就是可以使用這些場景嗎

在線用戶統計(去重)

記錄所有在線用戶,使用 Set 去重,并可實時獲取當前在線用戶數量。

redis結構:

// userID加入
SADD online:users userId
// 求在key(online:users)下有多少個userID
SCARD online:users
// 移除online:users userId 元素
SREM online:users userId
@Service
@RequiredArgsConstructor
public class OnlineUserService {private final RedisTemplate<String, Object> redisTemplate;private static final String ONLINE_USER_KEY = "online:users";// 用戶上線public void userOnline(String userId) {redisTemplate.opsForSet().add(ONLINE_USER_KEY, userId);}// 用戶下線public void userOffline(String userId) {redisTemplate.opsForSet().remove(ONLINE_USER_KEY, userId);}// 獲取當前在線人數public long getOnlineCount() {return redisTemplate.opsForSet().size(ONLINE_USER_KEY);}// 獲取所有在線用戶public Set<Object> getAllOnlineUsers() {return redisTemplate.opsForSet().members(ONLINE_USER_KEY);}
}

相似度計算(用戶標簽交集)

為每個用戶打標簽,利用標簽集合之間的交集計算相似度(如推薦系統)。

redis結構

// 給user:tags:uid123  打上三個標簽
SADD user:tags:uid123 "java" "redis" "ai"
// 給user:tags:uid456  打上三個標簽
SADD user:tags:uid456 "java" "kafka" "ai"
// 求交集
SINTER user:tags:uid123 user:tags:uid456
@Service
@RequiredArgsConstructor
public class UserTagSimilarityService {private final RedisTemplate<String, Object> redisTemplate;// 為用戶添加標簽public void addTags(String userId, String... tags) {String key = "user:tags:" + userId;redisTemplate.opsForSet().add(key, (Object[]) tags);}// 獲取兩個用戶的共同標簽(交集)public Set<Object> getCommonTags(String uid1, String uid2) {String key1 = "user:tags:" + uid1;String key2 = "user:tags:" + uid2;return redisTemplate.opsForSet().intersect(key1, key2);}// 相似度得分(交集大小 / 并集大小)public double computeSimilarity(String uid1, String uid2) {String k1 = "user:tags:" + uid1;String k2 = "user:tags:" + uid2;Set<Object> inter = redisTemplate.opsForSet().intersect(k1, k2);Set<Object> union = redisTemplate.opsForSet().union(k1, k2);if (union == null || union.isEmpty()) return 0.0;return (double) inter.size() / union.size();}
}

秒殺活動參與者去重

高并發下秒殺活動記錄用戶參與,每個用戶只能參與一次。

Redis 結構:

// seckill:activity:10001 下 增加一個 userId
SADD seckill:activity:10001 userId
@Service
@RequiredArgsConstructor
public class SeckillService {private final RedisTemplate<String, Object> redisTemplate;// 秒殺參與:返回是否成功(是否重復)public boolean tryJoinSeckill(String activityId, String userId) {String key = "seckill:activity:" + activityId;Long added = redisTemplate.opsForSet().add(key, userId);return added != null && added > 0;}// 獲取參與人數public long getJoinCount(String activityId) {return redisTemplate.opsForSet().size("seckill:activity:" + activityId);}// 判斷用戶是否參與過public boolean hasJoined(String activityId, String userId) {return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember("seckill:activity:" + activityId, userId));}
}

總結:

場景Redis Key 樣例操作高并發優勢
在線用戶統計online:usersSADD / SCARD去重 + 實時查詢人數
用戶相似度分析user:tags:uid123SINTER / SUNION集合交集操作原子且高效
秒殺用戶去重seckill:activity:10001SADD去重保證每人只能參與一次

總結

Set類型的并發優勢:

原子性:所有Set操作都是原子的
去重特性:自動處理重復數據
高性能:O(1)時間復雜度的添加/刪除/查詢
集合運算:支持交集、并集、差集等復雜操作
內存高效:緊湊的數據結構

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

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

相關文章

Linux中rw-rw-r--相關的訪問權限講解

下面就是關于 rw-rw-r-- 的知識圖譜式講解。核心節點&#xff1a;rw-rw-r-- (文件權限表示法) 這是一個在 Linux/Unix 操作系統中&#xff0c;通過 ls -l 命令查看到的&#xff0c;用于描述文件或目錄訪問權限的10字符字符串。分支一&#xff1a;字符串的解剖 (Anatomy of the …

C#異常處理:更優雅的方式

C#異常處理&#xff1a;更優雅的方式 在 C# 編程的世界里&#xff0c;異常處理是繞不開的重要環節。程序運行時難免會出現各種意外&#xff0c;若處理不當&#xff0c;可能導致程序崩潰&#xff0c;給用戶帶來糟糕體驗。所以&#xff0c;掌握更優雅的異常處理方式&#xff0c;對…

Qt6中模態與非模態對話框區別

一.阻塞 vs 非阻塞1.模態對話框阻塞父窗口&#xff1a;打開后&#xff0c;用戶必須先處理該對話框&#xff08;關閉或完成操作&#xff09;&#xff0c;才能繼續操作父窗口。應用場景&#xff1a;強制用戶立即響應的場景&#xff0c;如確認對話框、登錄窗口、文件選擇器等。2.非…

處理Web請求路徑參數

目錄 1. 路徑變量&#xff08;Path Variable&#xff09; 2. 查詢參數&#xff08;Query Parameter&#xff09; 3. 表單參數&#xff08;Form Data&#xff09; 4. 請求體JSON參數&#xff08;Request Body JSON&#xff09; 5. 請求頭參數&#xff08;Header Parameters&…

創客匠人:技術賦能下的創始人 IP 打造與內容創作新邏輯

在知識變現的浪潮中&#xff0c;創始人 IP 的核心競爭力始終圍繞內容展開&#xff0c;但內容創作的效率與質量往往成為瓶頸。創客匠人基于對行業的深刻洞察&#xff0c;探索出技術與內容融合的路徑&#xff0c;為創始人 IP 打造提供了新的思路 —— 不再將內容創作視為單純的輸…

Mysql分片:一致性哈希算法

一、一致性哈希的核心原理哈希取模最大的痛點是&#xff1a;當分片數量&#xff08;例如數據庫節點數&#xff09;發生變化時&#xff0c;幾乎所有數據的哈希結果都會改變&#xff0c;導致大規模的數據遷移。一致性哈希就是為了解決這個“伸縮性差”的問題而誕生的。核心思想&a…

前端學習 vben 之 axios interceptors

前端學習 vben 之 axios interceptors interceptor 攔截器&#xff0c;是一種軟件設計模式&#xff0c;核心思想就是在程序執行的特定階段&#xff08;如請求發送前&#xff0c;響應返回后&#xff0c;方法調用前后等&#xff09;自動插入自定義邏輯。實現對核心流程的“攔截”…

【java面試day4】redis緩存-數據持久化

文章目錄問題&#x1f4ac; Question 1相關知識問題 &#x1f4ac; Question 1 Q&#xff1a;redis作為緩存&#xff0c;數據的持久化是怎么做的? A&#xff1a;有兩種機制&#xff0c;一種是RDB&#xff0c;RDB會在指定的時間間隔內將內存中的數據生成快照&#xff0c;保存…

Vue3中element plus默認獲取最近一周和上個月的時間區間并在后端分開傳值

<el-form-item label"結算時間&#xff1a;" prop"datetimerangevalue"><el-date-pickerv-model"datetimerangevalue"value-format"YYYY-MM-DD HH:mm:ss"type"datetimerange"range-separator"至"start-p…

SQLAlchemy數據庫連接密碼特殊字符處理完全指南

引言 在使用SQLAlchemy連接數據庫時&#xff0c;我們通常使用URL格式指定連接信息&#xff0c;如mysqlpymysql://user:passwordhost:port/database。然而&#xff0c;當密碼中包含特殊字符&#xff08;如、#、$、!等&#xff09;時&#xff0c;會導致URL解析錯誤&#xff0c;進…

1.4 ARM安全參考架構(PSA Certified)

目錄1.4.1 PSA Certified概述1.4.2 PSA認證級別詳解1.4.3 PSA與TF-A的關系1.4.4 PSA安全模型實現信任根(RoT)架構關鍵安全服務&#xff1a;1.4.5 認證流程實踐1.4.6 典型應用案例參考資料1.4.1 PSA Certified概述 ARM Platform Security Architecture (PSA) Certified 是一套完…

企業網絡安全的“金字塔”策略:構建全方位防護體系的核心思路

在數字化轉型的浪潮中&#xff0c;企業的網絡安全已從單一的防護措施&#xff0c;發展成為多層次、全方位的安全體系。如何精準應對日益復雜的網絡威脅&#xff0c;成為眾多企業關注的焦點。本文將分享企業構建高效安全防護“金字塔”的核心思路。一、從“排查隱患”到“主動防…

爬蟲-request模塊使用

1.使用和安裝2.代碼測試打印返回的內容&#xff0c;默認是請求體中的標識.text 是打印源代碼設置一下編碼

HTML + CSS + JavaScript

目錄 1 HTML HTML 文件基本結構 HTML 開發工具 HTML 常見標簽 標題標簽&#xff1a;h1 - h6 段落標簽&#xff1a;p 換行標簽&#xff1a;br 圖片標簽&#xff1a;img 超鏈接標簽&#xff1a;a 表格標簽 表單標簽 form 標簽 input 標簽 select 標簽 textarea 標…

Java 與 MySQL 性能優化:MySQL連接池參數優化與性能提升

文章目錄引言一、連接池的基本概念與作用二、關鍵連接參數詳解2.1 max_connections2.2 wait_timeout2.3 interactive_timeout2.4 connect_timeout2.5 thread_cache_size三、連接池參數不合理導致的性能問題3.1 連接耗盡3.2 響應變慢3.3 連接失效3.4 資源浪費四、連接池參數優化…

浪潮CD1000-移動云電腦-RK3528芯片-2+32G-開啟ADB ROOT破解教程

浪潮CD1000-移動云電腦-RK3528芯片-232G-安卓9-開啟ADB ROOT破解教程破解教程&#xff1a;1.先下載好開心電視助手&#xff08;下載地址及其他版本&#xff1a;【工具大全】-【開心電視助手3.8&#xff0f;4.0&#xff0f;4.6&#xff0f;6.0&#xff0f;6.2&#xff0f;6.3&am…

【網絡編程】簡易的 p2p 模型,實現兩臺虛擬機之間的簡單點對點通信,并以小見大觀察 TCP 協議的具體運行

文章目錄基本概念業務拆解代碼實現準備工作實現被動的功能——多線程指針函數實現主動的功能——用戶選擇界面主函數代碼執行效果意外收獲總結推薦一個零聲教育學習教程&#xff0c;個人覺得老師講得不錯&#xff0c;分享給大家&#xff1a;[Linux&#xff0c;Nginx&#xff0c…

react狀態管理庫 - zustand

什么是zustand&#xff1f; zustand 是一個輕量級、快速且可擴展的 React 狀態管理庫&#xff0c;旨在提供一種簡單直接的方式來管理應用狀態&#xff0c;而無需其他解決方案通常伴隨的繁瑣代碼。根據官方 Zustand 文檔&#xff0c;Zustand 是“一個使用簡化 flux 原理的小型、…

粗排樣本架構升級:融合LTR特征提升模型性能的技術實踐

粗排樣本架構升級&#xff1a;融合LTR特征提升模型性能的技術實踐 ——基于PySpark的樣本構建與特征工程深度解析 一、粗排系統的定位與技術演進 在推薦系統級聯架構中&#xff0c;?粗排&#xff08;Rough Ranking&#xff09;?? 承擔著關鍵過渡角色&#xff1a;從召回層獲…

CCF-GESP 等級考試 2025年6月認證C++四級真題解析

1 單選題&#xff08;每題 2 分&#xff0c;共 30 分&#xff09;第1題 在C中&#xff0c;聲明一個指向整型變量的指針的正確語法是&#xff08; &#xff09;。A. int* ptr; B. *int ptr; C. int ptr*; D. ptr …