在分佈式系統開發中,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 自動配置類和核心 APIcommons-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:
- 在啓動類添加
@EnableCaching開啓緩存支持 - 在 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 中(分數為時間戳)
- 每次請求前刪除過期令牌,統計當前令牌數是否超過限制
- 適用於接口限流、短信發送頻率控制等場景
五、注意事項與優化建議
- 緩存一致性:更新數據庫後需及時刪除對應緩存,或使用延遲雙刪策略,避免緩存髒數據
- 序列化問題:確保所有存儲到 Redis 的對象實現
Serializable接口,或使用 JSON 序列化(如本文配置) - 連接池優化:根據業務併發量調整連接池參數,避免連接數不足導致阻塞
- 緩存穿透 / 擊穿 / 雪崩:
- 穿透:使用布隆過濾器過濾無效鍵,或緩存空值
- 擊穿:熱點 key 設置永不過期,或使用互斥鎖
- 雪崩:緩存過期時間添加隨機值,避免同時過期
- Redis 集羣:生產環境建議使用 Redis 集羣(主從 + 哨兵或 Redis Cluster),提高可用性
總結
Spring Boot 與 Redis 的整合核心在於 RedisTemplate 的配置與使用,通過自動配置機制大幅降低了集成門檻。本文從環境搭建、配置優化、API 實操到場景落地,全面覆蓋了 Redis 在 Spring Boot 中的核心用法,包括字符串、哈希、列表等數據結構操作,以及緩存、分佈式鎖、限流等典型場景。
實際開發中,需根據業務需求選擇合適的 API 和數據結構,同時關注緩存一致性、性能優化等問題。Redis 的功能遠不止於此,後續還可探索 Redis 持久化、哨兵機制、Lua 腳本等高級特性,進一步提升系統性能與穩定性。