如何在Lettuce中覆盖Redis集群节点为Nginx代理地址
问题根因
Lettuce集群模式默认开启拓扑动态刷新功能,会执行CLUSTER NODES命令获取集群所有真实节点的内网地址,后续请求会直接连接这些真实地址,不会走你配置的Nginx代理,就会出现网络不可达超时的问题。
修复步骤
1. 先修正Nginx配置笔误
你的Nginx stream配置里监听端口的指令拼写错误,把lister改为listen,否则Nginx无法正常启动代理:
stream { upstream grp1 { server node1-1:8762; server node1-2:8762; server node1-3:8762; } upstream grp2 { server node2-1:8762; server node2-2:8762; server node2-3:8762; } server { listen 8441; # 修正拼写错误 proxy_pass grp1; } server { listen 8442; # 修正拼写错误 proxy_pass grp2; } }
2. 修改Lettuce集群配置,禁用动态节点源+配置地址映射
需要调整ClusterClientOptions的配置,核心是两个参数:
dynamicRefreshSources(false):禁止使用动态发现的节点作为拓扑刷新源,始终只用你初始配置的Nginx节点拉取拓扑addressMapper:把拓扑返回的所有真实Redis节点地址,映射为对应的Nginx代理地址,确保所有请求都走代理
修改后的RedisConfig核心代码如下:
@Configuration @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig { @Value("${ngnix.ip}") private String ngnixIp; // 修正类型拼写:String首字母大写 @Bean LettuceConnectionFactory lettuceConnectionFactory() { RedisClusterConfiguration redisCluster = new RedisClusterConfiguration(); Set<RedisNode> nodes = new HashSet<>(); nodes.add(new RedisNode(ngnixIp,8441)); // 补全语法括号 nodes.add(new RedisNode(ngnixIp,8442)); // 补全语法括号 redisCluster.setClusterNodes(nodes); redisCluster.setPassword("mypassword"); // 新增集群拓扑刷新配置 ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder() // 禁用动态刷新源,始终只用初始配置的Nginx节点拉取拓扑 .dynamicRefreshSources(false) // 按需开启拓扑刷新周期,比如每30秒刷新一次,可选配置 .enablePeriodicRefresh(Duration.ofSeconds(30)) .enableAllAdaptiveRefreshTriggers() .build(); // 新增地址映射,把真实节点地址映射为Nginx代理地址 AddressMapper addressMapper = new AddressMapper() { @Override public HostAndPort map(HostAndPort hostAndPort) { // 判断真实节点所属分片,映射到对应Nginx端口,可按实际规则调整 String host = hostAndPort.getHost(); if (host.startsWith("node1-")) { return HostAndPort.of(ngnixIp, 8441); } else if (host.startsWith("node2-")) { return HostAndPort.of(ngnixIp, 8442); } return hostAndPort; } }; // 改用集群专用的ClusterClientOptions ClusterClientOptions clientOptions = ClusterClientOptions.builder() .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).build()) .topologyRefreshOptions(topologyRefreshOptions) .addressMapper(addressMapper) .build(); LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofSeconds(5)) .clientOptions(clientOptions) .build(); LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisCluster, clientConfig); lettuceConnectionFactory.setValidateConnection(true); lettuceConnectionFactory.setShareNativeConnection(true); // 补全语法分号 return lettuceConnectionFactory; } @Bean public RedisTemplate<Long, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Long, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // 建议补充序列化器配置,避免默认JDK序列化产生乱码问题 return template; } }
3. 可选优化
如果你的Nginx两个代理入口已经做了集群全局请求转发,不需要区分分片,也可以直接把所有真实节点都映射到任意一个Nginx代理端口,Nginx会自动转发到对应节点,不需要判断节点前缀。
内容的提问来源于stack exchange,提问作者scoder
相关产品推荐
相关产品推荐

