|
|
@@ -1,114 +1,114 @@
|
|
|
-//package shop.alien.store.config;
|
|
|
-//
|
|
|
-//import io.lettuce.core.RedisURI;
|
|
|
-//import io.lettuce.core.sentinel.api.sync.RedisSentinelCommands;
|
|
|
-//import lombok.extern.slf4j.Slf4j;
|
|
|
-//import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
|
|
-//import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
|
|
-//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.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 时:启动阶段向 Sentinel 查询当前 Master 地址,并只建立到该 Master 的独立连接(单节点)。
|
|
|
-// * 不建 MasterReplica 拓扑,从根源上避免请求被路由到只读副本导致 READONLY。
|
|
|
-// * 仅当配置了 spring.redis.sentinel.master 时生效;未配 Sentinel 时仍走自动配置。
|
|
|
-// */
|
|
|
-//@Slf4j
|
|
|
-//@Configuration
|
|
|
-//@AutoConfigureBefore(RedisAutoConfiguration.class)
|
|
|
-//public class RedisLettuceConfig {
|
|
|
-//
|
|
|
-// @Bean
|
|
|
-// @Primary
|
|
|
-// @ConditionalOnProperty(
|
|
|
-// prefix = "spring.redis.sentinel",
|
|
|
-// name = "master",
|
|
|
-// matchIfMissing = false)
|
|
|
-// public LettuceConnectionFactory redisConnectionFactory(
|
|
|
-// RedisProperties properties,
|
|
|
-// @Value("${spring.redis.master-port:#{null}}") Integer masterPortOverride) {
|
|
|
-// log.info("RedisLettuceConfig: 使用 Sentinel 解析 Master 并创建仅连 Master 的 Redis 连接(避免 READONLY)");
|
|
|
-// RedisProperties.Sentinel sentinel = properties.getSentinel();
|
|
|
-// 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())) {
|
|
|
-// standaloneConfig.setPassword(RedisPassword.of(properties.getPassword()));
|
|
|
-// }
|
|
|
-//
|
|
|
-// LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder();
|
|
|
-// if (properties.getTimeout() != null) {
|
|
|
-// builder.commandTimeout(properties.getTimeout());
|
|
|
-// }
|
|
|
-// LettuceClientConfiguration clientConfig = builder.build();
|
|
|
-//
|
|
|
-// 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;
|
|
|
-// }
|
|
|
-//}
|
|
|
+package shop.alien.store.config;
|
|
|
+
|
|
|
+import io.lettuce.core.RedisURI;
|
|
|
+import io.lettuce.core.sentinel.api.sync.RedisSentinelCommands;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
|
|
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
|
|
+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.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 时:启动阶段向 Sentinel 查询当前 Master 地址,并只建立到该 Master 的独立连接(单节点)。
|
|
|
+ * 不建 MasterReplica 拓扑,从根源上避免请求被路由到只读副本导致 READONLY。
|
|
|
+ * 仅当配置了 spring.redis.sentinel.master 时生效;未配 Sentinel 时仍走自动配置。
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Configuration
|
|
|
+@AutoConfigureBefore(RedisAutoConfiguration.class)
|
|
|
+public class RedisLettuceConfig {
|
|
|
+
|
|
|
+ @Bean
|
|
|
+ @Primary
|
|
|
+ @ConditionalOnProperty(
|
|
|
+ prefix = "spring.redis.sentinel",
|
|
|
+ name = "master",
|
|
|
+ matchIfMissing = false)
|
|
|
+ public LettuceConnectionFactory redisConnectionFactory(
|
|
|
+ RedisProperties properties,
|
|
|
+ @Value("${spring.redis.master-port:#{null}}") Integer masterPortOverride) {
|
|
|
+ log.info("RedisLettuceConfig: 使用 Sentinel 解析 Master 并创建仅连 Master 的 Redis 连接(避免 READONLY)");
|
|
|
+ RedisProperties.Sentinel sentinel = properties.getSentinel();
|
|
|
+ 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())) {
|
|
|
+ standaloneConfig.setPassword(RedisPassword.of(properties.getPassword()));
|
|
|
+ }
|
|
|
+
|
|
|
+ LettuceClientConfiguration.LettuceClientConfigurationBuilder builder = LettuceClientConfiguration.builder();
|
|
|
+ if (properties.getTimeout() != null) {
|
|
|
+ builder.commandTimeout(properties.getTimeout());
|
|
|
+ }
|
|
|
+ LettuceClientConfiguration clientConfig = builder.build();
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+}
|