RedisTemplate配置读写分离

一、前言:为什么需要 Redis 读写分离?

在高并发场景下,所有请求都打到 Redis 主节点,容易导致:

  • ❌ 主节点 CPU/网络带宽打满
  • ❌ 写操作阻塞读请求(虽然 Redis 单线程,但网络和客户端处理有开销)
  • ❌ 无法利用从节点的读扩展能力

而 Redis 主从架构天然支持读写分离

  • 写操作 → 主节点
  • 读操作 → 从节点

但 Spring Boot 默认的 RedisTemplate 所有操作都走主节点

本文将教你如何通过 Lettuce 客户端 + 自定义配置 ,实现 RedisTemplate透明读写分离


二、技术选型:为什么用 Lettuce?

客户端 是否支持读写分离 线程模型 Spring Boot 默认
Jedis ❌(需手动管理连接池) 多线程(非线程安全) 否(2.x+ 默认 Lettuce)
Lettuce ✅(内置主从/哨兵读写分离) 单连接多路复用(线程安全)

Lettuce 优势

  • 原生支持 READONLY 模式
  • 与 Spring Data Redis 深度集成
  • 连接资源占用低

三、前提条件

  1. 已搭建 Redis 主从集群 (1 主 + N 从)

    bash 复制代码
    # 示例:主 6379,从 6380
    redis-server --port 6379
    redis-server --port 6380 --replicaof 127.0.0.1 6379
  2. Spring Boot 2.7+(默认使用 Lettuce)

  3. Maven 依赖:

    复制代码
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactityId>spring-boot-starter-data-redis</artifactId>
    </dependency>

四、核心原理:Lettuce 的读模式(ReadFrom)

Lettuce 提供 ReadFrom 枚举,控制读请求路由策略:

模式 行为
MASTER 所有读写都走主(默认)
MASTER_PREFERRED 优先读主,主不可用时读从
SLAVE 只读从节点(适合纯读场景)
SLAVE_PREFERRED 优先读从,从不可用时读主(推荐!)
NEAREST 读最近节点(延迟最低)

🔑 我们选择 SLAVE_PREFERRED:写走主,读优先走从,兼顾性能与容错。


五、配置步骤:自定义 RedisConnectionFactory

5.1 创建配置类

java 复制代码
@Configuration
public class RedisReplicaConfig {

    @Value("${spring.redis.host:localhost}")
    private String host;

    @Value("${spring.redis.port:6379}")
    private int port;

    @Value("${spring.redis.password:}")
    private String password;

    @Bean
    @Primary // 标记为主 RedisTemplate
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        
        // 使用 Jackson 序列化(避免乱码)
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(mapper);

        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 配置支持读写分离的 Lettuce 连接工厂
     */
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 1. 创建 RedisStandaloneConfiguration(单机主从)
        RedisStandaloneConfiguration config = 
            new RedisStandaloneConfiguration(host, port);
        if (StringUtils.hasText(password)) {
            config.setPassword(RedisPassword.of(password));
        }

        // 2. 配置 LettuceClientConfiguration
        LettuceClientConfiguration clientConfig = 
            LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.SLAVE_PREFERRED) // 👈 关键!启用读写分离
                .commandTimeout(Duration.ofSeconds(10))
                .build();

        return new LettuceConnectionFactory(config, clientConfig);
    }
}

关键代码.readFrom(ReadFrom.SLAVE_PREFERRED)


六、验证读写分离是否生效

6.1 编写测试 Controller

java 复制代码
@RestController
public class RedisTestController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("/write")
    public String write(@RequestParam String key, @RequestParam String value) {
        redisTemplate.opsForValue().set(key, value); // 写操作 → 主节点
        return "Write OK";
    }

    @GetMapping("/read")
    public Object read(@RequestParam String key) {
        return redisTemplate.opsForValue().get(key); // 读操作 → 优先从节点
    }
}

6.2 监控 Redis 节点命令

