目錄
- 緩存穿透
- 緩存雪崩
- 緩存擊穿
- Redis 連接池耗盡
- Redis 序列化問題
- 總結
1. 緩存穿透
問題描述
緩存穿透是指查詢一個不存在的數據,由于緩存中沒有該數據,請求會直接打到數據庫上,導致數據庫壓力過大。
解決方案
- 緩存空值:即使查詢的數據不存在,也將空值緩存起來,并設置一個較短的過期時間。
- 布隆過濾器:在查詢緩存之前,先通過布隆過濾器判斷數據是否存在。
示例代碼
@Service
public class UserService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;public User getUserById(Long id) {String key = "user:" + id;// 從緩存中獲取數據User user = (User) redisTemplate.opsForValue().get(key);if (user != null) {return user;}// 緩存中不存在,查詢數據庫user = userRepository.findById(id).orElse(null);if (user == null) {// 緩存空值,設置較短的過期時間redisTemplate.opsForValue().set(key, null, 60, TimeUnit.SECONDS);} else {// 緩存查詢結果redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);}return user;}
}
2. 緩存雪崩
問題描述
緩存雪崩是指大量緩存數據在同一時間失效,導致所有請求都打到數據庫上,造成數據庫壓力過大甚至崩潰。
解決方案
- 設置不同的過期時間:為緩存數據設置隨機的過期時間,避免大量緩存同時失效。
- 使用分布式鎖:在緩存失效時,使用分布式鎖保證只有一個線程去加載數據。
示例代碼
@Service
public class ProductService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RedissonClient redissonClient;public Product getProductById(Long id) {String key = "product:" + id;Product product = (Product) redisTemplate.opsForValue().get(key);if (product != null) {return product;}// 使用分布式鎖防止緩存擊穿RLock lock = redissonClient.getLock("lock:" + key);try {lock.lock();// 雙重檢查,防止其他線程已經加載了數據product = (Product) redisTemplate.opsForValue().get(key);if (product != null) {return product;}// 查詢數據庫product = productRepository.findById(id).orElse(null);if (product != null) {// 設置隨機的過期時間int expireTime = 3600 + new Random().nextInt(600); // 1小時 + 隨機10分鐘redisTemplate.opsForValue().set(key, product, expireTime, TimeUnit.SECONDS);}} finally {lock.unlock();}return product;}
}
3. 緩存擊穿
問題描述
緩存擊穿是指某個熱點數據在緩存中失效后,大量請求同時打到數據庫上,導致數據庫壓力過大。
解決方案
- 使用互斥鎖:在緩存失效時,使用互斥鎖保證只有一個線程去加載數據。
- 永不過期策略:對熱點數據設置永不過期,通過后臺任務定期更新緩存。
示例代碼
@Service
public class HotDataService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;public String getHotData() {String key = "hot_data";String data = (String) redisTemplate.opsForValue().get(key);if (data != null) {return data;}// 使用 Redis 的 SETNX 實現互斥鎖String lockKey = "lock:" + key;boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);if (locked) {try {// 雙重檢查data = (String) redisTemplate.opsForValue().get(key);if (data != null) {return data;}// 模擬從數據庫加載熱點數據data = loadHotDataFromDB();redisTemplate.opsForValue().set(key, data, 1, TimeUnit.HOURS);} finally {// 釋放鎖redisTemplate.delete(lockKey);}} else {// 未獲取到鎖,等待重試try {Thread.sleep(100);return getHotData(); // 重試} catch (InterruptedException e) {Thread.currentThread().interrupt();}}return data;}private String loadHotDataFromDB() {// 模擬數據庫查詢return "hot_data_from_db";}
}
4. Redis 連接池耗盡
問題描述
在高并發場景下,Redis 連接池可能會被耗盡,導致請求失敗。
解決方案
- 增加連接池大小:根據實際需求調整連接池的最大連接數。
- 優化連接使用:確保每次操作 Redis 后及時釋放連接。
示例代碼
在 application.yml
中配置連接池:
spring:redis:host: localhostport: 6379lettuce:pool:max-active: 50 # 最大連接數max-idle: 10 # 最大空閑連接數min-idle: 5 # 最小空閑連接數
5. Redis 序列化問題
問題描述
默認情況下,Spring Boot 使用 JdkSerializationRedisSerializer 進行序列化,可能導致存儲的數據不易閱讀或兼容性問題。
解決方案
使用更高效的序列化方式,如 Jackson2JsonRedisSerializer
或 StringRedisSerializer
。
示例代碼
@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);// 使用 Jackson2JsonRedisSerializer 序列化值Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);template.setValueSerializer(serializer);template.setHashValueSerializer(serializer);// 使用 StringRedisSerializer 序列化鍵template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());return template;}
}
6. 總結
在 Spring Boot 項目中使用 Redis 時,可能會遇到緩存穿透、緩存雪崩、緩存擊穿、連接池耗盡以及序列化等問題。通過合理的緩存策略、分布式鎖、連接池配置和序列化方式,可以有效解決這些問題,提升系統的穩定性和性能。希望本文的解決方案和示例代碼能幫助到你!