Spring 使用 Lettuce 连接到 AWS ElastiCache Redis 的会话

Spring Session using Lettuce connecting to AWS ElastiCache Redis

我正在使用 Spring Session 将我们的会话外部化到 Redis (AWS ElastiCache)。 Lettuce 被用作 Redis 的客户端。

我的 AWS Redis 配置如下:

  1. 已启用 Redis 集群
  2. 两个分片(即两个主节点)
  3. 每个主人一个奴隶

我的 Lettuce 配置如下:

<!-- Lettuce Configuration -->
<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
    <constructor-arg ref="redisClusterConfiguration"/>
</bean>

<!-- Redis Cluster Configuration -->
<bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration">
    <constructor-arg>
        <list>
            <value><!-- AMAZON SINGLE ENDPOINT HERE --></value>
         </list>
     </constructor-arg>
 </bean>

当我们触发主节点的故障转移时出现问题。我在测试故障转移期间记录了以下事件

myserver-0001-001
cache-cluster
Monday, July 6, 2020 at 8:25:32 PM UTC+3
Finished recovery for cache nodes 0001
 
myserver-0001-001
cache-cluster
Monday, July 6, 2020 at 8:20:38 PM UTC+3
Recovering cache nodes 0001
 
myserver
replication-group
Monday, July 6, 2020 at 8:19:14 PM UTC+3
Failover to replica node myserver-0001-002 completed
 
myserver
replication-group
Monday, July 6, 2020 at 8:17:59 PM UTC+3
Test Failover API called for node group 0001

AWS 客户支持声称,只要使用的 Redis 客户端在到副本节点 myserver-0001-002 的故障转移已完成 事件被触发(即 1m 和触发故障转移后 15 秒)它应该能够连接到它(即新提升的主机)。我们的客户端似乎仅在 Finished recovery for cache nodes 0001 事件触发后(即 7 分钟和 32 秒后)才重新连接。同时我们得到如下错误

org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: CLUSTERDOWN The cluster is down

在进行故障转移时,可以从redis-cli中看到以下信息。

endpoint:6379> cluster nodes
ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 172.31.11.216:6379@1122 master - 0 1594114396000 9 connected 0-8191
f8ff7a20f4c493b63ba65f107f575631faa4eb1b 172.31.11.52:6379@1122 slave 4ab10ca0a6a932179432769fcba7fab0faba01f7 0 1594114396872 2 connected
c18fee0e47800d792676c7d14782d81d7d1684e8 172.31.10.64:6379@1122 master,fail - 1594114079948 1594114077000 8 connected
4ab10ca0a6a932179432769fcba7fab0faba01f7 172.31.10.84:6379@1122 myself,master - 0 1594114395000 2 connected 8192-16383
endpoint:6379> cluster nodes
ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 172.31.11.216:6379@1122 master - 0 1594114461262 9 connected 0-8191
f8ff7a20f4c493b63ba65f107f575631faa4eb1b 172.31.11.52:6379@1122 slave 4ab10ca0a6a932179432769fcba7fab0faba01f7 0 1594114460000 2 connected
6a7339ae4df3c78e31c9cc8fd8cec4803eed5fc1 172.31.10.64:6379@1122 master - 0 1594114460256 0 connected
c18fee0e47800d792676c7d14782d81d7d1684e8 172.31.10.64:6379@1122 master,fail - 1594114079948 1594114077000 8 connected
4ab10ca0a6a932179432769fcba7fab0faba01f7 172.31.10.84:6379@1122 myself,master - 0 1594114458000 2 connected 8192-16383
endpoint:6379> cluster nodes
ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 172.31.11.216:6379@1122 master - 0 1594114509000 9 connected 0-8191
f8ff7a20f4c493b63ba65f107f575631faa4eb1b 172.31.11.52:6379@1122 slave 4ab10ca0a6a932179432769fcba7fab0faba01f7 0 1594114510552 2 connected
6a7339ae4df3c78e31c9cc8fd8cec4803eed5fc1 172.31.10.64:6379@1122 slave ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 0 1594114510000 9 connected
c18fee0e47800d792676c7d14782d81d7d1684e8 172.31.10.64:6379@1122 master,fail - 1594114079948 1594114077000 8 connected
4ab10ca0a6a932179432769fcba7fab0faba01f7 172.31.10.84:6379@1122 myself,master - 0 1594114508000 2 connected 8192-16383
endpoint:6379> cluster nodes
ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 172.31.11.216:6379@1122 master - 0 1594114548000 9 connected 0-8191
f8ff7a20f4c493b63ba65f107f575631faa4eb1b 172.31.11.52:6379@1122 slave 4ab10ca0a6a932179432769fcba7fab0faba01f7 0 1594114548783 2 connected
6a7339ae4df3c78e31c9cc8fd8cec4803eed5fc1 172.31.10.64:6379@1122 slave ffe51ecc6a8c1f32ab3774eb8f159bd64392dc14 0 1594114547776 9 connected
4ab10ca0a6a932179432769fcba7fab0faba01f7 172.31.10.84:6379@1122 myself,master - 0 1594114547000 2 connected 8192-16383

