最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot自定義Redis代碼實(shí)踐指南

 更新時(shí)間:2025年12月30日 10:20:15   作者:czlczl20020925  
文章主要介紹了如何通過自定義RedisCacheConfiguration和RedisCacheManager來解決Spring Boot整合Redis過程中出現(xiàn)的序列化問題、Key命名冗余和缺乏過期限制等問題,通過這些定制,可以實(shí)現(xiàn)規(guī)范化的緩存存儲(chǔ)策略,感興趣的朋友跟隨小編一起看看吧

RedisCacheConfiguration 的全局定制

在 Spring Boot 整合 Redis 的過程中,默認(rèn)的緩存配置(RedisCacheConfiguration)雖然開箱即用,但在生產(chǎn)環(huán)境中往往存在以下痛點(diǎn):

  1. 數(shù)據(jù)不可讀:默認(rèn)使用 JDK 序列化,Redis 中存儲(chǔ)的是二進(jìn)制亂碼。
  2. Key 命名冗余:默認(rèn)使用雙冒號(hào) :: 分隔,導(dǎo)致在 Redis 可視化工具中出現(xiàn)空文件夾層級。
  3. 缺乏過期限制:默認(rèn)緩存永不過期,可能導(dǎo)致 Redis 內(nèi)存堆積。

本章將詳細(xì)解析如何通過自定義 RedisCacheConfiguration Bean,一次性解決上述問題,實(shí)現(xiàn)規(guī)范化的緩存存儲(chǔ)策略。

一、 RedisCacheConfiguration 的核心職責(zé)

RedisCacheConfiguration 是 Spring Data Redis 提供的一個(gè)不可變(Immutable)配置類。它的核心職責(zé)是定義緩存數(shù)據(jù)的屬性格式。

由于它是不可變的,任何修改方法(如 entryTtl)都不會(huì)修改當(dāng)前對象,而是返回一個(gè)新的配置實(shí)例。我們主要通過以下四個(gè)維度進(jìn)行定制:

  1. 序列化策略 (Serialization):Key 和 Value 的二進(jìn)制轉(zhuǎn)換規(guī)則。
  2. 前綴策略 (Prefixing):Key 的命名空間生成規(guī)則。
  3. 生存時(shí)間 (TTL):全局默認(rèn)的過期時(shí)間。
  4. 空值處理 (Null Handling):防止緩存穿透的策略。

二、 核心定制點(diǎn)詳解

1. 序列化定制:從 JDK 二進(jìn)制到 JSON

Spring 默認(rèn)使用 JdkSerializationRedisSerializer

  • 缺點(diǎn):生成的 Value 是 \xac\xed\x00\x05... 這樣的二進(jìn)制流,無法人工閱讀,且 Java 強(qiáng)依賴,這就限制了其他語言(如 Python/Go)讀取該緩存。
  • 解決:通常替換為 GenericJackson2JsonRedisSerializer,將對象存儲(chǔ)為標(biāo)準(zhǔn)的 JSON 字符串。需要注意處理 LocalDateTime 等 Java 8 時(shí)間類型的序列化問題。

2. 前綴策略定制:去除雙冒號(hào)

Spring 默認(rèn)生成的 Key 格式為 CacheName::Key。

  • 缺點(diǎn):雙冒號(hào) :: 在 Redis Desktop Manager 等工具中會(huì)被解析為兩層目錄,其中一層是空的,視覺效果差。
  • 解決:通過 computePrefixWith 方法,將分隔符修改為單冒號(hào) :,例如 CacheName:Key

3. 過期時(shí)間定制

默認(rèn)配置是 Duration.ZERO(永不過期)。建議在全局配置中設(shè)置一個(gè)合理的默認(rèn)值(如 1 小時(shí)),作為兜底策略。

三、 完整代碼實(shí)踐