主节点(6379)

bash 复制代码
# 监控命令
redis-cli -p 6379 MONITOR

→ 应看到 SET 命令,几乎看不到 GET

从节点(6380)

bash 复制代码
redis-cli -p 6380 MONITOR

→ 应看到大量 GET 命令

效果:写入走主,读取走从,读写分离成功!


七、进阶:哨兵(Sentinel)或集群(Cluster)模式

7.1 哨兵模式配置

java 复制代码
@Bean
public LettuceConnectionFactory sentinelConnectionFactory() {
    RedisSentinelConfiguration sentinelConfig = 
        new RedisSentinelConfiguration()
            .master("mymaster")
            .sentinel("192.168.1.10", 26379)
            .sentinel("192.168.1.11", 26379)
            .sentinel("192.168.1.12", 26379);
    
    if (StringUtils.hasText(password)) {
        sentinelConfig.setPassword(RedisPassword.of(password));
    }

    LettuceClientConfiguration clientConfig = 
        LettuceClientConfiguration.builder()
            .readFrom(ReadFrom.SLAVE_PREFERRED)
            .build();

    return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}

7.2 Redis Cluster 模式

⚠️ 注意 :Cluster 模式不支持传统主从读写分离

因为 Cluster 本身已分片,每个 master-slave 对负责不同 slot。

若需读从,需显式设置 ReadFrom,但需确保 key 路由正确。


八、常见问题与避坑指南

❌ 问题 1:读操作仍走主节点

  • 原因 :未配置 readFrom,或配置了但使用了事务/脚本
  • 注意 :以下操作强制走主 (Lettuce 限制):
    • MULTI/EXEC 事务
    • Lua 脚本(EVAL
    • 写命令(如 SET, HSET

❌ 问题 2:从节点数据延迟导致读到旧值

  • 场景:刚写入主,立即读从 → 可能读不到
  • 解决方案
    1. 业务容忍短暂不一致(最终一致性)

    2. 关键读走主 :提供两个 RedisTemplate(一个读写,一个只读)

      java 复制代码
      @Bean("readOnlyRedisTemplate")
      public RedisTemplate<String, Object> readOnlyRedisTemplate() {
          // ... same as above, but ReadFrom.SLAVE
      }

      使用时显式指定:

      java 复制代码
      @Autowired
      @Qualifier("readOnlyRedisTemplate")
      private RedisTemplate<String, Object> readOnlyRedisTemplate;

✅ 最佳实践

  • 普通查询 → 读从
  • 强一致性读(如支付后查余额)→ 读主
  • 监控从节点 lagINFO REPLICATIONslave0:lag=0

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
Dxy123931021612 小时前
如何使用jQuery获取一类元素并遍历它们
前端·javascript·jquery
csdn小瓯13 小时前
AI质量评估体系:LLM-as-a-Judge实现与自动化测试实战
前端·网络·人工智能
jiayong2313 小时前
第 43 课:任务详情抽屉里的批量处理闭环与删除联动
java·开发语言·前端
刀法如飞13 小时前
JavaScript 数组去重的 20 种实现方式,学会用不同思路解决问题
前端·javascript·算法
小江的记录本13 小时前
【AI大模型选型指南】《2026年5月(最新版)国内外主流AI大模型选型指南》(个人版)
前端·人工智能·后端·ai·aigc·ai编程·ai写作
@PHARAOH13 小时前
HOW - 前端输入场景支持拼音匹配
前端
计算机安禾13 小时前
【c++面向对象编程】第21篇:运算符重载基础:语法、规则与不可重载的运算符
java·前端·c++
__log14 小时前
Vue 3 核心技术深度解析:从“会用API“到“懂原理、能表达“
前端·javascript·vue.js
ZC跨境爬虫14 小时前
跟着 MDN 学 HTML day_52:(深入 XPathExpression 接口)
开发语言·前端·javascript·ui·html·音视频