据我了解,Spring Session 使用的 Lettuce 是 Redis 集群感知的,因此在 XML 配置中使用了 RedisClusterConfiguration class。在 SO 以及 Lettuce 的 GitHub 问题页面上查看文档和一些类似问题,并没有让我清楚 Lettuce 如何在 Redis 集群模式下工作,特别是 AWS 将 IP 隐藏在一个公共端点下的诡计。

我的配置不应该让Lettuce连接到新晋升的master吗?我是否需要在 Lettuce 中启用不同的模式,以便它能够接收来自 Redis 的通知并切换到新的 master(e.b。拓扑刷新)?

此外,Lettuce 如何处理来自 AWS 的单个端点?它是解析 IP 然后使用它们吗?它们被缓存了吗?

如果我想启用所有四个节点的读取,我的配置是否足够?在 Redis 集群中(即,即使在 AWS 的上下文之外)当从属服务器提升为主服务器时,客户端是在轮询以获取信息还是集群以某种方式将其推送给客户端?

您拥有的任何资源(甚至 Lettuce 源文件)都可以阐明上述内容以及 Lettuce、Redis 和 AWS 上下文中的不同模式。

如你所见,我对此仍然有些困惑。

非常感谢您提供的任何帮助和信息。


更新

已启用调试并使用断点来拦截 bean 创建并以这种方式配置拓扑刷新。似乎通过 ClusterClientOptions:

的构造函数启用 ClusterTopologyRefreshTask
protected ClusterClientOptions(Builder builder) {
 
        super(builder);
 
        this.validateClusterNodeMembership = builder.validateClusterNodeMembership;
        this.maxRedirects = builder.maxRedirects;
 
        ClusterTopologyRefreshOptions refreshOptions = builder.topologyRefreshOptions;
 
        if (refreshOptions == null) {
            refreshOptions = ClusterTopologyRefreshOptions.builder() //
                    .enablePeriodicRefresh(DEFAULT_REFRESH_CLUSTER_VIEW) // Breakpoint here and enter to enable refreshing
                    .refreshPeriod(DEFAULT_REFRESH_PERIOD_DURATION) // Breakpoint here and enter to set the refresh interval
                    .closeStaleConnections(builder.closeStaleConnections) //
                    .build();
        }
 
        this.topologyRefreshOptions = refreshOptions;
    }

看起来刷新OK,但现在的问题是当Lettuce 通过Spring Session 而不是作为Redis 的普通客户端使用时如何配置它?

当我回答我的问题时,我意识到我还没有回答那个问题!所以这里是为了防止有人遇到同样的问题。

我最终做的是为 Redis 创建一个配置 bean,而不是使用 XML。代码如下:

@EnableRedisHttpSession
public class RedisConfig {
  private static final List<String> clusterNodes = Arrays.asList(System.getProperty("redis.endpoint"));

  @Bean
  public static ConfigureRedisAction configureRedisAction() {
    return ConfigureRedisAction.NO_OP;
  }

  @Bean(destroyMethod = "destroy")
  public LettuceConnectionFactory lettuceConnectionFactory() {
    RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodes);
    return new LettuceConnectionFactory(redisClusterConfiguration, getLettuceClientConfiguration());
  }


  private LettuceClientConfiguration getLettuceClientConfiguration() {
    ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder().enablePeriodicRefresh(Duration.ofSeconds(30)).build();

    ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build();

    return LettucePoolingClientConfiguration.builder().clientOptions(clusterClientOptions).build();
  }
}

然后,我没有通过 XML 注册我的 ContextLoaderListener,而是像这样使用初始化器:

public class Initializer extends AbstractHttpSessionApplicationInitializer {
  public Initializer() {
    super(RedisConfig.class);
  }
}

这样设置刷新好像OK了,不知道这样设置对不对!如果有人对更合适的解决方案有任何想法,请随时在这里发表评论。