下面是一個(gè)生產(chǎn)級可用的配置示例。將此代碼放入你的 Spring Boot 項(xiàng)目的配置包下即可生效。

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching // 開啟緩存注解支持
public class RedisConfig {
    /**
     * 自定義 RedisCacheConfiguration
     * 作用:定制 Key/Value 的序列化方式、過期時(shí)間、Key 前綴格式
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        // 1. 構(gòu)造 JSON 序列化器
        // 使用 GenericJackson2JsonRedisSerializer 替換默認(rèn)序列化
        GenericJackson2JsonRedisSerializer jsonSerializer = buildJsonSerializer();
        // 2. 構(gòu)建配置
        return RedisCacheConfiguration.defaultCacheConfig()
                // 【核心 1】設(shè)置默認(rèn)過期時(shí)間:1 小時(shí)
                .entryTtl(Duration.ofHours(1))
                // 【核心 2】定制 Key 的序列化:使用 String (默認(rèn)就是 String,顯式指定更清晰)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 【核心 3】定制 Value 的序列化:使用 JSON
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer))
                // 【核心 4】定制 Key 前綴策略:CacheName + ":" + Key
                // 默認(rèn)是 "::",這里改為 ":",避免 RDM 工具出現(xiàn)空目錄
                .computePrefixWith(name -> name + ":")
                // 【可選】禁止緩存 Null 值 (根據(jù)業(yè)務(wù)需求決定是否開啟)
                // .disableCachingNullValues()
                ;
    }
    /**
     * 輔助方法:構(gòu)建支持 Java8 時(shí)間和類型信息的 JSON 序列化器
     */
    private GenericJackson2JsonRedisSerializer buildJsonSerializer() {
        ObjectMapper objectMapper = new ObjectMapper();
        // 1. 注冊 JavaTimeModule,解決 LocalDateTime 等時(shí)間類序列化問題
        objectMapper.registerModule(new JavaTimeModule());
        // 2. 啟用多態(tài)類型驗(yàn)證,將類信息 (@class) 寫入 JSON
        // 這樣從 Redis 讀出來時(shí),才能自動(dòng)轉(zhuǎn)回具體的 Java 對象,而不僅僅是 Map
        objectMapper.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY
        );
        return new GenericJackson2JsonRedisSerializer(objectMapper);
    }
}

四、 配置前后的效果對比

假設(shè)我們有一個(gè) @Cacheable(value = "users", key = "1001") 的緩存操作,存儲(chǔ)一個(gè) User 對象。

1. 修改前(默認(rèn)配置)

  • Redis Key: users::1001 (雙冒號(hào))
  • Redis Value: \xac\xed\x00\x05sr\x00\x1bcom.example.User... (二進(jìn)制亂碼)
  • TTL: -1 (永不過期)

2. 修改后(自定義配置)

  • Redis Key: users:1001 (單冒號(hào),目錄結(jié)構(gòu)清晰)
  • Redis Value:
{
  "@class": "com.example.User",
  "id": 1001,
  "username": "admin",
  "createTime": [2023, 10, 1, 12, 0, 0]
}

(標(biāo)準(zhǔn) JSON,可讀性強(qiáng),其他語言可讀)

  • TTL: 3600 (1小時(shí)后自動(dòng)刪除)

進(jìn)階定制 RedisCacheManager實(shí)現(xiàn)動(dòng)態(tài)過期時(shí)間

在上一章中,我們通過 RedisCacheConfiguration 解決了序列化和 Key 命名格式的問題。然而,在實(shí)際生產(chǎn)環(huán)境中,我們面臨一個(gè)新的挑戰(zhàn):緩存的生命周期管理。

默認(rèn)的 RedisCacheManager 雖然支持設(shè)置全局過期時(shí)間(例如統(tǒng)一 1 小時(shí)),但業(yè)務(wù)場景往往更加復(fù)雜:

  • 驗(yàn)證碼:需要 5 分鐘過期。
  • 用戶會(huì)話:需要 30 分鐘過期。
  • 首頁廣告:需要 24 小時(shí)過期。
  • 系統(tǒng)參數(shù):可能需要永不過期。

如果使用 Spring 原生方式,我們需要在配置類中構(gòu)建一個(gè)巨大的 Map<String, RedisCacheConfiguration> 來一一指定,這既繁瑣又難以維護(hù)。

本章將介紹如何通過繼承并擴(kuò)展 RedisCacheManager,實(shí)現(xiàn)基于注解后綴的動(dòng)態(tài)過期時(shí)間控制(例如:@Cacheable(value = "users#30m"))。

一、 核心思路:重寫工廠方法

RedisCacheManager 的本質(zhì)是一個(gè)工廠(Factory),負(fù)責(zé)生產(chǎn) RedisCache 對象。其核心方法是 createRedisCache(String name, RedisCacheConfiguration cacheConfig)。

