amazon-web-services - 使用 Lettuce 连接到 AWS ElastiCache Redis 的 Spring Session
问题描述
我正在使用 Spring Session 将我们的会话外部化到 Redis (AWS ElastiCache)。Lettuce 被用作 Redis 的客户端。
我的 AWS Redis 配置如下:
- 启用 Redis 集群
- 两个分片(即两个master)
- 每个主站一个从站
我的生菜配置如下:
<!-- 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 的普通客户端时,如何配置它?
解决方案
当我处理我的问题时,我意识到我还没有回答那个问题!所以这里是为了以防有人遇到同样的问题。
我最终做的是为 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,但我不知道它是否是正确的做法!如果有人对更合适的解决方案有任何想法,请随时在此处发表评论。
推荐阅读
- javascript - 高图表数据表未按范围选择器过滤数据
- tensorflow - Tensorflow 如何使用多个 GPU 进行单独训练?
- grpc - 如何为 gRPC 创建和使用自定义传输通道?
- javascript - 与图例 D3.js 分组条形图的交互
- sql - 选择距当前日期最多两天的列 [SQLite3]
- objective-c - macOS:使用父窗口移动子窗口时出现问题
- amazon-rds - 如何解决创建 MySQL 5.5.53 只读副本时出现错误 InvalidParameterCombination、Status 400?
- flutter - 如何使曲线角像使用剪辑路径或任何其他小部件附加的文件一样颤动?
- laravel - findWhere() 函数在 laravel 查询中的作用
- lua - ROBLOX 战斗系统脚本 - 语法错误:预期 ')' 在第 7 行关闭 '('),得到 ','