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

九、结语

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

相关推荐
CQU_JIAKE1 小时前
4.4【Q】
java·前端·javascript
小陈工1 小时前
Python Web开发入门(十二):使用Flask-RESTful构建API——让后端开发更优雅
开发语言·前端·python·安全·oracle·flask·restful
木斯佳1 小时前
前端八股文面经大全:字节前端一面(2026-04-03)·面经深度解析
前端·面试题·面经
xiaotao1311 小时前
第八章:实战项目案例
前端·vue.js·vite·前端打包
We་ct1 小时前
JS手撕:性能优化、渲染技巧与定时器实现
开发语言·前端·javascript·面试·性能优化·定时器·性能
夜雨飘零11 小时前
零门槛!用 AI 生成 HTML 并一键部署到云端桌面
人工智能·python·html
taWSw5OjU1 小时前
vue对接海康摄像头-H5player
开发语言·前端·javascript
huwuhang1 小时前
跨平台电子书阅读器 | Readest最新版 安卓版+PC版全平台
android·前端·javascript
C澒1 小时前
AI 生码:RAG 检索优化实现可评估、可回溯工程化
前端·ai编程
条tiao条1 小时前
不止语法糖:TypeScript Set 与 Map 深度解析
前端·javascript·typescript