我們的改造思路遵循 “約定優(yōu)于配置” 的原則:

  1. 攔截:在創(chuàng)建緩存實(shí)例前,攔截傳入的 name(緩存名稱)。
  2. 解析:判斷 name 中是否包含特定的分隔符(如 #),提取時(shí)間信息。
  3. 定制:如果包含時(shí)間信息,則基于全局配置衍生出一個(gè)新的配置對象,并修改其 TTL。
  4. 還原:將 name 還原為干凈的業(yè)務(wù)名稱(去除時(shí)間后綴),創(chuàng)建緩存實(shí)例。

二、 代碼實(shí)現(xiàn)

下面是一個(gè)生產(chǎn)級的 TimeoutRedisCacheManager 實(shí)現(xiàn)。它支持 d (天), h (小時(shí)), m (分鐘), s (秒) 等時(shí)間單位。

1. 自定義 Manager 類

import cn.hutool.core.util.StrUtil; // 示例使用了 Hutool 工具類,也可使用原生 String 方法
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
public class TimeoutRedisCacheManager extends RedisCacheManager {
    private static final String SPLIT_FLAG = "#";
    public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }
    /**
     * 核心重寫方法:在創(chuàng)建 Cache 對象時(shí)介入
     */
    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        // 1. 如果名稱為空或不包含分隔符,直接使用父類邏輯(應(yīng)用全局默認(rèn)配置)
        if (StrUtil.isEmpty(name) || !name.contains(SPLIT_FLAG)) {
            return super.createRedisCache(name, cacheConfig);
        }
        // 2. 切割字符串
        // 格式約定:cacheName#ttl,例如 "system_users#30m"
        String[] parts = name.split(SPLIT_FLAG);
        if (parts.length != 2) {
            return super.createRedisCache(name, cacheConfig);
        }
        // 3. 提取真實(shí)緩存名和時(shí)間字符串
        String realName = parts[0]; // "system_users"
        String ttlStr = parts[1];   // "30m"
        // 4. 解析時(shí)間,并修改配置
        // 注意:RedisCacheConfiguration 是不可變的,entryTtl 會(huì)返回新實(shí)例
        Duration duration = parseDuration(ttlStr);
        if (cacheConfig != null) {
            cacheConfig = cacheConfig.entryTtl(duration);
        }
        // 5. 使用真實(shí)的名稱和定制后的配置創(chuàng)建緩存
        // 這樣 Redis 中的 Key 就是 "prefix:system_users:key",不會(huì)帶上 "#30m"
        return super.createRedisCache(realName, cacheConfig);
    }
    /**
     * 輔助方法:解析時(shí)間字符串
     * 支持格式:1d, 4h, 30m, 60s
     */
    private Duration parseDuration(String ttlStr) {
        String timeUnit = StrUtil.subSuf(ttlStr, -1).toLowerCase(); // 獲取最后一位單位
        long timeValue = Long.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1)); // 獲取數(shù)值
        switch (timeUnit) {
            case "d":
                return Duration.ofDays(timeValue);
            case "h":
                return Duration.ofHours(timeValue);
            case "m":
                return Duration.ofMinutes(timeValue);
            case "s":
                return Duration.ofSeconds(timeValue);
            default:
                // 默認(rèn)單位為秒,或者根據(jù)業(yè)務(wù)需求拋出異常
                return Duration.ofSeconds(Long.parseLong(ttlStr));
        }
    }
}

2. 注冊 Bean

將自定義的 Manager 注冊到 Spring 容器中,替換默認(rèn)的 CacheManager。

@Bean
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
                                           RedisCacheConfiguration redisCacheConfiguration) {
    // 1. 獲取連接工廠
    RedisConnectionFactory factory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
    // 2. 創(chuàng)建無鎖的 CacheWriter
    // BatchStrategies.scan(1000) 是為了在使用 evict(allEntries=true) 時(shí)
    // 使用 SCAN 命令代替 KEYS 命令,防止阻塞 Redis 主線程
    RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory,
            BatchStrategies.scan(1000));
    // 3. 返回自定義的 TimeoutRedisCacheManager
    return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
}

三、 使用效果演示

完成上述配置后,開發(fā)人員在編寫業(yè)務(wù)代碼時(shí),可以極其靈活地控制緩存時(shí)間:

場景 A:使用默認(rèn)時(shí)間

// 不帶后綴,使用 RedisCacheConfiguration 配置的全局默認(rèn)時(shí)間(如 1 小時(shí))
@Cacheable(value = "system_dict", key = "#type")
public List<Dict> getDictByType(String type) { ... }
  • Redis Key: system_dict:sex
  • TTL: 1 小時(shí)

場景 B:自定義短緩存

// 后綴 #60s,指定 60 秒過期
@Cacheable(value = "sms_code#60s", key = "#mobile")
public String getVerifyCode(String mobile) { ... }
  • Redis Key: sms_code:13800138000 (注意:后綴被去除了)
  • TTL: 60 秒

場景 C:自定義長緩存

// 后綴 #1d,指定 1 天過期
@Cacheable(value = "daily_ranking#1d", key = "#date")
public Ranking getDailyRanking(String date) { ... }
  • Redis Key: daily_ranking:20231001
  • TTL: 24 小時(shí)

