在分佈式系統開發中,Redis 憑藉其高性能、多數據結構的特性,成為緩存、分佈式鎖、限流等場景的首選中間件。Spring Boot 作為主流的 Java 開發框架,通過自動配置機制簡化了 Redis 的集成流程,讓開發者無需關注複雜的底層實現,即可快速上手。本文將從環境準備、核心配置、API 實操、典型場景四個維度,詳細講解 Spring Boot 中 Redis 的使用方法,幫助開發者快速落地實戰。

一、環境準備:搭建基礎開發環境

1.1 安裝 Redis 服務

Redis 支持 Windows、Linux、Mac 多平台部署,推薦使用 Docker 快速搭建(避免環境配置衝突):

  • 拉取 Redis 鏡像:docker pull redis:6.2.6(選擇穩定版)
  • 啓動容器:docker run -d -p 6379:6379 --name redis-demo redis --requirepass "123456"
  • 暴露 6379 端口,設置密碼 123456,容器名稱為 redis-demo
  • 驗證連接:使用 Redis 客户端執行 redis-cli -h localhost -p 6379 -a 123456,輸入 ping 返回 PONG 即成功。

1.2 項目依賴配置

創建 Spring Boot 項目(推薦 2.7.x 版本,兼容性更優),在 pom.xml 中引入 Redis 核心依賴:

xml

<!-- Spring Boot Redis  starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 連接池依賴(Spring Boot 2.x 默認使用 Lettuce) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- 測試依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • spring-boot-starter-data-redis 包含 Redis 自動配置類和核心 API
  • commons-pool2 提供連接池支持,優化 Redis 連接性能
  • 若需切換為 Jedis 客户端,排除 Lettuce 依賴並引入 Jedis 即可。

二、核心配置:自定義 Redis 連接與序列化

2.1 基礎連接配置

在 application.yml 中配置 Redis 連接信息,覆蓋默認自動配置:

yaml

spring:
  redis:
    # 連接信息
    host: localhost
    port: 6379
    password: 123456
    database: 0 # 選擇第 0 個數據庫(Redis 默認 16 個數據庫)
    # 連接池配置(Lettuce)
    lettuce:
      pool:
        max-active: 8 # 最大連接數
        max-idle: 8 # 最大空閒連接
        min-idle: 2 # 最小空閒連接
        max-wait: 1000ms # 連接等待超時時間
    timeout: 5000ms # 命令執行超時時間
  • 數據庫索引 database 用於隔離不同業務數據,避免鍵名衝突
  • 連接池參數需根據業務壓力調整,避免連接泄露或資源浪費

2.2 序列化配置(關鍵優化)

Spring Boot 默認使用 JdkSerializationRedisSerializer 序列化對象,存在可讀性差、佔用空間大的問題。推薦自定義序列化配置,使用 Jackson2JsonRedisSerializer 實現 JSON 序列化:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // JSON 序列化配置
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 支持 Java 8 時間類型(LocalDateTime 等)
        objectMapper.registerModule(new JavaTimeModule());
        // 序列化時包含對象類型信息(避免反序列化失敗)
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(objectMapper);

        // 字符串序列化配置(鍵名使用 String 序列化)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // 配置序列化方式
        template.setKeySerializer(stringSerializer); // 鍵序列化
        template.setValueSerializer(jacksonSerializer); // 值序列化
        template.setHashKeySerializer(stringSerializer); // 哈希鍵序列化
        template.setHashValueSerializer(jacksonSerializer); // 哈希值序列化

        template.afterPropertiesSet();
        return template;
    }
}
  • 鍵名使用 StringRedisSerializer,確保鍵名可讀性
  • 值使用 JSON 序列化,支持複雜對象和時間類型,且序列化後的數據可直接通過 Redis 客户端查看
  • 若僅需操作字符串類型,可直接使用 StringRedisTemplate(默認 String 序列化,無需額外配置)

三、API 實操:Redis 核心數據結構操作

Spring Boot 提供 RedisTemplate 和 StringRedisTemplate 兩大核心 API,前者支持任意類型對象,後者專注字符串操作。以下結合 Redis 五大核心數據結構,講解常用操作。

