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

九、结语

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

相关推荐
IT_陈寒31 分钟前
Vue的v-for里用index当key,我被自己坑惨了
前端·人工智能·后端
代码不加糖1 小时前
0基础搭建前后端分离项目:实现菜单与界面左右布局
java·前端·javascript·mysql·elementui·mybatis
zhensherlock2 小时前
Protocol Launcher 系列:Tally 快速计数器的深度集成
前端·javascript·typescript·node.js·自动化·github·js
AC赳赳老秦2 小时前
OpenClaw权限管理实操:团队共享Agent,设置操作权限,保障数据安全
服务器·开发语言·前端·javascript·excel·deepseek·openclaw
光影少年2 小时前
Polyline 组件如何绘制渐变区域?
前端·javascript·掘金·金石计划
Pkmer2 小时前
古法编程: React思维模型快速建立
前端·react.js
jiayong232 小时前
第 38 课:任务列表里高亮当前正在查看详情的任务
开发语言·前端·javascript·vue.js·学习
anOnion3 小时前
构建无障碍组件之Spinbutton Pattern
前端·html·交互设计
程序员Better3 小时前
前端成功转型AI全栈,我踩过的坑都替你填上了
前端·后端·ai编程
兔子零10243 小时前
GPT-5.5 与 DeepSeek-V4:大模型竞争的本质,正在从“谁更强”变成“谁让成本更低”
前端·javascript·后端