深度定制 RedisCache 實(shí)現(xiàn)多級緩存與監(jiān)控

在掌握了 RedisCacheConfiguration(定制屬性)和 RedisCacheManager(定制策略)之后,我們來到了 Spring Cache 定制的“深水區(qū)”——自定義 RedisCache。

前兩者的定制局限于“配置”層面(數(shù)據(jù)存什么格式、存多久),而自定義 RedisCache 則是直接介入緩存的運(yùn)行時(shí)行為(Runtime Behavior)。

當(dāng)你遇到以下需求時(shí),必須重寫 RedisCache

  1. 多級緩存:在訪問 Redis 前,先訪問本地堆內(nèi)存(Caffeine/HashMap),以減少網(wǎng)絡(luò) IO。
  2. 讀寫分離/雙寫:寫入緩存時(shí)需要同步寫入其他存儲(chǔ)介質(zhì)。
  3. 埋點(diǎn)監(jiān)控:需要精確統(tǒng)計(jì)緩存的命中率、讀取耗時(shí),并上報(bào)監(jiān)控系統(tǒng)(如 Prometheus)。
  4. 容錯(cuò)降級:當(dāng) Redis 宕機(jī)時(shí),不拋出異常,而是記錄日志并回源查詢數(shù)據(jù)庫。

本章將通過實(shí)現(xiàn)一個(gè)簡單的 “內(nèi)存 + Redis 二級緩存” 組件,展示如何重寫 RedisCache 的核心生命周期。

一、 核心原理:RedisCache 的職責(zé)

org.springframework.data.redis.cache.RedisCache 是 Spring 官方提供的核心實(shí)現(xiàn)類。它繼承自 AbstractValueAdaptingCache。

我們主要關(guān)注以下三個(gè)方法的重寫:

  • lookup(Object key):對應(yīng) @Cacheable 的查詢操作。底層核心。
  • put(Object key, Object value):對應(yīng) @Cacheable 的寫入和 @CachePut。
  • evict(Object key):對應(yīng) @CacheEvict

二、 代碼實(shí)現(xiàn):構(gòu)建二級緩存 (Level2RedisCache)

我們將構(gòu)建一個(gè) Level2RedisCache。它的邏輯是:

  • :先查 L1(本地內(nèi)存),有則返回;無則查 L2(Redis);如果 Redis 有,則回填 L1。
  • :同時(shí)寫入 L1 和 L2。
  • :同時(shí)刪除 L1 和 L2。

1. 自定義 Cache 類

import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 自定義二級緩存實(shí)現(xiàn)
 * L1: JVM 本地內(nèi)存 (使用 ConcurrentHashMap 模擬,生產(chǎn)建議使用 Caffeine)
 * L2: Redis (使用父類邏輯)
 */
public class Level2RedisCache extends RedisCache {
    // 一級緩存容器
    private final Map<Object, Object> localCache = new ConcurrentHashMap<>();
    // 統(tǒng)計(jì)指標(biāo)(可選)
    private final String name;
    public Level2RedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter, cacheConfig);
        this.name = name;
    }
    /**
     * 重寫查詢邏輯 (核心)
     * 對應(yīng) @Cacheable 注解的讀取操作
     */
    @Override
    protected Object lookup(Object key) {
        // 1. 【L1】先檢查本地內(nèi)存
        Object localValue = localCache.get(key);
        if (localValue != null) {
            // log.debug("L1 緩存命中: key={}", key);
            return localValue;
        }
        // 2. 【L2】本地沒有,調(diào)用父類方法查 Redis
        // super.lookup() 內(nèi)部會(huì)處理序列化和反序列化
        Object redisValue = super.lookup(key);
        // 3. 【回填】如果 Redis 查到了,回寫到本地內(nèi)存
        if (redisValue != null) {
            // log.debug("L2 緩存命中并回填 L1: key={}", key);
            localCache.put(key, redisValue);
        }
        return redisValue;
    }
    /**
     * 重寫寫入邏輯
     * 對應(yīng) @Cacheable 的寫入和 @CachePut
     */
    @Override
    public void put(Object key, Object value) {
        // 1. 【L2】先寫 Redis (保證遠(yuǎn)程數(shù)據(jù)的強(qiáng)一致性)
        super.put(key, value);
        // 2. 【L1】再寫本地內(nèi)存
        // 注意:如果是集群環(huán)境,這里只更新了當(dāng)前節(jié)點(diǎn)的 L1。
        // 生產(chǎn)環(huán)境通常需要結(jié)合 Redis Pub/Sub 通知其他節(jié)點(diǎn)失效 L1。
        if (value != null) {
            localCache.put(key, value);
        }
    }
    /**
     * 重寫刪除邏輯
     * 對應(yīng) @CacheEvict
     */
    @Override
    public void evict(Object key) {
        // 1. 【L2】刪 Redis
        super.evict(key);
        // 2. 【L1】刪本地
        localCache.remove(key);
    }
    /**
     * 重寫清空邏輯
     * 對應(yīng) @CacheEvict(allEntries = true)
     */
    @Override
    public void clear() {
        super.clear();
        localCache.clear();
    }
}

