前言
本篇讀者收益
- 理解 Redis 五大數據結構的特性和適用場景
- 掌握在 Java 中高效操作各種 Redis 數據結構的方法
- 學會根據業務需求選擇合適的數據結構
- 瞭解數據結構層面的性能優化技巧
先修要求
- 已完成第一篇環境搭建
- 瞭解 Redis 基本命令
- 熟悉 Java 集合框架
- 具備面向對象編程基礎
關鍵要點
- 字符串不僅是文本,更是計數器、緩存和分佈式鎖的基礎
- 哈希適合存儲對象,減少鍵數量,優化內存使用
- 列表實現隊列、棧和時間線,支持阻塞操作
- 集合處理唯一性、標籤系統和社交關係
- 有序集合構建排行榜、延遲隊列和範圍查詢
背景簡述
Redis 之所以能夠提供極高的性能,很大程度上得益於其精心設計的數據結構體系。與傳統的鍵值存儲不同,Redis 的值可以是多種數據結構,每種結構都針對特定的使用場景進行了優化。
Redis 數據結構體系:
理解這些數據結構的特性和適用場景,是高效使用 Redis 的關鍵。不同的數據結構在內存使用、操作複雜度和適用場景上都有顯著差異。
環境準備與快速上手
在開始深入學習數據結構之前,需要已經建立了 Redis 連接。我們基於第一篇的優化連接池繼續構建:
public class RedisDataStructureBase {
protected JedisPool jedisPool;
public RedisDataStructureBase() {
this.jedisPool = OptimizedJedisPool.getJedisPool();
}
// 統一的資源清理方法
public void cleanup() {
try (Jedis jedis = jedisPool.getResource()) {
// 清理測試數據
jedis.del("test:*");
}
}
}
核心用法與代碼示例
字符串(String):不僅僅是文本
字符串是 Redis 最基本的數據類型,但它的應用遠不止存儲文本那麼簡單。
內存結構與特性:
- 最大 512MB 容量
- 二進制安全,可存儲任何數據
- 支持數值操作和位操作
- 常用於緩存、計數器、分佈式鎖
Java 操作實戰:
public class StringOperations extends RedisDataStructureBase {
/**
* 基礎字符串操作 - 緩存場景
*/
public void basicStringOperations() {
try (Jedis jedis = jedisPool.getResource()) {
// 設置鍵值 - 普通緩存
jedis.set("user:1001:name", "張三");
jedis.set("user:1001:email", "zhangsan@example.com");
// 帶過期時間的緩存 - 會話數據
jedis.setex("session:abc123", 3600, "session_data_json");
// 只有鍵不存在時設置 - 分佈式鎖基礎
boolean success = jedis.setnx("resource:lock", "locked") == 1;
System.out.println("獲取鎖結果: " + success);
// 批量操作 - 提升性能
jedis.mset("config:timeout", "30", "config:retries", "3", "config:theme", "dark");
}
}
/**
* 計數器應用 - 閲讀量統計
*/
public void counterApplications() {
try (Jedis jedis = jedisPool.getResource()) {
// 文章閲讀計數
Long views = jedis.incr("article:1001:views");
System.out.println("文章閲讀量: " + views);
// 帶步長的計數
jedis.incrBy("user:1001:points", 10);
// 遞減操作
jedis.decr("product:1001:stock");
// 獲取並設置 - 原子操作
String oldValue = jedis.getSet("config:version", "2.0");
System.out.println("舊版本: " + oldValue);
}
}
/**
* 位操作 - 用户標籤系統
*/
public void bitOperations() {
try (Jedis jedis = jedisPool.getResource()) {
String userKey = "user:2001:tags";
// 設置位 - 每個位代表一個標籤
jedis.setbit(userKey, 0, true); // 標籤1: VIP用户
jedis.setbit(userKey, 1, true); // 標籤2: 活躍用户
jedis.setbit(userKey, 2, false); // 標籤3: 新用户(未設置)
// 檢查標籤
boolean isVip = jedis.getbit(userKey, 0);
System.out.println("是否是VIP: " + isVip);
// 統計設置的位數 - 用户標籤數量
long tagCount = jedis.bitcount(userKey);
System.out.println("用户標籤數量: " + tagCount);
}
}
}
應用場景分析:
- 緩存系統:存儲序列化的對象、HTML 片段
- 計數器:網站訪問量、用户積分、商品庫存
- 分佈式鎖:基於 SETNX 實現互斥訪問
- 會話存儲:用户登錄狀態、臨時配置
- 位圖統計:用户標籤、活躍度統計
哈希(Hash):對象存儲的最佳選擇
哈希類型適合存儲對象,可以將多個字段組合在一個鍵中,減少鍵的數量,優化內存使用。
內存優化原理:
- 小哈希使用 ziplist 編碼,內存緊湊
- 字段數量少時,比多個字符串鍵更節省內存
- 適合存儲結構化數據
Java 操作實戰:
public class HashOperations extends RedisDataStructureBase {
/**
* 用户對象存儲示例
*/
public void userObjectStorage() {
try (Jedis jedis = jedisPool.getResource()) {
String userKey = "user:3001";
// 單個字段設置
jedis.hset(userKey, "name", "李四");
jedis.hset(userKey, "age", "28");
jedis.hset(userKey, "email", "lisi@example.com");
// 批量設置字段 - 性能更優
Map<String, String> userFields = new HashMap<>();
userFields.put("department", "技術部");
userFields.put("position", "高級工程師");
userFields.put("salary", "15000");
jedis.hmset(userKey, userFields);
// 獲取單個字段
String userName = jedis.hget(userKey, "name");
System.out.println("用户姓名: " + userName);
// 獲取多個字段
List<String> userInfo = jedis.hmget(userKey, "name", "age", "department");
System.out.println("用户信息: " + userInfo);
// 獲取所有字段 - 小心大對象
Map<String, String> allFields = jedis.hgetAll(userKey);
System.out.println("完整用户信息: " + allFields);
// 字段遞增 - 用户積分
Long newPoints = jedis.hincrBy(userKey, "points", 5);
System.out.println("新積分: " + newPoints);
}
}
/**
* 購物車實現
*/
public void shoppingCartExample() {
try (Jedis jedis = jedisPool.getResource()) {
String cartKey = "cart:user:4001";
// 添加商品到購物車
jedis.hset(cartKey, "product:1001", "2"); // 商品ID:1001, 數量:2
jedis.hset(cartKey, "product:1002", "1");
jedis.hset(cartKey, "product:1003", "3");
// 更新商品數量
jedis.hincrBy(cartKey, "product:1001", 1); // 增加1個
// 移除商品
jedis.hdel(cartKey, "product:1002");
// 獲取購物車商品數量
long itemCount = jedis.hlen(cartKey);
System.out.println("購物車商品種類: " + itemCount);
// 獲取購物車所有商品
Map<String, String> cartItems = jedis.hgetAll(cartKey);
System.out.println("購物車內容: " + cartItems);
}
}
/**
* 對象序列化工具方法
*/
public <T> void storeObject(String key, T obj, Class<T> clazz) {
try (Jedis jedis = jedisPool.getResource()) {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> fieldMap = mapper.convertValue(obj,
mapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
jedis.hmset(key, fieldMap);
} catch (Exception e) {
throw new RuntimeException("對象存儲失敗", e);
}
}
public <T> T getObject(String key, Class<T> clazz) {
try (Jedis jedis = jedisPool.getResource()) {
Map<String, String> fieldMap = jedis.hgetAll(key);
if (fieldMap.isEmpty()) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(fieldMap, clazz);
} catch (Exception e) {
throw new RuntimeException("對象獲取失敗", e);
}
}
}
應用場景分析:
- 用户信息存儲:用户屬性字段動態更新
- 購物車系統:商品 ID 和數量的映射
- 配置信息:應用的動態配置項
- 對象緩存:結構化數據的序列化存儲
- 計數器組:多個相關計數器的集合
列表(List):隊列與時間線的實現
Redis 列表基於雙向鏈表實現,支持從兩端快速插入和刪除,是實現隊列、棧和時間線的理想選擇。
數據結構特性:
- 最大元素數:2³² - 1 個
- 兩端操作時間複雜度 O(1)
- 支持阻塞操作,適合消息隊列
- 索引操作時間複雜度 O(n)
Java 操作實戰:
public class ListOperations extends RedisDataStructureBase {
/**
* 消息隊列實現
*/
public void messageQueueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String queueKey = "queue:notifications";
// 生產者:從左側推入消息
jedis.lpush(queueKey, "消息1: 用户註冊成功");
jedis.lpush(queueKey, "消息2: 訂單創建完成");
jedis.lpush(queueKey, "消息3: 支付成功");
// 消費者:從右側彈出消息
String message = jedis.rpop(queueKey);
while (message != null) {
System.out.println("處理消息: " + message);
message = jedis.rpop(queueKey);
}
}
}
/**
* 阻塞隊列 - 實時消息處理
*/
public void blockingQueueExample() {
String queueKey = "queue:real-time";
// 生產者線程
Thread producer = new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
for (int i = 0; i < 5; i++) {
jedis.lpush(queueKey, "實時消息_" + i);
System.out.println("生產消息: 實時消息_" + i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
});
// 消費者線程 - 阻塞等待
Thread consumer = new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
for (int i = 0; i < 5; i++) {
// 阻塞式彈出,最多等待10秒
List<String> messages = jedis.blpop(10, queueKey);
if (messages != null) {
System.out.println("消費消息: " + messages.get(1));
}
}
}
});
producer.start();
consumer.start();
}
/**
* 最新消息時間線
*/
public void timelineExample() {
try (Jedis jedis = jedisPool.getResource()) {
String timelineKey = "timeline:user:5001";
// 添加動態到時間線
jedis.lpush(timelineKey,
"{\"type\": \"post\", \"content\": \"發佈了新文章\", \"time\": \"2024-01-15 10:00:00\"}",
"{\"type\": \"like\", \"content\": \"點讚了文章\", \"time\": \"2024-01-15 09:30:00\"}",
"{\"type\": \"comment\", \"content\": \"發表了評論\", \"time\": \"2024-01-15 09:00:00\"}"
);
// 限制時間線長度,防止無限增長
jedis.ltrim(timelineKey, 0, 99); // 只保留最新100條
// 分頁獲取時間線
List<String> recentActivities = jedis.lrange(timelineKey, 0, 9);
System.out.println("最近10條動態: " + recentActivities);
// 獲取時間線長度
long timelineLength = jedis.llen(timelineKey);
System.out.println("時間線長度: " + timelineLength);
}
}
/**
* 棧(Stack)實現 - 後進先出
*/
public void stackExample() {
try (Jedis jedis = jedisPool.getResource()) {
String stackKey = "stack:operations";
// 壓棧操作
jedis.lpush(stackKey, "操作1", "操作2", "操作3");
// 彈棧操作
String operation = jedis.lpop(stackKey);
while (operation != null) {
System.out.println("執行操作: " + operation);
operation = jedis.lpop(stackKey);
}
}
}
}
應用場景分析:
- 消息隊列:系統間異步通信
- 時間線:用户動態、新聞流
- 操作日誌:用户操作記錄
- 任務隊列:後台任務處理
- 最新列表:最新文章、最新評論
集合(Set):唯一性與關係運算
Redis 集合存儲不重複的字符串元素,支持交集、並集、差集等關係運算,是處理唯一性和集合關係的利器。
性能特點:
- 添加、刪除、查找時間複雜度 O(1)
- 支持集合間運算
- 最大元素數:2³² - 1 個
- 內部實現:整數集合或哈希表
Java 操作實戰:
public class SetOperations extends RedisDataStructureBase {
/**
* 標籤系統實現
*/
public void tagSystemExample() {
try (Jedis jedis = jedisPool.getResource()) {
// 文章標籤
String articleTagsKey = "article:6001:tags";
jedis.sadd(articleTagsKey, "技術", "Redis", "數據庫", "高性能");
// 用户興趣標籤
String userInterestsKey = "user:6001:interests";
jedis.sadd(userInterestsKey, "技術", "編程", "Redis", "Java");
// 檢查文章是否包含某個標籤
boolean hasTechTag = jedis.sismember(articleTagsKey, "技術");
System.out.println("文章是否包含技術標籤: " + hasTechTag);
// 獲取共同興趣 - 集合交集
Set<String> commonTags = jedis.sinter(articleTagsKey, userInterestsKey);
System.out.println("共同標籤: " + commonTags);
// 獲取所有標籤 - 集合並集
Set<String> allTags = jedis.sunion(articleTagsKey, userInterestsKey);
System.out.println("所有標籤: " + allTags);
// 獲取文章特有標籤 - 集合差集
Set<String> articleOnlyTags = jedis.sdiff(articleTagsKey, userInterestsKey);
System.out.println("文章特有標籤: " + articleOnlyTags);
}
}
/**
* 好友關係與社交網絡
*/
public void socialNetworkExample() {
try (Jedis jedis = jedisPool.getResource()) {
String userAFriends = "user:7001:friends";
String userBFriends = "user:7002:friends";
// 添加好友
jedis.sadd(userAFriends, "7002", "7003", "7004");
jedis.sadd(userBFriends, "7001", "7005", "7006");
// 共同好友
Set<String> mutualFriends = jedis.sinter(userAFriends, userBFriends);
System.out.println("共同好友: " + mutualFriends);
// 可能認識的人 - 好友的好友
Set<String> potentialFriends = jedis.sdiff(userBFriends, userAFriends);
potentialFriends.remove("7001"); // 排除自己
System.out.println("可能認識的人: " + potentialFriends);
// 統計好友數量
long friendCount = jedis.scard(userAFriends);
System.out.println("用户A好友數量: " + friendCount);
// 隨機推薦好友
String randomFriend = jedis.srandmember(userAFriends);
System.out.println("隨機好友推薦: " + randomFriend);
}
}
/**
* 唯一值處理 - 用户投票系統
*/
public void uniqueValueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String votersKey = "poll:8001:voters";
// 用户投票 - 自動去重
long result1 = jedis.sadd(votersKey, "user:9001");
long result2 = jedis.sadd(votersKey, "user:9002");
long result3 = jedis.sadd(votersKey, "user:9001"); // 重複投票
System.out.println("第一次投票結果: " + (result1 == 1 ? "成功" : "失敗"));
System.out.println("第二次投票結果: " + (result2 == 1 ? "成功" : "失敗"));
System.out.println("第三次投票結果: " + (result3 == 1 ? "成功" : "失敗"));
// 獲取所有投票用户
Set<String> allVoters = jedis.smembers(votersKey);
System.out.println("所有投票用户: " + allVoters);
// 統計投票人數
long voterCount = jedis.scard(votersKey);
System.out.println("總投票人數: " + voterCount);
}
}
/**
* 抽獎系統 - 隨機抽取
*/
public void lotterySystemExample() {
try (Jedis jedis = jedisPool.getResource()) {
String participantsKey = "lottery:9001:participants";
// 添加參與者
jedis.sadd(participantsKey,
"user:10001", "user:10002", "user:10003",
"user:10004", "user:10005", "user:10006"
);
// 隨機抽取3名獲獎者(不重複)
List<String> winners = jedis.srandmember(participantsKey, 3);
System.out.println("獲獎用户: " + winners);
// 隨機抽取並移除(抽獎後移除)
String grandPrizeWinner = jedis.spop(participantsKey);
System.out.println("特等獎獲得者: " + grandPrizeWinner);
// 剩餘參與者數量
long remaining = jedis.scard(participantsKey);
System.out.println("剩餘參與者: " + remaining);
}
}
}
應用場景分析:
- 標籤系統:文章標籤、用户興趣
- 社交關係:好友列表、關注關係
- 唯一值處理:用户投票、訪問 IP 記錄
- 數據過濾:已讀文章、已處理任務
- 隨機抽樣:抽獎系統、AB 測試
有序集合(ZSet):排行榜與範圍查詢
有序集合為每個元素關聯一個分數(score),支持按分數排序和範圍查詢,是實現排行榜、延遲隊列的理想選擇。
排序原理:
- 跳躍表(skiplist)實現,查詢效率 O(logN)
- 元素按分數從小到大排序
- 分數可重複,元素唯一
- 支持按分數範圍、按排名範圍查詢
Java 操作實戰:
public class SortedSetOperations extends RedisDataStructureBase {
/**
* 遊戲排行榜實現
*/
public void leaderboardExample() {
try (Jedis jedis = jedisPool.getResource()) {
String leaderboardKey = "leaderboard:game:10001";
// 添加玩家分數
jedis.zadd(leaderboardKey, 1500.0, "player:1001");
jedis.zadd(leaderboardKey, 1800.5, "player:1002");
jedis.zadd(leaderboardKey, 2200.0, "player:1003");
jedis.zadd(leaderboardKey, 1900.0, "player:1004");
jedis.zadd(leaderboardKey, 2100.0, "player:1005");
// 更新玩家分數
jedis.zincrby(leaderboardKey, 100.0, "player:1001"); // 增加100分
// 獲取top3玩家
List<String> topPlayers = jedis.zrevrange(leaderboardKey, 0, 2);
System.out.println("排行榜前三名: " + topPlayers);
// 獲取玩家排名(從0開始,分數從高到低)
Long playerRank = jedis.zrevrank(leaderboardKey, "player:1001");
System.out.println("玩家1001的排名: " + (playerRank != null ? playerRank + 1 : "未上榜"));
// 獲取玩家分數
Double playerScore = jedis.zscore(leaderboardKey, "player:1001");
System.out.println("玩家1001的分數: " + playerScore);
// 獲取分數區間內的玩家(1800-2100分)
Set<String> rangePlayers = jedis.zrangeByScore(leaderboardKey, 1800, 2100);
System.out.println("1800-2100分數段玩家: " + rangePlayers);
}
}
/**
* 延遲隊列實現
*/
public void delayedQueueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String delayedQueueKey = "queue:delayed:tasks";
// 當前時間戳
long currentTime = System.currentTimeMillis();
// 添加延遲任務(分數為執行時間戳)
jedis.zadd(delayedQueueKey, currentTime + 5000, "task:process_order:1001"); // 5秒後執行
jedis.zadd(delayedQueueKey, currentTime + 10000, "task:send_email:1001"); // 10秒後執行
jedis.zadd(delayedQueueKey, currentTime + 15000, "task:cleanup_cache:1001"); // 15秒後執行
// 處理到期任務的工作線程
Thread worker = new Thread(() -> {
try (Jedis workerJedis = jedisPool.getResource()) {
while (!Thread.currentThread().isInterrupted()) {
long now = System.currentTimeMillis();
// 獲取所有到期的任務(分數小於等於當前時間)
Set<String> readyTasks = workerJedis.zrangeByScore(delayedQueueKey, 0, now);
for (String task : readyTasks) {
// 處理任務
System.out.println("執行任務: " + task + " at " + new Date(now));
// 從隊列中移除已處理任務
workerJedis.zrem(delayedQueueKey, task);
}
if (readyTasks.isEmpty()) {
// 沒有任務,休眠1秒
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
worker.start();
// 運行10秒後停止
try {
Thread.sleep(10000);
worker.interrupt();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* 時間序列數據 - 股票價格記錄
*/
public void timeSeriesExample() {
try (Jedis jedis = jedisPool.getResource()) {
String stockPriceKey = "stock:AAPL:prices";
// 記錄不同時間點的股價(時間戳作為分數)
long baseTime = System.currentTimeMillis();
jedis.zadd(stockPriceKey, baseTime, "150.25");
jedis.zadd(stockPriceKey, baseTime + 60000, "151.10"); // 1分鐘後
jedis.zadd(stockPriceKey, baseTime + 120000, "150.75"); // 2分鐘後
jedis.zadd(stockPriceKey, baseTime + 180000, "152.30"); // 3分鐘後
jedis.zadd(stockPriceKey, baseTime + 240000, "153.15"); // 4分鐘後
// 查詢最近3分鐘的股價數據
long threeMinutesAgo = baseTime + 180000; // 從開始時間算3分鐘後
Set<String> recentPrices = jedis.zrangeByScore(stockPriceKey, threeMinutesAgo, baseTime + 240000);
System.out.println("最近股價: " + recentPrices);
// 獲取股價範圍統計
long priceCount = jedis.zcount(stockPriceKey, 150.0, 152.0);
System.out.println("150-152價格區間的數據點數量: " + priceCount);
}
}
/**
* 帶權重的標籤系統
*/
public void weightedTagsExample() {
try (Jedis jedis = jedisPool.getResource()) {
String articleWeightedTags = "article:11001:weighted_tags";
// 添加標籤及權重(權重代表相關性強度)
jedis.zadd(articleWeightedTags, 0.9, "Redis");
jedis.zadd(articleWeightedTags, 0.7, "數據庫");
jedis.zadd(articleWeightedTags, 0.5, "緩存");
jedis.zadd(articleWeightedTags, 0.3, "NoSQL");
jedis.zadd(articleWeightedTags, 0.1, "開源");
// 獲取相關性最高的3個標籤
Set<String> topTags = jedis.zrevrange(articleWeightedTags, 0, 2);
System.out.println("最相關標籤: " + topTags);
// 按權重範圍查詢標籤
Set<String> strongTags = jedis.zrangeByScore(articleWeightedTags, 0.7, 1.0);
System.out.println("強相關標籤: " + strongTags);
// 增加標籤權重
jedis.zincrby(articleWeightedTags, 0.1, "Redis");
// 獲取標籤權重
Double redisWeight = jedis.zscore(articleWeightedTags, "Redis");
System.out.println("Redis標籤權重: " + redisWeight);
}
}
}
應用場景分析:
- 排行榜系統:遊戲分數、商品銷量、內容熱度
- 延遲隊列:定時任務、消息延遲投遞
- 時間序列:監控數據、股票價格、傳感器數據
- 帶權重標籤:內容相關性、用户興趣強度
- 範圍查詢:地理位置、價格區間、時間範圍
性能優化
數據結構選擇策略
| 業務需求 | 推薦數據結構 | 理由 |
|---|---|---|
| 簡單緩存 | 字符串 | 直接、高效 |
| 對象存儲 | 哈希 | 內存優化、字段操作 |
| 消息隊列 | 列表 | 順序性、阻塞操作 |
| 唯一集合 | 集合 | 去重、集合運算 |
| 排序需求 | 有序集合 | 自動排序、範圍查詢 |
內存優化技巧
public class MemoryOptimizationTips {
/**
* 小對象存儲優化
*/
public void smallObjectOptimization() {
try (Jedis jedis = jedisPool.getResource()) {
// 錯誤方式:大量小字符串鍵
// jedis.set("user:1001:name", "張三");
// jedis.set("user:1001:age", "25");
// jedis.set("user:1001:email", "zhangsan@example.com");
// 正確方式:使用哈希存儲小對象
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "張三");
userInfo.put("age", "25");
userInfo.put("email", "zhangsan@example.com");
jedis.hmset("user:1001", userInfo);
}
}
/**
* 大集合分片策略
*/
public void largeSetSharding() {
try (Jedis jedis = jedisPool.getResource()) {
String largeSetKey = "large:set";
int shardCount = 10;
// 添加元素時分配到不同的分片
for (int i = 0; i < 10000; i++) {
String element = "element_" + i;
int shardIndex = Math.abs(element.hashCode()) % shardCount;
String shardKey = largeSetKey + ":" + shardIndex;
jedis.sadd(shardKey, element);
}
// 查詢時檢查所有分片
String targetElement = "element_5000";
int targetShard = Math.abs(targetElement.hashCode()) % shardCount;
boolean exists = jedis.sismember(largeSetKey + ":" + targetShard, targetElement);
System.out.println("元素是否存在: " + exists);
}
}
}
案例:電商平台數據模型設計
public class ECommerceDataModel {
private JedisPool jedisPool;
public ECommerceDataModel(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 完整的電商數據模型示例
*/
public void completeEcommerceExample() {
try (Jedis jedis = jedisPool.getResource()) {
// 1. 用户信息 - 使用哈希
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "王五");
userInfo.put("level", "VIP");
userInfo.put("points", "1500");
jedis.hmset("user:12001", userInfo);
// 2. 商品信息 - 使用哈希
Map<String, String> productInfo = new HashMap<>();
productInfo.put("name", "iPhone 15");
productInfo.put("price", "5999");
productInfo.put("stock", "100");
productInfo.put("category", "electronics");
jedis.hmset("product:2001", productInfo);
// 3. 購物車 - 使用哈希(用户ID -> 商品ID:數量)
jedis.hset("cart:12001", "product:2001", "2");
jedis.hset("cart:12001", "product:2002", "1");
// 4. 商品分類索引 - 使用集合
jedis.sadd("category:electronics:products", "product:2001", "product:2002");
jedis.sadd("category:books:products", "product:3001", "product:3002");
// 5. 商品銷量排行榜 - 使用有序集合
jedis.zadd("leaderboard:products:sales", 150, "product:2001");
jedis.zadd("leaderboard:products:sales", 89, "product:2002");
jedis.zadd("leaderboard:products:sales", 203, "product:3001");
// 6. 用户瀏覽歷史 - 使用列表(最近瀏覽)
jedis.lpush("user:12001:history", "product:2001", "product:3002");
jedis.ltrim("user:12001:history", 0, 49); // 只保留最近50條
// 7. 用户收藏夾 - 使用集合
jedis.sadd("user:12001:favorites", "product:2001", "product:3001");
// 8. 庫存計數器 - 使用字符串
jedis.set("product:2001:stock", "100");
System.out.println("電商數據模型初始化完成");
}
}
/**
* 複雜的業務操作:用户下單
*/
public boolean placeOrder(String userId, String productId, int quantity) {
try (Jedis jedis = jedisPool.getResource()) {
// 使用事務保證原子性
jedis.watch("product:" + productId + ":stock", "user:" + userId + ":points");
// 檢查庫存
String stockStr = jedis.hget("product:" + productId, "stock");
if (stockStr == null || Integer.parseInt(stockStr) < quantity) {
jedis.unwatch();
return false; // 庫存不足
}
// 檢查用户積分
String pointsStr = jedis.hget("user:" + userId, "points");
int userPoints = pointsStr != null ? Integer.parseInt(pointsStr) : 0;
int requiredPoints = quantity * 10; // 假設每件商品需要10積分
if (userPoints < requiredPoints) {
jedis.unwatch();
return false; // 積分不足
}
// 開啓事務
Transaction transaction = jedis.multi();
try {
// 扣減庫存
transaction.hincrBy("product:" + productId, "stock", -quantity);
// 扣減積分
transaction.hincrBy("user:" + userId, "points", -requiredPoints);
// 增加銷量
transaction.zincrby("leaderboard:products:sales", quantity, productId);
// 生成訂單記錄
String orderId = "order:" + System.currentTimeMillis();
Map<String, String> orderInfo = new HashMap<>();
orderInfo.put("userId", userId);
orderInfo.put("productId", productId);
orderInfo.put("quantity", String.valueOf(quantity));
orderInfo.put("status", "created");
transaction.hmset(orderId, orderInfo);
// 添加到用户訂單列表
transaction.lpush("user:" + userId + ":orders", orderId);
// 提交事務
List<Object> results = transaction.exec();
return results != null; // null表示事務失敗
} catch (Exception e) {
transaction.discard();
throw e;
}
}
}
}
小結
| 業務場景 | 推薦數據結構 | 關鍵優勢 |
|---|---|---|
| 緩存數據 | 字符串 | 簡單高效,支持過期 |
| 用户信息 | 哈希 | 字段操作,內存優化 |
| 消息隊列 | 列表 | 順序性,阻塞操作 |
| 社交關係 | 集合 | 去重,集合運算 |
| 排行榜 | 有序集合 | 自動排序,範圍查詢 |