首页 > 解决方案 > 使用 Lettuce 连接到 AWS ElastiCache Redis 的 Spring Session

问题描述

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

我的 AWS Redis 配置如下:

  1. 启用 Redis 集群
  2. 两个分片(即两个master)
  3. 每个主站一个从站

我的生菜配置如下:

<!-- 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 客户支持声称,只要在触发故障转移到副本节点 myserver-0001-002 完成事件时(即触发故障转移后的 1m 和 15s)使用的 Redis 客户端是 Redis Cluster 感知的,它应该能够连接到它(即新晋高手)。我们的客户端似乎只有在缓存节点 0001 的 Finished recovery事件被触发后(即 7m 和 32s 之后)才重新连接。同时我们得到如下错误

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

我的配置应该不够 Lettuce 连接到新提升的 master 吧?我是否需要在 Lettuce 中启用不同的模式才能接收来自 Redis 的通知并切换到新的主节点(eb 拓扑刷新)?

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

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

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

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

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


更新

启用了调试,并使用断点来拦截 bean 创建和配置拓扑刷新。似乎ClusterTopologyRefreshTask通过以下构造函数启用ClusterClientOptions

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;
    }

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

标签: amazon-web-servicesredis-clusterlettuce

解决方案


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

我最终做的是为 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,但我不知道它是否是正确的做法!如果有人对更合适的解决方案有任何想法,请随时在此处发表评论。


推荐阅读