2. 在 Manager 中啟用自定義 Cache

有了自定義的“工人”(Cache),我們需要在“工廠”(Manager)中雇傭它。

我們需要再次回到 RedisCacheManagercreateRedisCache 方法,將返回值修改為我們的 Level2RedisCache。

public class MultiLevelCacheManager extends RedisCacheManager {
    public MultiLevelCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }
    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        // 這里可以結(jié)合上一章的邏輯,先處理 cacheConfig 的 TTL
        // ... (省略 TTL 解析邏輯) ...
        // 【關(guān)鍵】返回自定義的 Level2RedisCache 實(shí)例
        // 這里的 super.createRedisCache 只是為了復(fù)用父類的一些初始化檢查,
        // 但我們直接 new 自己的對象是最直接的。
        return new Level2RedisCache(name, getCacheWriter(), cacheConfig);
    }
}

三、 進(jìn)階擴(kuò)展:添加監(jiān)控埋點(diǎn)

除了做多級緩存,自定義 RedisCache 也是做應(yīng)用層監(jiān)控的最佳切入點(diǎn)。我們可以在 lookup 方法中加入耗時(shí)統(tǒng)計(jì)。

@Override
protected Object lookup(Object key) {
    long start = System.nanoTime();
    try {
        Object value = super.lookup(key);
        recordMetrics(key, value != null, System.nanoTime() - start);
        return value;
    } catch (Exception e) {
        // 容錯(cuò)降級:如果 Redis 掛了,記錄日志,返回 null 讓業(yè)務(wù)查庫
        // log.error("Redis 異常", e);
        return null; 
    }
}
private void recordMetrics(Object key, boolean hit, long durationNs) {
    // 偽代碼:上報(bào)給 Prometheus 或 Micrometer
    // Metrics.counter("cache.requests", "name", this.name, "hit", String.valueOf(hit)).increment();
    // Metrics.timer("cache.latency", "name", this.name).record(durationNs, TimeUnit.NANOSECONDS);
}

除了上面討論的 RedisCacheConfiguration (配置屬性)、RedisCacheManager (管理策略) 和 RedisCache (運(yùn)行時(shí)行為) 這“三駕馬車”之外,Spring Data Redis 體系非常龐大,還存在其他幾個(gè)關(guān)鍵層面的定制。

如果把之前的三個(gè)層面比作“裝修房子”,那么剩下的層面涉及 “地基加固”、“進(jìn)門鎖匙”“應(yīng)急預(yù)案”。

以下是另外 4 個(gè) 常見且重要的高級定制層面:

第四層:AOP 輔助層定制 (KeyGenerator & ErrorHandler)

這層定制發(fā)生在緩存邏輯執(zhí)行的 “前”“后”

1. Custom KeyGenerator (定制鍵生成器)
  • 場景:你厭倦了在每個(gè) @Cacheable 里寫 key = "#user.id"?;蛘吣愕娜?yún)⒑軓?fù)雜(比如是一個(gè)大對象),你想用一種通用的算法(如 SHA256)生成 Key。
  • 默認(rèn)行為SimpleKeyGenerator (使用參數(shù)的 hashCode)。
  • 如何定制:實(shí)現(xiàn) KeyGenerator 接口。
@Bean("md5KeyGenerator")
public KeyGenerator keyGenerator() {
    return (target, method, params) -> {
        // 自定義邏輯:拼接類名+方法名+參數(shù)MD5
        return method.getName() + "_" + Arrays.toString(params);
    };
}
// 使用:@Cacheable(keyGenerator = "md5KeyGenerator")
2. CacheErrorHandler (定制異常處理器)
  • 場景這是生產(chǎn)環(huán)境的保命符。默認(rèn)情況下,如果 Redis 掛了(連接超時(shí)),@Cacheable 會(huì)拋出異常,導(dǎo)致整個(gè)接口報(bào)錯(cuò)(500 Error)。大多數(shù)時(shí)候,我們希望 “緩存掛了降級查庫,不要影響主流程”。
  • 如何定制:實(shí)現(xiàn) CacheErrorHandler。
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                // 僅打印日志,不拋出異常 -> 視為未命中,自動(dòng)穿透到數(shù)據(jù)庫
                log.error("Redis get failed, key: {}", key, exception);
            }
            // ... handlePutError, handleEvictError, handleClearError
        };
    }
}

