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

文章目錄

  • 前言
  • hash 對比 String
    • 簡單存儲對象
    • 【秒殺系統】- 商品庫存管理
    • 【用戶會話管理】- 分布式Session存儲
    • 【信息預熱】- 首頁信息預熱
    • 降級策略
  • 總結

前言

上文我們分析了String類型 在多并發下的應用 本文該輪到 Hash了,期不期待 兄弟們 hhh

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

okok 那么hash 相對于String類型有哪些優勢呢?

hash 對比 String

舉一個簡單的例子:
你可以看到 hash 在面對存在結構化的數據 會更有優勢 方便統一管理

適合存儲對象

// 單獨String
redisTemplate.opsForValue().set("user:123:name", "張三");
redisTemplate.opsForValue().set("user:123:age", "25");
redisTemplate.opsForValue().set("user:123:city", "北京");
// 問題:3次網絡往返 + 3個key占用更多內存// 一個HashMap
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("name", "張三");
userInfo.put("age", "25");
userInfo.put("city", "北京");redisTemplate.opsForHash().putAll("user:123", userInfo);
// 優勢:1次網絡往返 + 內存更緊湊

簡單存儲對象

@Service
@RequiredArgsConstructor
public class HashRedisService {private final RedisTemplate<String, Object> redisTemplate;// 商品屬性存儲public void saveProduct(String productId, Product product) {String key = "product:" + productId;Map<String, Object> productMap = new HashMap<>();productMap.put("name", product.getName());productMap.put("price", product.getPrice().toString());productMap.put("category", product.getCategory());productMap.put("stock", product.getStock().toString());redisTemplate.opsForHash().putAll(key, productMap);}}

在這里插入圖片描述

【秒殺系統】- 商品庫存管理

設計思路:

使用Hash存儲商品庫存信息,避免熱點key問題
Key: seckill:stock:date
Field: productId
Value: stock

Lua腳本 原子化扣減庫存Value
·1. 獲取當前庫存(HGET key field)
·2. 判斷商品是否存在
·3. 判斷庫存是否足夠
·4. 使用 HINCRBY 扣減庫存(負數)
使用 HMGET 命令批量獲取庫存 例如
·HMGET seckill:stock:2025-07-08 1001 1002 1003

// 1. 【秒殺系統】- 商品庫存管理
@Service
@RequiredArgsConstructor
@Slf4j
public class SeckillStockService {private final RedisTemplate<String, Object> redisTemplate;/*** 使用Hash存儲商品庫存信息,避免熱點key問題* Key: seckill:stock:date* Field: productId* Value: stock*/public void initSeckillStock(String date, Map<String, Integer> productStocks) {String stockKey = "seckill:stock:" + date;// 批量初始化庫存,比逐個set效率高很多Map<String, Object> stockMap = new HashMap<>();productStocks.forEach((productId, stock) -> {stockMap.put(productId, stock);});redisTemplate.opsForHash().putAll(stockKey, stockMap);redisTemplate.expire(stockKey, Duration.ofDays(1));log.info("初始化秒殺庫存完成,商品數量: {}", productStocks.size());}/*** 高并發扣減庫存 - 使用Lua腳本保證原子性*/public boolean decrementStock(String date, String productId, int quantity) {String stockKey = "seckill:stock:" + date;String luaScript = """local stockKey = KEYS[1]local productId = KEYS[2] local quantity = tonumber(ARGV[1])local currentStock = redis.call('HGET', stockKey, productId)if currentStock == false thenreturn -1  -- 商品不存在endcurrentStock = tonumber(currentStock)if currentStock < quantity thenreturn 0   -- 庫存不足endredis.call('HINCRBY', stockKey, productId, -quantity)return 1      -- 扣減成功""";DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);Long result = redisTemplate.execute(script, Arrays.asList(stockKey, productId),quantity);return result != null && result == 1;}/*** 批量查詢庫存狀態 - 單次查詢多個商品*/public Map<String, Integer> batchGetStock(String date, List<String> productIds) {String stockKey = "seckill:stock:" + date;// 使用HMGet批量獲取,比多次HGet效率高List<Object> stocks = redisTemplate.opsForHash().multiGet(stockKey,new ArrayList<>(productIds));Map<String, Integer> result = new HashMap<>();for (int i = 0; i < productIds.size(); i++) {Object stock = stocks.get(i);result.put(productIds.get(i), stock != null ? (Integer) stock : 0);}return result;}
}

寫一個測試類 去測試 這個扣減庫存的邏輯

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SeckillStockServiceTest {@Autowiredprivate SeckillStockService seckillStockService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private static final String TEST_DATE = "2025-07-07";@Test@Order(1)void testInitSeckillStock() {Map<String, Integer> stockMap = new HashMap<>();stockMap.put("p1001", 10);stockMap.put("p1002", 5);stockMap.put("p1003", 0);seckillStockService.initSeckillStock(TEST_DATE, stockMap);Map<String, Integer> result = seckillStockService.batchGetStock(TEST_DATE, List.of("p1001", "p1002", "p1003"));assertEquals(10, result.get("p1001"));assertEquals(5, result.get("p1002"));assertEquals(0, result.get("p1003"));}@Test@Order(2)void testDecrementStockSuccess() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p1001", 2);assertTrue(success);Map<String, Integer> result = seckillStockService.batchGetStock(TEST_DATE, List.of("p1001"));assertEquals(8, result.get("p1001"));}@Test@Order(3)void testDecrementStockFailDueToNotEnough() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p1003", 1);assertFalse(success);  // 原庫存是 0,無法扣減}@Test@Order(4)void testDecrementStockFailDueToNonExistProduct() {boolean success = seckillStockService.decrementStock(TEST_DATE, "p9999", 1);assertFalse(success);  // 商品不存在,Lua 返回 -1,也處理為 false}}

在這里插入圖片描述

【用戶會話管理】- 分布式Session存儲

設計思路:

用戶登錄 - 創建分布式Session
Key: session:userId
Fields: token, loginTime, lastActiveTime, deviceInfo, permissions…
Value: token_value,loginTime_value…
Lua腳本 權限檢查 - 快速獲取用戶權限
·1. HGET session:{userId} permissions
·2. 判斷權限信息是否存在
Lua腳本 更新用戶活躍時間 - 只更新單個字段
·1. HSET session:123 lastActiveTime 1720492341255

// 2. 【用戶會話管理】- 分布式Session存儲
@Service
@RequiredArgsConstructor
public class UserSessionService {private final RedisTemplate<String, Object> redisTemplate;/*** 用戶登錄 - 創建分布式Session* Key: session:userId* Fields: token, loginTime, lastActiveTime, deviceInfo, permissions...*/public String createUserSession(String userId, String deviceInfo, Set<String> permissions) {String sessionKey = "session:" + userId;String token = generateToken();long currentTime = System.currentTimeMillis();Map<String, Object> sessionData = new HashMap<>();sessionData.put("token", token);sessionData.put("loginTime", currentTime);sessionData.put("lastActiveTime", currentTime);sessionData.put("deviceInfo", deviceInfo);sessionData.put("permissions", String.join(",", permissions));sessionData.put("status", "active");// 一次性存儲所有session數據redisTemplate.opsForHash().putAll(sessionKey, sessionData);redisTemplate.expire(sessionKey, Duration.ofHours(24));return token;}/*** 更新用戶活躍時間 - 只更新單個字段*/public void updateLastActiveTime(String userId) {String sessionKey = "session:" + userId;redisTemplate.opsForHash().put(sessionKey, "lastActiveTime", System.currentTimeMillis());// 延長session過期時間redisTemplate.expire(sessionKey, Duration.ofHours(24));}/*** 權限檢查 - 快速獲取用戶權限*/public boolean hasPermission(String userId, String permission) {String sessionKey = "session:" + userId;Object permissions = redisTemplate.opsForHash().get(sessionKey, "permissions");if (permissions == null) return false;String permissionStr = (String) permissions;return Arrays.asList(permissionStr.split(",")).contains(permission);}/*** 批量獲取在線用戶信息*/public Map<String, Map<Object, Object>> batchGetUserSessions(List<String> userIds) {Map<String, Map<Object, Object>> result = new HashMap<>();// 使用Pipeline批量獲取,避免多次網絡往返List<Object> sessionData = redisTemplate.executePipelined(new SessionCallback<Object>() {@Override@SuppressWarnings("unchecked")public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {for (String userId : userIds) {operations.opsForHash().entries((K) ("session:" + userId));}return null;}});for (int i = 0; i < userIds.size(); i++) {@SuppressWarnings("unchecked")Map<Object, Object> session = (Map<Object, Object>) sessionData.get(i);if (session != null && !session.isEmpty()) {result.put(userIds.get(i), session);}}return result;}private String generateToken() {return UUID.randomUUID().toString().replace("-", "");}
}

寫一個測試類去測試獲取權限信息

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserSessionServiceTest {@Autowiredprivate UserSessionService userSessionService;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Test@Order(1)void testCreateUserSession() {String userId = "user123";String deviceInfo = "iPhone 15 Pro";Set<String> permissions = Set.of("read", "write", "admin");String token = userSessionService.createUserSession(userId, deviceInfo, permissions);assertNotNull(token);assertFalse(token.isEmpty());// 驗證session數據是否正確存儲String sessionKey = "session:" + userId;Map<Object, Object> sessionData = redisTemplate.opsForHash().entries(sessionKey);assertEquals(token, sessionData.get("token"));assertEquals(deviceInfo, sessionData.get("deviceInfo"));
//        assertEquals("read,write,admin", sessionData.get("permissions"));assertEquals("active", sessionData.get("status"));System.out.println("用戶會話創建成功,Token: " + token);}@Test@Order(2)void testUpdateLastActiveTime() throws InterruptedException {String userId = "user123";// 獲取初始時間Object initialTime = redisTemplate.opsForHash().get("session:" + userId, "lastActiveTime");Thread.sleep(10); // 等待一小段時間// 更新活躍時間userSessionService.updateLastActiveTime(userId);// 驗證時間是否更新Object updatedTime = redisTemplate.opsForHash().get("session:" + userId, "lastActiveTime");assertNotEquals(initialTime, updatedTime);System.out.println("用戶活躍時間更新成功");}@Test@Order(3)void testHasPermission() {String userId = "user123";assertTrue(userSessionService.hasPermission(userId, "read"));assertTrue(userSessionService.hasPermission(userId, "write"));assertTrue(userSessionService.hasPermission(userId, "admin"));assertFalse(userSessionService.hasPermission(userId, "delete"));System.out.println("權限檢查功能正常");}@Test@Order(4)void testBatchGetUserSessions() {// 創建多個用戶會話String userId2 = "user456";String userId3 = "user789";userSessionService.createUserSession(userId2, "Android Phone", Set.of("read"));userSessionService.createUserSession(userId3, "MacBook Pro", Set.of("read", "write"));// 批量獲取用戶會話List<String> userIds = List.of("user123", userId2, userId3, "nonexistent");Map<String, Map<Object, Object>> sessions = userSessionService.batchGetUserSessions(userIds);assertEquals(3, sessions.size()); // 應該返回3個存在的用戶會話assertTrue(sessions.containsKey("user123"));assertTrue(sessions.containsKey(userId2));assertTrue(sessions.containsKey(userId3));assertFalse(sessions.containsKey("nonexistent"));// 驗證批量獲取的數據正確性Map<Object, Object> user123Session = sessions.get("user123");assertEquals("iPhone 15 Pro", user123Session.get("deviceInfo"));assertEquals("active", user123Session.get("status"));System.out.println("批量獲取用戶會話功能正常");System.out.println("獲取到 " + sessions.size() + " 個用戶會話");}//    @AfterAll
//    static void cleanup(@Autowired RedisTemplate<String, Object> redisTemplate) {
//        // 清理測試數據
//        redisTemplate.delete("session:user123");
//        redisTemplate.delete("session:user456");
//        redisTemplate.delete("session:user789");
//        System.out.println("測試數據清理完成");
//    }
} 

在這里插入圖片描述

【信息預熱】- 首頁信息預熱

設計思路:

緩存預熱就是在系統啟動時就“主動把常用數據裝進 Redis”,讓用戶訪問時“直接命中緩存” 一下是一個簡單的使用場景

// 【緩存預熱】- 提升系統啟動速度
@Service
@RequiredArgsConstructor
public class CacheWarmupService {private final RedisTemplate<String, Object> redisTemplate;private final ProductService productService;private final UserService userService;/*** 商品信息緩存預熱*/@EventListener(ApplicationReadyEvent.class)public void warmupProductCache() {log.info("開始商品緩存預熱...");// 首先從數據庫 獲取熱門商品列表List<Product> hotProducts = productService.getHotProducts(1000);// 批量緩存商品信息Map<String, Map<String, Object>> productBatch = new HashMap<>();for (Product product : hotProducts) {Map<String, Object> productInfo = new HashMap<>();productInfo.put("name", product.getName());productInfo.put("price", product.getPrice().toString());productInfo.put("category", product.getCategory());productInfo.put("brand", product.getBrand());productInfo.put("stock", product.getStock());productInfo.put("sales", product.getSales());productInfo.put("rating", product.getRating());productBatch.put("product:" + product.getId(), productInfo);}// 使用Pipeline批量預熱redisTemplate.executePipelined(new SessionCallback<Object>() {@Overridepublic <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {productBatch.forEach((key, productInfo) -> {operations.opsForHash().putAll((K) key, productInfo);operations.expire((K) key, Duration.ofHours(6));});return null;}});log.info("商品緩存預熱完成,緩存商品數量: {}", hotProducts.size());}}

降級策略

那么如果當redis不可用了 該怎么處理呢?那么就應該使用多級緩存的思想 樓主后續也會專門寫一個文章去講解多級緩存 請期待 這里首先給出一個簡單的 降級策略 代碼片段

public Map<String, Object> getProductInfoWithFallback(String productId) {try {// 優先從 Redis 緩存中讀取商品詳情return redisTemplate.opsForHash().entries("product:" + productId);} catch (Exception e) {// Redis 報錯時(如連接失敗、超時等),降級處理log.warn("Redis查詢失敗,降級到數據庫", e);// 從數據庫中查詢return productService.getFromDatabase(productId);}
}

總結

使用hash結構去存儲結構化的數據 例如 本質都是一種緩存的思想

網頁的首頁展示 (你想想不可能去數據庫查詢吧 響應太慢了)
電商系統 庫存管理
用戶系統 用戶信息權限管理
排行榜 管理點贊數量
配置中心 …

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

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

相關文章

雙因子認證(2FA)是什么?從零設計一個安全的雙因子登錄接口

前言在信息系統逐漸走向數字化、云端化的今天&#xff0c;賬號密碼登錄已不再是足夠安全的手段。數據泄露、撞庫攻擊、社工手段頻發&#xff0c;僅靠「你知道的密碼」已不足以保證賬戶安全。因此&#xff0c;雙因子認證&#xff08;2FA, Two-Factor Authentication&#xff09;…

stack棧練習

為了你&#xff0c;我變成狼人模樣&#xff1b; 為了你&#xff0c;染上了瘋狂~ 目錄stack棧練習棧括號的分數單調棧模板框架小結下一個更大元素 I&#xff08;單調棧哈希&#xff09;接雨水stack棧練習 棧 一種先進后出的線性數據結構 具體用法可參考往期文章或者維基介紹i…

詳細頁智能解析算法:洞悉海量頁面數據的核心技術

詳細頁智能解析算法&#xff1a;突破網頁數據提取瓶頸的核心技術剖析引言&#xff1a;數字時代的數據采集革命在當今數據驅動的商業環境中&#xff0c;詳細頁數據已成為企業決策的黃金資源。無論是電商商品詳情、金融公告還是新聞資訊&#xff0c;??有效提取結構化信息??直…

ubuntu環境如何安裝matlab2016

一、下載安裝文件&#xff08;里面包含激活包CRACK&#xff09;可從度盤下載&#xff1a;鏈接:https://pan.baidu.com/s/1wxmVMzXiSY4RIT0dyKkjZg?pwd26h6 復制這段內容打開「百度網盤APP 即可獲取」注&#xff1a;這里面包含三個文件&#xff0c;其中ISO包含安裝文件&#x…

Mybits-plus 表關聯查詢,嵌套查詢,子查詢示例演示

在 MyBatis-Plus 中實現表關聯查詢、嵌套查詢和子查詢&#xff0c;通常需要結合 XML 映射文件或 Select 注解編寫自定義 SQL。以下是具體示例演示&#xff1a;示例場景 假設有兩張表&#xff1a; 用戶表 userCREATE TABLE user (id BIGINT PRIMARY KEY,name VARCHAR(50),age IN…

Stable Diffusion Web 環境搭建

默認你的系統Ubuntu、CUDA、Conda等都存在&#xff0c;即具備運行深度學習模型的基礎環境 本人&#xff1a;Ubuntu22.04、CUDA11.8環境搭建 克隆項目并且創建環境 https://github.com/AUTOMATIC1111/stable-diffusion-webui conda create -n sd python3.10運行過程自動安裝依賴…

嵌入式系統中實現串口重定向

在嵌入式系統中實現串口重定向&#xff08;將標準輸出如 printf 函數輸出重定向到串口&#xff09;通常有以下幾種常用方法&#xff0c;下面結合具體代碼示例和適用場景進行說明&#xff1a; 1. 重寫 fputc 函數&#xff08;最常見、最基礎的方法&#xff09; 通過重寫標準庫中…

static補充知識點-代碼

public class Student {private static int age;//靜態的變量private double score;//非靜態的方法public void run(){}public static void go(){}public static void main(String[] args) {new Student().run();Student.go();} } public class Person {//2 &#xff1a; 賦初始…

使用泛型<T>,模塊化,反射思想進行多表數據推送

需求&#xff1a;有13個表&#xff0c;其中一個主表和12細表&#xff0c;主表用來記錄推送狀態&#xff0c;細表記錄12種病例的詳細信息&#xff0c;現在需要把這12張病例表數據進行數據推送&#xff1b;普通方法需要寫12個方法分別去推送數據然后修改狀態&#xff1b;現在可以…

光流 | RAFT光流算法如何改進提升

RAFT(Recurrent All-Pairs Field Transforms)作為ECCV 2020最佳論文,已成為光流估計領域的標桿模型。其通過構建4D相關體金字塔和GRU迭代優化機制,在精度與泛化性上實現了突破。但針對其計算效率、大位移處理、跨場景泛化等問題,研究者提出了多維度改進方案,核心方向可系…

linux/ubuntu日志管理--/dev/log 的本質與作用

文章目錄 **一、基本概念****二、技術細節:UNIX域套接字****三、在不同日志系統中的角色****四、應用程序如何使用 `dev/log`****五、查看和驗證 `/dev/log`****六、總結 `/dev/log` 的核心作用**一、基本概念 /dev/log 是一個 UNIX域套接字(Unix Domain Socket),是Linux系…

EMC整改案例之(1):汽車NFC進入模塊BCI整改

EMC整改案例(1):汽車NFC進入模塊BCI整改 在汽車電子系統中,NFC(Near Field Communication)進入模塊用于實現無鑰匙進入功能,但它在電磁兼容(EMC)測試中常面臨挑戰。本案例聚焦于BCI(Bulk Current Injection)測試整改,該測試模擬大電流注入對設備的影響。以下是基于…

2025年INS SCI2區,靈活交叉變異灰狼算法GWO_C/M+集群任務調度,深度解析+性能實測

目錄1.摘要2.灰狼算法GWO原理3.靈活交叉變異灰狼算法GWO_C/M4.結果展示5.參考文獻6.代碼獲取7.算法輔導應用定制讀者交流1.摘要 隨著云計算的快速發展&#xff0c;受自然現象啟發的任務調度算法逐漸成為研究的熱點。灰狼算法&#xff08;GWO&#xff09;因其強大的收斂性和易于…

Java常用加密算法詳解與實戰代碼 - 附可直接運行的測試示例

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有堅忍不拔之志 &#x1f390; 個人CSND主頁——Micro麥可樂的博客 &#x1f425;《Docker實操教程》專欄以最新的Centos版本為基礎進行Docker實操教程&#xff0c;入門到實戰 &#x1f33a;《RabbitMQ》…

2025開發者工具鏈革命:AI賦能的效率躍遷

目錄引言&#xff1a;效率焦慮下的開發者生存現狀一、智能代碼編輯器&#xff1a;從輔助到主導的進化1.1 GitHub Copilot&#xff1a;全能型AI助手1.2 Cursor Pro&#xff1a;極致編碼體驗1.3 飛算JavaAI&#xff1a;垂直領域顛覆者二、版本控制革命&#xff1a;Git的AI進化論2…

“虛空”的物理、哲學悖論

一、虛空并非“完全真空”&#xff1a;量子場論揭示的“真空不空” 物理真空的本質 現代物理學中的“真空”并非絕對的空無一物&#xff0c;而是量子場的基態&#xff08;能量最低狀態&#xff09;。根據量子場論&#xff1a; 虛粒子漲落&#xff1a;真空中持續發生量子漲落&am…

CSP-S模擬賽二總結(實際難度大于CSP-S)

T1 很簡短&#xff0c;也很好做&#xff0c;第一題直接場切。 我的方法 首先要明確一件事&#xff1a;就是如果選了 ax,ya_{x,y}ax,y?&#xff0c;那么就必然要選 ay,xa_{y,x}ay,x?&#xff0c;所以第一步就在 ax,ya_{x,y}ax,y? 的基礎上加上 ay,xa_{y,x}ay,x?。 然后我…

旋轉屏幕優化

1.問題背景 從google原生算法&#xff0c;可以知道其有2個比較大的缺陷&#xff1a; 1) 通過重力傳感器傳來的x&#xff0c;y&#xff0c;z軸的加速度合成之后只有一個垂直往下的加速度&#xff0c;如果此時用戶在別的方向上有加速度&#xff0c;那么通過反余弦、反正切等計算…

Java---day2

七、IDEA開發工具 &#x1f4e6; 一、下載 IntelliJ IDEA 官網地址&#xff1a; &#x1f517; IntelliJ IDEA – the IDE for Pro Java and Kotlin Development 版本選擇&#xff1a; 版本說明Community Edition (CE)免費開源版本&#xff0c;適合 Java、Kotlin、Android…

RAL-2025 | 清華大學數字孿生驅動的機器人視覺導航!VR-Robo:面向視覺機器人導航與運動的現實-模擬-現實框架

作者&#xff1a; Shaoting Zhu, Linzhan Mou, Derun Li, Baijun Ye, Runhan Huang, Hang Zhao單位&#xff1a;清華大學交叉信息研究院&#xff0c;上海期智研究院&#xff0c;Galaxea AI&#xff0c;上海交通大學電子信息與電氣工程學院論文標題&#xff1a;VR-Robo: A Real-…