Pārlūkot izejas kodu

仅当配置了 spring.redis.sentinel.master 时生效;未配 Sentinel 时仍走自动配置。

dujian 1 mēnesi atpakaļ
vecāks
revīzija
491ce9fa69

+ 73 - 19
alien-store/src/main/java/shop/alien/store/config/RedisLettuceConfig.java

@@ -1,53 +1,107 @@
 package shop.alien.store.config;
 
-import io.lettuce.core.ReadFrom;
+import io.lettuce.core.RedisURI;
+import io.lettuce.core.sentinel.api.sync.RedisSentinelCommands;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
 import org.springframework.data.redis.connection.RedisPassword;
-import org.springframework.data.redis.connection.RedisSentinelConfiguration;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
 import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
 import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
 import org.springframework.util.StringUtils;
 
+import java.util.List;
+import java.util.Map;
+
 /**
- * 使用 Sentinel 时显式创建 RedisConnectionFactory,强制 ReadFrom.MASTER,避免写请求打到只读副本导致 READONLY。
+ * 使用 Sentinel 时:启动阶段向 Sentinel 查询当前 Master 地址,并只建立到该 Master 的独立连接(单节点)。
+ * 不建 MasterReplica 拓扑,从根源上避免请求被路由到只读副本导致 READONLY。
  * 仅当配置了 spring.redis.sentinel.master 时生效;未配 Sentinel 时仍走自动配置。
  */
+@Slf4j
 @Configuration
 public class RedisLettuceConfig {
 
     @Bean
+    @Primary
     @ConditionalOnProperty(prefix = "spring.redis.sentinel", name = "master")
-    public LettuceConnectionFactory redisConnectionFactory(RedisProperties properties) {
+    public LettuceConnectionFactory redisConnectionFactory(
+            RedisProperties properties,
+            @Value("${spring.redis.master-port:#{null}}") Integer masterPortOverride) {
         RedisProperties.Sentinel sentinel = properties.getSentinel();
-        RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
-                .master(sentinel.getMaster());
-        for (String node : sentinel.getNodes()) {
-            for (String one : node.split(",")) {
-                String hostPort = one.trim();
-                if (!hostPort.isEmpty()) {
-                    String[] parts = hostPort.split(":");
-                    String host = parts[0].trim();
-                    int port = parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 26379;
-                    sentinelConfig.sentinel(host, port);
+        String masterName = sentinel.getMaster();
+        String[] firstSentinel = parseFirstSentinelNode(sentinel.getNodes());
+        if (firstSentinel == null) {
+            throw new IllegalStateException("spring.redis.sentinel.nodes 未配置或格式错误");
+        }
+        String sentinelHost = firstSentinel[0];
+        int sentinelPort = Integer.parseInt(firstSentinel[1]);
+
+        // 向 Sentinel 查询当前 Master 地址
+        String masterHost;
+        int masterPort;
+        io.lettuce.core.RedisClient probeClient = null;
+        try {
+            probeClient = io.lettuce.core.RedisClient.create(
+                    RedisURI.create("redis://" + sentinelHost + ":" + sentinelPort));
+            try (io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection<String, String> probeConn =
+                    probeClient.connectSentinel()) {
+                RedisSentinelCommands<String, String> sync = probeConn.sync();
+                Map<String, String> master = sync.master(masterName);
+                if (master == null || !master.containsKey("ip") || !master.containsKey("port")) {
+                    throw new IllegalStateException("Sentinel 未返回 master 地址: " + masterName);
+                }
+                masterHost = master.get("ip");
+                masterPort = Integer.parseInt(master.get("port"));
+                // 若配置了 spring.redis.master-port(如宿主机映射 30012),用其覆盖 Sentinel 返回的端口
+                if (masterPortOverride != null) {
+                    masterPort = masterPortOverride;
                 }
+                log.info("Redis Sentinel 当前 Master: {}:{} (masterName={})", masterHost, masterPort, masterName);
+            }
+        } finally {
+            if (probeClient != null) {
+                probeClient.shutdown();
             }
         }
+
+        // 仅连接该 Master 单节点,不建 MasterReplica,避免请求打到副本
+        RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration(masterHost, masterPort);
+        standaloneConfig.setDatabase(properties.getDatabase());
         if (StringUtils.hasText(properties.getPassword())) {
-            sentinelConfig.setPassword(RedisPassword.of(properties.getPassword()));
+            standaloneConfig.setPassword(RedisPassword.of(properties.getPassword()));
         }
 
-        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder()
-                .readFrom(ReadFrom.MASTER);
+        LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder();
         if (properties.getTimeout() != null) {
             builder.commandTimeout(properties.getTimeout());
         }
         LettuceClientConfiguration clientConfig = builder.build();
 
-        LettuceConnectionFactory factory = new LettuceConnectionFactory(sentinelConfig, clientConfig);
-        factory.setDatabase(properties.getDatabase());
+        LettuceConnectionFactory factory = new LettuceConnectionFactory(standaloneConfig, clientConfig);
         return factory;
     }
+
+    private static String[] parseFirstSentinelNode(List<String> nodes) {
+        if (nodes == null || nodes.isEmpty()) {
+            return null;
+        }
+        for (String node : nodes) {
+            for (String one : node.split(",")) {
+                String hostPort = one.trim();
+                if (!hostPort.isEmpty()) {
+                    String[] parts = hostPort.split(":");
+                    String host = parts[0].trim();
+                    String port = parts.length > 1 ? parts[1].trim() : "26379";
+                    return new String[]{host, port};
+                }
+            }
+        }
+        return null;
+    }
 }