第五層:客戶端驅(qū)動(dòng)層定制 (LettuceClientConfiguration)

這層定制屬于 “基礎(chǔ)設(shè)施” 級別,涉及網(wǎng)絡(luò)連接、線程模型和讀寫分離。它不在 Spring Cache 的邏輯里,而是在連接工廠(RedisConnectionFactory)的底層。

1. 讀寫分離策略 (ReadFrom)
  • 場景:你搭建了 Redis Sentinel(哨兵)或 Cluster(集群),主節(jié)點(diǎn)壓力大,希望 “寫主讀從”
  • 如何定制:通過 LettuceClientConfiguration。
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
    LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            // 優(yōu)先從從節(jié)點(diǎn)讀取,如果沒有從節(jié)點(diǎn)則讀主節(jié)點(diǎn)
            .readFrom(ReadFrom.REPLICA_PREFERRED) 
            .build();
    return new LettuceConnectionFactory(new RedisStandaloneConfiguration(), clientConfig);
}
2. Netty 線程池調(diào)優(yōu)
  • 場景:Redis 吞吐量極高,默認(rèn)的 Lettuce 線程數(shù)(CPU 核心數(shù))成為瓶頸,或者發(fā)生了 Netty 堆外內(nèi)存溢出。
  • 如何定制:自定義 ClientResources

第六層:操作模版層定制 (RedisTemplate)

雖然我們主要討論 @Cacheable(基于注解),但很多業(yè)務(wù)場景需要直接操作 Redis(基于代碼)。這時(shí)就需要定制 RedisTemplate。

1. 腳本執(zhí)行 (Scripting)
  • 場景:需要執(zhí)行復(fù)雜的原子操作(如“扣減庫存”或“限流算法”),Java 代碼分步執(zhí)行有并發(fā)問題。
  • 定制點(diǎn):封裝 DefaultRedisScript<T>,加載 Lua 腳本。
2. 流水線 (Pipeline)
  • 場景:需要一次性寫入 10000 條數(shù)據(jù),循環(huán)調(diào)用 put 會(huì)產(chǎn)生 10000 次網(wǎng)絡(luò) RTT(往返時(shí)延)。
  • 定制點(diǎn):使用 redisTemplate.executePipelined(...),將命令打包發(fā)送,批量執(zhí)行。

第七層:事件監(jiān)聽層定制 (MessageListener)

Redis 不僅僅是存儲(chǔ),還是一個(gè)消息總線。

1. 鍵過期監(jiān)聽 (Keyspace Notifications)
  • 場景:當(dāng)訂單支付超時(shí)(30分鐘未支付),Redis Key 過期刪除,你需要監(jiān)聽到這個(gè)事件來自動(dòng)取消訂單。
  • 如何定制:配置 RedisMessageListenerContainer 并開啟 Redis 的 notify-keyspace-events 配置。
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    return container;
}
// 然后監(jiān)聽 __keyevent@0__:expired 頻道

Spring Data Redis 全景定制圖

為了讓你一眼看清整個(gè)架構(gòu),我總結(jié)了這 7 個(gè)層面:

層面核心類/接口作用域典型應(yīng)用場景
1. 全局配置層RedisCacheConfiguration屬性規(guī)范改 JSON 序列化、統(tǒng)一 Key 前綴
2. 管理策略層RedisCacheManager調(diào)度策略動(dòng)態(tài)過期時(shí)間 (#30m)
3. 運(yùn)行時(shí)行為層RedisCache執(zhí)行邏輯多級緩存、埋點(diǎn)監(jiān)控
4. AOP 輔助層CacheErrorHandler容錯(cuò)/切面Redis 宕機(jī)自動(dòng)降級、自定義 Key 生成
5. 驅(qū)動(dòng)配置層LettuceClientConfiguration網(wǎng)絡(luò)/連接讀寫分離、連接池參數(shù)、Netty 調(diào)優(yōu)
6. 操作模版層RedisTemplate直接操作Lua 腳本、Pipeline 批量操作
7. 事件監(jiān)聽層KeyExpirationEvent異步回調(diào)監(jiān)聽 Key 過期事件處理業(yè)務(wù)邏輯

本文主要重點(diǎn)在于前三層定制,也是最常見的三層。

在面對需求時(shí),你應(yīng)該自上而下進(jìn)行考慮:能用上一層解決的,絕不輕易下沉到下一層,以保持系統(tǒng)的簡潔性和可維護(hù)性。

核心決策圖譜

第一層:RedisCacheConfiguration (配置層)

這是最頂層,也是改動(dòng)成本最低的一層。它控制的是數(shù)據(jù)的靜態(tài)屬性

  • 核心定義:定義緩存的 格式 (Format)默認(rèn)規(guī)則 (Defaults)。
  • 適用場景
  1. 修改序列化方式:不想用 JDK 的亂碼二進(jìn)制,想用 JSON 格式(通用需求)。
  2. 規(guī)范 Key 的命名:把 Spring 默認(rèn)的雙冒號(hào) :: 改成單冒號(hào) :,或者統(tǒng)一添加全局前綴(如 app_name:)。
  3. 設(shè)置兜底 TTL:為了防止 Redis 內(nèi)存爆炸,設(shè)置一個(gè)全局默認(rèn)過期時(shí)間(如 1 小時(shí))。
  4. 空值防御:全局禁止緩存 null 值。

一句話判別:如果你想改變存入 Redis 的數(shù)據(jù) “長什么樣”,或者設(shè)置 “默認(rèn)存多久”,選它。

第二層:RedisCacheManager (策略層)

這是中間層,它控制的是配置的分發(fā)策略。

  • 核心定義:根據(jù) 緩存名稱 (CacheName) 的不同,動(dòng)態(tài)應(yīng)用不同的 Configuration。
  • 適用場景
  1. 差異化過期時(shí)間:這是最經(jīng)典的需求。需要支持 @Cacheable("code#5m")@Cacheable("data#1d") 共存。
  2. 差異化序列化:極少數(shù)情況下,某些緩存想存 JSON,某些想存純 String(#raw),需要 Manager 識(shí)別后綴并切換配置。
  3. 多租戶隔離:根據(jù)緩存名動(dòng)態(tài)添加租戶 ID 前綴。

一句話判別:如果你的需求是 “看菜下碟”(即不同的業(yè)務(wù)緩存需要不同的過期時(shí)間或配置),選它。

第三層:RedisCache (行為層)

這是最底層,也是“深水區(qū)”。它控制的是**“數(shù)據(jù)的讀寫邏輯”**。

  • 核心定義:介入 getput、evict運(yùn)行時(shí)行為 (Runtime Behavior)
  • 適用場景
  1. 多級緩存:這是最硬核的理由。引入 Caffeine 做 L1 緩存,Redis 做 L2 緩存。
  2. 埋點(diǎn)監(jiān)控:需要精確統(tǒng)計(jì)每次 Redis IO 的耗時(shí),并上報(bào)給 Prometheus/SkyWalking。
  3. 容錯(cuò)降級:當(dāng) Redis 連接超時(shí)或宕機(jī)時(shí),捕獲異常并打印日志,而不是讓整個(gè)接口報(bào)錯(cuò)(雖然 CacheErrorHandler 也能做,但這里更靈活)。
  4. 讀寫分離/雙寫:寫入緩存時(shí),需要同步寫入另一個(gè)存儲(chǔ)介質(zhì)。

一句話判別:如果你需要 “改變流程”(比如讀 Redis 前先讀內(nèi)存,或者讀完 Redis 后發(fā)個(gè)消息),或者需要 “強(qiáng)行介入” 讀寫過程,選它。

決策速查表 (Cheat Sheet)

在實(shí)際開發(fā)中,對照下表進(jìn)行選型:

你的需求推薦方案復(fù)雜度
“我想讓 Redis 里存的是 JSON,不是亂碼”RedisCacheConfiguration? (低)
“我想把 Key 中間的雙冒號(hào) :: 去掉”RedisCacheConfiguration? (低)
“我想讓所有緩存默認(rèn) 1 小時(shí)過期”RedisCacheConfiguration? (低)
“我的驗(yàn)證碼要 5分鐘過期,排行榜要 1天過期”RedisCacheManager (解析 #ttl)?? (中)
“我想根據(jù)緩存名決定是否存 Null 值”RedisCacheManager (解析 #nonull)?? (中)
“Redis 太慢了,我想加一層本地內(nèi)存緩存”自定義 RedisCache??? (高)
“我想知道 Redis 緩存的命中率和平均耗時(shí)”自定義 RedisCache??? (高)
“Redis 掛了不能報(bào)錯(cuò),要自動(dòng)去查數(shù)據(jù)庫”自定義 RedisCache (或 ErrorHandler)??? (高)

最佳實(shí)踐建議:

對于絕大多數(shù)(90%)的 Spring Boot 業(yè)務(wù)系統(tǒng),“JSON 序列化配置 (Layer 1) + 動(dòng)態(tài)過期時(shí)間支持 (Layer 2)” 的組合是最完美的黃金搭檔。除非你有明確的性能瓶頸需要多級緩存,否則盡量不要觸碰 Layer 3,以降低系統(tǒng)的維護(hù)成本。

到此這篇關(guān)于SpringBoot自定義Redis代碼實(shí)踐指南的文章就介紹到這了,更多相關(guān)SpringBoot自定義Redis內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring整合mybatis實(shí)現(xiàn)過程詳解

    Spring整合mybatis實(shí)現(xiàn)過程詳解

    這篇文章主要介紹了Spring整合mybatis實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 利用session實(shí)現(xiàn)簡單購物車功能

    利用session實(shí)現(xiàn)簡單購物車功能

    這篇文章主要為大家詳細(xì)介紹了利用session實(shí)現(xiàn)簡單購物車功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 一文詳解Mybatis-plus的介紹與使用

    一文詳解Mybatis-plus的介紹與使用

    Mybatis-Plus?是?MyBatis?的一個(gè)增強(qiáng)工具,專門針對于傳統(tǒng)MyBatis開發(fā)中sql需要手動(dòng)進(jìn)行映射配置繁瑣缺點(diǎn)的一款框架技術(shù)。本文將為大家詳細(xì)講講Mybatis-plus的介紹與使用,感興趣的可以了解一下
    2022-07-07
  • hashCode方法的使用講解

    hashCode方法的使用講解

    有許多人學(xué)了很長時(shí)間的Java,但一直不明白hashCode方法的作用,我來解釋一下吧。
    2013-03-03
  • Java調(diào)用Windows命令的兩種方法詳解

    Java調(diào)用Windows命令的兩種方法詳解

    在開發(fā)過程中,我們有時(shí)需要通過Java程序來執(zhí)行一些操作系統(tǒng)級別的任務(wù),本文將詳細(xì)介紹如何使用這兩種方法來調(diào)用Windows命令,并提供示例代碼,有需要的可以了解下
    2025-11-11
  • SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題及解決方法

    SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題及解決方法

    這篇文章主要介紹了解決SpringBoot項(xiàng)目中分頁插件PageHelper無效的問題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-06-06
  • Java?IO流與NIO技術(shù)綜合應(yīng)用詳細(xì)實(shí)例代碼

    Java?IO流與NIO技術(shù)綜合應(yīng)用詳細(xì)實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于Java?IO流與NIO技術(shù)綜合應(yīng)用的相關(guān)資料,文中包括了字節(jié)流和字符流,以及它們的高級特性如緩沖區(qū)、序列化和反序列化,同時(shí)還介紹了NIO中的通道和緩沖區(qū),以及選擇器的使用,需要的朋友可以參考下
    2024-12-12
  • Java集合框架數(shù)據(jù)存儲(chǔ)與操作的神器

    Java集合框架數(shù)據(jù)存儲(chǔ)與操作的神器

    Java集合框架提供了多種數(shù)據(jù)結(jié)構(gòu)和操作方法,包括Collection接口、Map接口以及各種實(shí)現(xiàn)類,本文詳細(xì)介紹了集合框架的層次結(jié)構(gòu)、常見接口和實(shí)現(xiàn)類的特點(diǎn)、使用場景以及最佳實(shí)踐,幫助開發(fā)者高效地進(jìn)行數(shù)據(jù)存儲(chǔ)和操作,感興趣的朋友跟隨小編一起看看吧
    2025-12-12
  • Java中類變量和類方法的基本使用

    Java中類變量和類方法的基本使用

    這篇文章主要介紹了Java中類變量和類方法的基本使用,類變量也叫靜態(tài)變量/靜態(tài)屬性,是該類的所有對象共享的變量,任何一個(gè)該類的對象訪問它時(shí),取到的都是相同的值,同樣任何一個(gè)該類的對象去修改它時(shí),修改的也是同一個(gè)變量,需要的朋友可以參考下
    2023-07-07
  • 解讀@RabbitListener起作用的原理

    解讀@RabbitListener起作用的原理

    這篇文章主要介紹了解讀@RabbitListener起作用的原理,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03

最新評論

石屏县| 电白县| 郴州市| 肇东市| 万全县| 得荣县| 诸暨市| 嫩江县| 新化县| 衡山县| 襄垣县| 彩票| 会宁县| 延安市| 锡林浩特市| 庆元县| 漳平市| 甘洛县| 乌鲁木齐市| 闸北区| 怀化市| 江阴市| 扶沟县| 东源县| 芒康县| 酒泉市| 恩平市| 怀安县| 辛集市| 阜平县| 故城县| 阜城县| 广宗县| 健康| 新平| 上杭县| 土默特左旗| 昌都县| 从江县| 东莞市| 上饶市|