3.1 字符串(String):最基礎的鍵值存儲

字符串是 Redis 最常用的數據結構,適用於緩存單個對象、計數器等場景:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class RedisStringTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testStringOps() {
        // 1. 存入鍵值對(無過期時間)
        redisTemplate.opsForValue().set("user:name", "張三");

        // 2. 存入鍵值對(設置 1 小時過期)
        redisTemplate.opsForValue().set("user:age", 25, 1, TimeUnit.HOURS);

        // 3. 獲取值
        String name = (String) redisTemplate.opsForValue().get("user:name");
        Integer age = (Integer) redisTemplate.opsForValue().get("user:age");
        System.out.println("姓名:" + name + ",年齡:" + age);

        // 4. 自增(計數器場景)
        redisTemplate.opsForValue().increment("article:view:1001", 1); // 閲讀量 +1
        Long viewCount = (Long) redisTemplate.opsForValue().get("article:view:1001");
        System.out.println("文章 1001 閲讀量:" + viewCount);

        // 5. 批量操作
        redisTemplate.opsForValue().multiSet(new HashMap<String, Object>() {{
            put("user:gender", "男");
            put("user:city", "北京");
        }});
        List<Object> multiGet = redisTemplate.opsForValue().multiGet(Arrays.asList("user:gender", "user:city"));
        System.out.println("批量獲取結果:" + multiGet);
    }
}

3.2 哈希(Hash):適合存儲對象屬性

哈希結構以鍵值對集合形式存儲數據,適用於緩存對象的多個屬性(如用户信息、商品詳情),支持單獨修改某個屬性:

@Test
public void testHashOps() {
    // 1. 存入哈希數據(用户 ID 為 1001 的信息)
    HashOperations<String, Object, Object> hashOps = redisTemplate.opsForHash();
    hashOps.put("user:1001", "name", "李四");
    hashOps.put("user:1001", "age", 30);
    hashOps.put("user:1001", "email", "lisi@xxx.com");

    // 2. 獲取單個屬性
    String email = (String) hashOps.get("user:1001", "email");
    System.out.println("用户 1001 郵箱:" + email);

    // 3. 獲取所有屬性
    Map<Object, Object> userMap = hashOps.entries("user:1001");
    System.out.println("用户 1001 完整信息:" + userMap);

    // 4. 批量存入屬性
    Map<Object, Object> newAttrs = new HashMap<>();
    newAttrs.put("gender", "男");
    newAttrs.put("city", "上海");
    hashOps.putAll("user:1001", newAttrs);

    // 5. 刪除某個屬性
    hashOps.delete("user:1001", "email");
}

3.3 列表(List):有序集合,支持隊列 / 棧操作

Redis 列表是有序的字符串集合,基於雙向鏈表實現,適用於消息隊列、最新消息排行等場景:

@Test
public void testListOps() {
    ListOperations<String, Object> listOps = redisTemplate.opsForList();
    String key = "message:queue";

    // 1. 左進(隊列:先進先出)
    listOps.leftPush(key, "消息1");
    listOps.leftPush(key, "消息2");
    listOps.leftPush(key, "消息3");

    // 2. 右出(獲取並移除隊尾元素)
    Object msg1 = listOps.rightPop(key);
    Object msg2 = listOps.rightPop(key);
    System.out.println("消費消息:" + msg1 + "," + msg2);

    // 3. 獲取列表範圍(0 到 -1 表示所有元素)
    List<Object> allMsg = listOps.range(key, 0, -1);
    System.out.println("剩餘消息:" + allMsg);

    // 4. 棧操作(左進左出)
    listOps.leftPush("stack", "元素A");
    listOps.leftPush("stack", "元素B");
    Object stackMsg1 = listOps.leftPop("stack");
    System.out.println("棧彈出元素:" + stackMsg1);

    // 5. 設置列表長度
    listOps.trim(key, 0, 0); // 只保留第一個元素
}

3.4 集合(Set):無序去重集合

