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

基于SpringBoot + Lettuce實現(xiàn)Redis集群讀寫分離

 更新時間:2026年07月15日 08:41:03   作者:IT_Octopus  
本文手把手教你基于SpringBoot和Lettuce實現(xiàn)讀寫分離,通過配置SLAVE_PREFERRED讓讀請求優(yōu)先走從庫,同時保持寫操作和強一致讀走主庫,方案零侵入、可灰度,只改需要分流的調用點,完美平衡性能與一致性,需要的朋友可以參考下

一、背景

項目基于 Spring Boot 2.1.5 + Redis 集群 + Lettuce 客戶端,所有讀寫默認都走主庫(ReadFrom = MASTER)。隨著讀流量增大,主庫壓力上升,需要把部分可容忍主從延遲的讀分流到從節(jié)點,同時保證寫操作和強一致讀仍走主庫。

目標很明確:

  • 讀多寫少的場景(如用戶標簽、菜單緩存讀?。┳邚膸?/li>
  • 分布式鎖、寫后立即讀等強一致場景仍走主庫
  • 對現(xiàn)有代碼侵入最小,不改變既有方法的語義

二、方案選型

Lettuce 原生支持 ReadFrom 策略,常見取值:

ReadFrom行為
MASTER只讀主庫(默認)
MASTER_PREFERRED優(yōu)先主庫,主庫不可用讀從庫
SLAVE_PREFERRED / REPLICA_PREFERRED優(yōu)先從庫,從庫不可用回退主庫
SLAVE / REPLICA只讀從庫

版本坑:Spring Boot 2.1.5 自帶 Lettuce 5.1.x,枚舉名是 SLAVE_PREFERRED;Lettuce 5.2+ 才更名為 REPLICA_PREFERRED,語義完全相同。直接照抄高版本代碼會編譯報錯。

核心思路是:構建一個獨立的副本 LettuceConnectionFactory,只把 ReadFrom 設為 SLAVE_PREFERRED,其余配置復用主庫,再基于它注冊一組"副本 RedisTemplate",在工具類里提供帶 FromReplica 后綴的專用讀方法,由調用方按需選擇。

這種"新增方法、不動現(xiàn)有方法"的做法好處是:零 風險、可灰度——只改需要分流的調用點,其余代碼一行不動。

三、實現(xiàn)

3.1 副本連接工廠 + 副本模板

新建 ReplicaRedisConfig,關鍵點:

  • 復用 RedisProperties(集群節(jié)點、密碼、max-redirects、連接池、超時)手動構建集群配置
  • ReadFrom.SLAVE_PREFERRED 優(yōu)先從庫
  • 注冊與主庫序列化器一致的副本模板,保證讀寫數(shù)據(jù)兼容
import io.lettuce.core.ReadFrom;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.time.Duration;

/**
 * Redis 從讀(Replica Read)配置
 *
 * 副本連接工廠 ReadFrom = SLAVE_PREFERRED(優(yōu)先從庫,從庫不可用回退主庫)。
 * 復用主庫的集群拓撲/密碼/連接池/超時,僅 ReadFrom 不同。
 * 注意:副本模板僅用于讀命令;集群模式下寫命令始終由主節(jié)點處理。
 */
@Configuration
public class ReplicaRedisConfig {

    @Bean
    public LettuceConnectionFactory replicaConnectionFactory(RedisProperties redisProperties) {
        if (redisProperties.getCluster() == null
                || redisProperties.getCluster().getNodes() == null
                || redisProperties.getCluster().getNodes().isEmpty()) {
            throw new IllegalStateException(
                    "ReplicaRedisConfig 需要集群配置 spring.redis.cluster.nodes,當前未配置");
        }

        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
        for (String node : redisProperties.getCluster().getNodes()) {
            String[] parts = node.split(":");
            clusterConfig.clusterNode(parts[0].trim(), Integer.parseInt(parts[1].trim()));
        }
        clusterConfig.setMaxRedirects(redisProperties.getCluster().getMaxRedirects());
        if (redisProperties.getPassword() != null && !redisProperties.getPassword().isEmpty()) {
            clusterConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
        }

        GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
        RedisProperties.Pool pool = redisProperties.getLettuce().getPool();
        if (pool != null) {
            poolConfig.setMaxTotal(pool.getMaxActive());
            poolConfig.setMaxIdle(pool.getMaxIdle());
            poolConfig.setMinIdle(pool.getMinIdle());
            if (pool.getMaxWait() != null) {
                poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
            }
        }

        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder =
                LettucePoolingClientConfiguration.builder()
                        .readFrom(ReadFrom.SLAVE_PREFERRED)   // 關鍵:優(yōu)先從庫
                        .poolConfig(poolConfig);

        Duration timeout = redisProperties.getTimeout();
        if (timeout != null) {
            builder.commandTimeout(timeout);
        }

        LettuceConnectionFactory factory = new LettuceConnectionFactory(clusterConfig, builder.build());
        factory.afterPropertiesSet();
        return factory;
    }

    /** 副本純字符串序列化模板 */
    @Bean
    public RedisTemplate<String, Object> customStringRedisTemplateReplica(
            LettuceConnectionFactory replicaConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(replicaConnectionFactory);
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        template.setValueSerializer(RedisSerializer.string());
        template.setHashValueSerializer(RedisSerializer.string());
        template.afterPropertiesSet();
        return template;
    }

    // 其他副本模板(gzip / Protobuf+Zstd)序列化器與主庫保持一致,略
}

3.2 工具類新增 FromReplica 讀方法

RedisUtils 中注入副本模板(用 @Qualifier 按名注入,避免與主庫模板沖突),新增一組后綴為 FromReplica 的讀方法:

@Autowired
@Qualifier("customStringRedisTemplateReplica")
private RedisTemplate<String, Object> customStringRedisTemplateReplica;

@Autowired
@Qualifier("gzipRedisTemplateReplica")
private RedisTemplate<String, Object> gzipRedisTemplateReplica;

@Autowired
@Qualifier("menuRedisTemplateReplica")
private RedisTemplate<String, Map<String, Menu>> menuRedisTemplateReplica;

// ==================== 從讀(Replica Read)方法 ====================
// 走副本連接工廠(ReadFrom = SLAVE_PREFERRED),僅用于可容忍主從復制延遲的讀場景。
// 現(xiàn)有同名(不帶 FromReplica)方法仍走主庫,語義不變。

public Object getStrFromReplica(String key) {
    return key == null ? null : customStringRedisTemplateReplica.opsForValue().get(key);
}

public Object hgetStringFromReplica(String key, String field) throws IOException {
    final Object object = customStringRedisTemplateReplica.opsForHash().get(key, field);
    if (object == null) {
        log.debug("data is null");
        return null;
    }
    return object;
}

public boolean existsFromReplica(String key) {
    return customStringRedisTemplateReplica.hasKey(key);
}

public Map<String, Menu> loadCompressedMenuByProtobufZstdFromReplica(String key) {
    try {
        Map<String, Menu> value = menuRedisTemplateReplica.opsForValue().get(key);
        return value == null ? null : value;
    } catch (Exception e) {
        throw new RuntimeException("Failed to load compressed menu from replica", e);
    }
}

3.3 調用方改造

把原來調主庫的地方換成 xxxFromReplica 即可,一行改動:

// 改造前:走主庫
Object object = redisUtils.getStr(redisKey);

// 改造后:走從庫優(yōu)先
Object object = redisUtils.getStrFromReplica(redisKey);

四、踩坑記錄

  1. Lettuce 版本與枚舉名:SB 2.1.x 對應 Lettuce 5.1.x,必須用 ReadFrom.SLAVE_PREFERRED,高版本的 REPLICA_PREFERRED 編譯不過。
  2. 副本模板只讀不寫:集群模式下寫命令(SET/HSET 等)始終路由到 slot owner(主節(jié)點),用副本模板寫沒有意義且易混淆,應在規(guī)范上禁止。
  3. 連接池配置要同步:副本連接工廠是手動 new 出來的,不會自動繼承 spring.redis.lettuce.pool.*,必須顯式讀取 RedisProperties 里的 pool 配置套用,否則會用默認池參數(shù)。
  4. afterPropertiesSet() 別忘:手動創(chuàng)建的 LettuceConnectionFactory 需要調用 afterPropertiesSet() 完成初始化,否則啟動時連接不上。
  5. 主從延遲評估SLAVE_PREFERRED 回退主庫只在"從節(jié)點不可達/報錯"時觸發(fā),不會因延遲大而回退。對延遲敏感的讀(寫后立即讀、分布式鎖判斷)必須用主庫方法。

五、總結

整套方案的核心是"雙連接工廠 + 專用方法名":

  • 主庫連接工廠由 Spring Boot 自動裝配,保持 MASTER,所有現(xiàn)有方法零改動
  • 新建副本連接工廠 SLAVE_PREFERRED,注冊配套副本模板
  • 工具類按需暴露 FromReplica 方法,調用方顯式選擇

優(yōu)點是漸進式、可控、零回歸風險:想分流哪個調用點就改哪個,出問題隨時回退到主庫方法,不影響其他業(yè)務。

以上就是基于SpringBoot + Lettuce實現(xiàn)Redis集群讀寫分離的詳細內容,更多關于SpringBoot Lettuce Redis集群讀寫分離的資料請關注腳本之家其它相關文章!

相關文章

  • java高質量縮放圖片的示例代碼

    java高質量縮放圖片的示例代碼

    這篇文章主要介紹了java高質量縮放圖片的示例代碼,幫助大家更好的使用Java處理圖片,感興趣的朋友可以了解下
    2020-09-09
  • Java音頻處理之音頻流轉音頻文件和獲取音頻播放時長詳解

    Java音頻處理之音頻流轉音頻文件和獲取音頻播放時長詳解

    這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)音頻流轉音頻文件和獲取音頻播放時長功能,文中的示例代碼簡潔易懂,有需要的小伙伴可以了解下
    2025-07-07
  • 爬蟲技術詳解

    爬蟲技術詳解

    本文全面的介紹了爬蟲的原理、技術現(xiàn)狀、以及目前仍面臨的問題。具有很好的參考價值。下面跟著小編一起來看下吧
    2017-03-03
  • Spring MVC 處理一個請求的流程

    Spring MVC 處理一個請求的流程

    Spring MVC是Spring系列框架中使用頻率最高的部分。不管是Spring Boot還是傳統(tǒng)的Spring項目,只要是Web項目都會使用到Spring MVC部分。因此程序員一定要熟練掌握MVC部分。本篇博客簡要分析Spring MVC處理一個請求的流程。
    2021-02-02
  • Java繼承的實現(xiàn)與繼承限制分析

    Java繼承的實現(xiàn)與繼承限制分析

    這篇文章主要介紹了Java繼承的實現(xiàn)與繼承限制,結合具體實例形式分析了Java繼承的定義、實現(xiàn)以及繼承的相關限制,需要的朋友可以參考下
    2019-01-01
  • Springboot中的@Conditional注解詳解

    Springboot中的@Conditional注解詳解

    這篇文章主要介紹了Springboot中的@Conditional注解詳解,@Conditional來源于spring-context包下的一個注解,Conditional中文是條件的意思,@Conditional注解它的作用是按照一定的條件進行判斷,滿足條件給容器注冊bean,需要的朋友可以參考下
    2023-09-09
  • Java空指針異常處理之判空、Optional與Assert解析

    Java空指針異常處理之判空、Optional與Assert解析

    本文將深入探討三種處理空指針異常的方法:傳統(tǒng)的判空檢查、Java 8引入的Optional類以及使用斷言(Assert),通過代碼示例和應用場景分析,幫助開發(fā)者理解并選擇最適合的方案以提升程序健壯性,感興趣的朋友一起看看吧
    2025-01-01
  • 關于Java創(chuàng)建線程的2種方式以及對比

    關于Java創(chuàng)建線程的2種方式以及對比

    這篇文章主要給大家介紹了關于Java創(chuàng)建線程的2種方式以及對比的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2022-01-01
  • Java中List.contains(Object?object)方法使用

    Java中List.contains(Object?object)方法使用

    本文主要介紹了Java中List.contains(Object?object)方法,使用List.contains(Object?object)方法判斷ArrayList是否包含一個元素對象,感興趣的可以了解一下
    2022-04-04
  • Mybatis plus結合springboot使用

    Mybatis plus結合springboot使用

    本文主要介紹了MyBatisPlus使用SpringBoot數(shù)據(jù)庫操作,從添加依賴到測試,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-11-11

最新評論

建德市| 朔州市| 麻江县| 绥德县| 疏勒县| 杭锦旗| 南通市| 榆社县| 南开区| 乌恰县| 南宁市| 新兴县| 固阳县| 宣武区| 涟源市| 夏邑县| 松阳县| 太白县| 潮州市| 商城县| 桃园市| 周至县| 琼海市| 河曲县| 仁怀市| 盐城市| 阳朔县| 马公市| 兰西县| 黄平县| 南和县| 胶州市| 南宫市| 湖州市| 丰顺县| 大石桥市| 西充县| 馆陶县| 罗源县| 田东县| 辉南县|