如何借助Spring Data Redis让Redis集群只读Lua脚本在从节点执行?
解决方案
方案1:手动指定从节点执行只读Lua脚本
Spring Data Redis的ReadFrom配置确实仅对标准只读命令生效,因为Lua脚本默认会被路由到主节点。你可以通过直接获取从节点连接来执行只读Lua脚本:
- 从Redis连接工厂中获取集群连接,过滤出所有从节点地址
- 选择目标从节点,创建专用连接执行脚本
- 最后释放连接避免资源泄漏
示例代码:
@Autowired private RedisConnectionFactory connectionFactory; public Object executeReadOnlyLuaScriptOnReplica(String script, List<String> keys, Object... args) { ClusterConnection clusterConn = (ClusterConnection) connectionFactory.getConnection(); try { // 过滤出所有从节点 Set<RedisNode> replicaNodes = clusterConn.clusterGetNodes().stream() .filter(node -> node.getType() == RedisNode.NodeType.REPLICA) .collect(Collectors.toSet()); if (replicaNodes.isEmpty()) { throw new RuntimeException("No replica nodes available"); } // 选取第一个可用从节点(可自行扩展负载均衡逻辑) RedisNode replicaNode = replicaNodes.iterator().next(); RedisClusterNode clusterNode = new RedisClusterNode(replicaNode.getHost(), replicaNode.getPort()); // 向指定从节点执行脚本 return clusterConn.execute(clusterNode, script.getBytes(), keys, args); } finally { clusterConn.close(); } }
方案2:利用Lettuce客户端临时切换ReadFrom策略
如果你的Spring Data Redis底层使用Lettuce客户端,可以通过原生连接临时切换ReadFrom策略,执行脚本后再恢复原配置:
@Autowired private RedisTemplate<String, Object> redisTemplate; public Object executeLuaScriptOnReplica(String script, List<String> keys, Object... args) { return redisTemplate.execute((RedisCallback<Object>) connection -> { LettuceConnection lettuceConn = (LettuceConnection) connection; StatefulRedisClusterConnection<String, String> nativeConn = lettuceConn.getNativeConnection(); // 保存原有ReadFrom配置 ReadFrom originalReadFrom = nativeConn.getReadFrom(); try { // 临时切换为优先从节点读取 nativeConn.setReadFrom(ReadFrom.REPLICA_PREFERRED); // 执行Lua脚本 return nativeConn.sync().eval(script, ScriptOutputType.VALUE, keys.toArray(new String[0]), args); } finally { // 恢复原有配置 nativeConn.setReadFrom(originalReadFrom); } }); }
方案3:创建专用只读RedisTemplate
配置一个专门用于只读操作的RedisTemplate,指定其连接工厂的ReadFrom为REPLICA_PREFERRED,仅用它执行只读Lua脚本:
- 配置专用Lettuce连接工厂
@Bean public LettuceConnectionFactory replicaPreferredConnectionFactory(RedisClusterConfiguration clusterConfig) { LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .readFrom(ReadFrom.REPLICA_PREFERRED) .build(); return new LettuceConnectionFactory(clusterConfig, clientConfig); } @Bean(name = "replicaRedisTemplate") public RedisTemplate<String, Object> replicaRedisTemplate(LettuceConnectionFactory replicaPreferredConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(replicaPreferredConnectionFactory); // 配置序列化器 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; }
- 使用专用模板执行脚本
@Autowired @Qualifier("replicaRedisTemplate") private RedisTemplate<String, Object> replicaRedisTemplate; public Object executeReadOnlyLuaScript(String script, List<String> keys, Object... args) { DefaultRedisScript<Object> redisScript = new DefaultRedisScript<>(script, Object.class); return replicaRedisTemplate.execute(redisScript, keys, args); }
注意:此方案仅适用于完全只读的Lua脚本,如果脚本包含写操作,会因从节点不允许写操作而执行失败。
内容的提问来源于stack exchange,提问作者Kostas
相关产品推荐
相关产品推荐