Set 是無序、不重複的字符串集合,支持交集、並集、差集運算,適用於標籤、好友關係等場景:

@Test
public void testSetOps() {
    SetOperations<String, Object> setOps = redisTemplate.opsForSet();

    // 1. 向集合添加元素
    setOps.add("user:tags:1001", "Java", "Redis", "Spring Boot");
    setOps.add("user:tags:1002", "Java", "MySQL", "Docker");

    // 2. 獲取集合所有元素
    Set<Object> tags1001 = setOps.members("user:tags:1001");
    System.out.println("用户 1001 標籤:" + tags1001);

    // 3. 交集(共同標籤)
    Set<Object> commonTags = setOps.intersect("user:tags:1001", "user:tags:1002");
    System.out.println("共同標籤:" + commonTags);

    // 4. 並集(所有標籤)
    Set<Object> allTags = setOps.union("user:tags:1001", "user:tags:1002");
    System.out.println("所有標籤:" + allTags);

    // 5. 差集(用户 1001 獨有的標籤)
    Set<Object> diffTags = setOps.difference("user:tags:1001", "user:tags:1002");
    System.out.println("用户 1001 獨有標籤:" + diffTags);

    // 6. 移除元素
    setOps.remove("user:tags:1001", "Redis");
}

3.5 有序集合(ZSet):帶分數的有序集合

ZSet 是有序集合,每個元素關聯一個分數(score),按分數排序,適用於排行榜、限流等場景:

@Test
public void testZSetOps() {
    ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();
    String key = "article:rank";

    // 1. 添加元素(分數為文章閲讀量)
    zSetOps.add(key, "文章1001", 100);
    zSetOps.add(key, "文章1002", 200);
    zSetOps.add(key, "文章1003", 150);

    // 2. 按分數升序排列(0 到 -1 表示所有元素)
    Set<Object> ascRank = zSetOps.range(key, 0, -1);
    System.out.println("閲讀量升序排行:" + ascRank);

    // 3. 按分數降序排列(排行榜常用)
    Set<Object> descRank = zSetOps.reverseRange(key, 0, -1);
    System.out.println("閲讀量降序排行:" + descRank);

    // 4. 增加元素分數(閲讀量 +50)
    zSetOps.incrementScore(key, "文章1001", 50);

    // 5. 獲取元素分數
    Double score = zSetOps.score(key, "文章1001");
    System.out.println("文章1001 最新閲讀量:" + score);

    // 6. 獲取元素排名(降序,從 0 開始)
    Long rank = zSetOps.reverseRank(key, "文章1001");
    System.out.println("文章1001 排名:第" + (rank + 1) + "名");
}

四、典型場景落地:從理論到實踐

4.1 緩存實現(@Cacheable 註解)

Spring Cache 整合 Redis 可快速實現緩存功能,無需手動調用 RedisTemplate

  1. 在啓動類添加 @EnableCaching 開啓緩存支持
  2. 在 Service 方法上添加緩存註解:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 查詢用户時緩存結果,key 為 "user:id:{id}"
    @Cacheable(value = "user", key = "'user:id:' + #id", unless = "#result == null")
    public User getUserById(Long id) {
        // 模擬數據庫查詢(實際開發中替換為 DAO 操作)
        System.out.println("查詢數據庫,用户 ID:" + id);
        return new User(id, "張三", 25, "北京");
    }

    // 更新用户時刪除緩存
    @CacheEvict(value = "user", key = "'user:id:' + #user.id")
    public void updateUser(User user) {
        // 模擬數據庫更新
        System.out.println("更新數據庫,用户:" + user);
    }
}
  • @Cacheable:查詢時先查緩存,緩存不存在則執行方法並緩存結果
  • @CacheEvict:更新 / 刪除數據時刪除對應緩存,避免緩存不一致
  • value 為緩存名稱,key 為緩存鍵名(支持 SpEL 表達式)

4.2 分佈式鎖(基於 Redis 實現)

