博客 / 詳情

返回

艾體寶乾貨 | Redis Java 開發系列#2 數據結構

前言

本篇讀者收益

  • 理解 Redis 五大數據結構的特性和適用場景
  • 掌握在 Java 中高效操作各種 Redis 數據結構的方法
  • 學會根據業務需求選擇合適的數據結構
  • 瞭解數據結構層面的性能優化技巧

先修要求

  • 已完成第一篇環境搭建
  • 瞭解 Redis 基本命令
  • 熟悉 Java 集合框架
  • 具備面向對象編程基礎

關鍵要點

  1. 字符串不僅是文本,更是計數器、緩存和分佈式鎖的基礎
  2. 哈希適合存儲對象,減少鍵數量,優化內存使用
  3. 列表實現隊列、棧和時間線,支持阻塞操作
  4. 集合處理唯一性、標籤系統和社交關係
  5. 有序集合構建排行榜、延遲隊列和範圍查詢

背景簡述

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);
        }
    }
}

應用場景分析​:

  1. 緩存系統​:存儲序列化的對象、HTML 片段
  2. 計數器​:網站訪問量、用户積分、商品庫存
  3. 分佈式鎖​:基於 SETNX 實現互斥訪問
  4. 會話存儲​:用户登錄狀態、臨時配置
  5. 位圖統計​:用户標籤、活躍度統計

哈希(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);
        }
    }
}

應用場景分析​:

  1. 用户信息存儲​:用户屬性字段動態更新
  2. 購物車系統​:商品 ID 和數量的映射
  3. 配置信息​:應用的動態配置項
  4. 對象緩存​:結構化數據的序列化存儲
  5. 計數器組​:多個相關計數器的集合

列表(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);
            }
        }
    }
}

應用場景分析​:

  1. 消息隊列​:系統間異步通信
  2. 時間線​:用户動態、新聞流
  3. 操作日誌​:用户操作記錄
  4. 任務隊列​:後台任務處理
  5. 最新列表​:最新文章、最新評論

集合(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);
        }
    }
}

應用場景分析​:

  1. 標籤系統​:文章標籤、用户興趣
  2. 社交關係​:好友列表、關注關係
  3. 唯一值處理​:用户投票、訪問 IP 記錄
  4. 數據過濾​:已讀文章、已處理任務
  5. 隨機抽樣​:抽獎系統、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);
        }
    }
}

應用場景分析​:

  1. 排行榜系統​:遊戲分數、商品銷量、內容熱度
  2. 延遲隊列​:定時任務、消息延遲投遞
  3. 時間序列​:監控數據、股票價格、傳感器數據
  4. 帶權重標籤​:內容相關性、用户興趣強度
  5. 範圍查詢​:地理位置、價格區間、時間範圍

性能優化

數據結構選擇策略

業務需求 推薦數據結構 理由
簡單緩存 字符串 直接、高效
對象存儲 哈希 內存優化、字段操作
消息隊列 列表 順序性、阻塞操作
唯一集合 集合 去重、集合運算
排序需求 有序集合 自動排序、範圍查詢

內存優化技巧

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;
            }
        }
    }
}

小結

業務場景 推薦數據結構 關鍵優勢
緩存數據 字符串 簡單高效,支持過期
用户信息 哈希 字段操作,內存優化
消息隊列 列表 順序性,阻塞操作
社交關係 集合 去重,集合運算
排行榜 有序集合 自動排序,範圍查詢
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.