分佈式系統中,使用 Redis 可實現簡單高效的分佈式鎖,解決併發搶佔資源問題:

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 鎖的過期時間(避免死鎖)
    private static final long LOCK_EXPIRE = 30000;
    // 鎖的等待時間(避免長時間阻塞)
    private static final long LOCK_WAIT = 5000;

    /**
     * 獲取分佈式鎖
     */
    public boolean tryLock(String lockKey, String lockValue) {
        long start = System.currentTimeMillis();
        try {
            while (true) {
                // 使用 SET NX EX 命令原子性獲取鎖
                Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
                if (success != null && success) {
                    return true; // 獲取鎖成功
                }
                // 等待一段時間後重試
                Thread.sleep(50);
                // 超過等待時間則放棄
                if (System.currentTimeMillis() - start > LOCK_WAIT) {
                    return false;
                }
            }
        } catch (InterruptedException e) {
            return false;
        }
    }

    /**
     * 釋放分佈式鎖(使用 Lua 腳本保證原子性)
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
        return result != null && result > 0;
    }
}
  • 使用 setIfAbsent 命令(NX + EX)原子性獲取鎖,避免併發問題
  • 釋放鎖時通過 Lua 腳本驗證鎖值,確保只有鎖持有者能釋放
  • 鎖設置過期時間,防止服務宕機導致死鎖

4.3 接口限流(令牌桶算法)

基於 Redis ZSet 可實現令牌桶限流,控制接口單位時間內的訪問次數:

j

@Component
public class RedisRateLimiter {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 限流判斷
     * @param key 限流鍵(如用户 ID、接口路徑)
     * @param limit 單位時間內最大訪問次數
     * @param period 時間週期(秒)
     * @return 是否允許訪問
     */
    public boolean isAllowed(String key, int limit, int period) {
        String redisKey = "rate:limiter:" + key;
        long now = System.currentTimeMillis();
        ZSetOperations<String, Object> zSetOps = redisTemplate.opsForZSet();

        // 1. 刪除過期的令牌
        zSetOps.removeRangeByScore(redisKey, 0, now - period * 1000);

        // 2. 統計當前令牌數
        Long count = zSetOps.zCard(redisKey);
        if (count != null && count < limit) {
            // 3. 新增令牌(分數為當前時間戳)
            zSetOps.add(redisKey, now, now);
            // 4. 設置鍵過期時間(避免垃圾數據)
            redisTemplate.expire(redisKey, period, TimeUnit.SECONDS);
            return true;
        }
        return false;
    }
}
  • 每個訪問請求對應一個令牌,存儲在 ZSet 中(分數為時間戳)
  • 每次請求前刪除過期令牌,統計當前令牌數是否超過限制
  • 適用於接口限流、短信發送頻率控制等場景

五、注意事項與優化建議

  1. 緩存一致性:更新數據庫後需及時刪除對應緩存,或使用延遲雙刪策略,避免緩存髒數據
  2. 序列化問題:確保所有存儲到 Redis 的對象實現 Serializable 接口,或使用 JSON 序列化(如本文配置)
  3. 連接池優化:根據業務併發量調整連接池參數,避免連接數不足導致阻塞
  4. 緩存穿透 / 擊穿 / 雪崩
  • 穿透:使用布隆過濾器過濾無效鍵,或緩存空值
  • 擊穿:熱點 key 設置永不過期,或使用互斥鎖
  • 雪崩:緩存過期時間添加隨機值,避免同時過期
  1. Redis 集羣:生產環境建議使用 Redis 集羣(主從 + 哨兵或 Redis Cluster),提高可用性

總結

Spring Boot 與 Redis 的整合核心在於 RedisTemplate 的配置與使用,通過自動配置機制大幅降低了集成門檻。本文從環境搭建、配置優化、API 實操到場景落地,全面覆蓋了 Redis 在 Spring Boot 中的核心用法,包括字符串、哈希、列表等數據結構操作,以及緩存、分佈式鎖、限流等典型場景。

實際開發中,需根據業務需求選擇合適的 API 和數據結構,同時關注緩存一致性、性能優化等問題。Redis 的功能遠不止於此,後續還可探索 Redis 持久化、哨兵機制、Lua 腳本等高級特性,進一步提升系統性能與穩定性。