关键步骤:

  1. Spring Boot 启动时创建 LettuceConnectionFactory

  2. 根据配置类型(集群/哨兵/单机)初始化客户端

  3. 对于集群模式:

    • 创建 RedisClusterClient

    • 调用 setOptions(getClusterClientOptions(configuration)) 应用配置

2. 节点状态检查机制

拓扑刷新选项加载:

java

private ClusterTopologyRefreshOptions getClusterTopologyRefreshOptions() {ClusterClientOptions clusterClientOptions = redisClusterClient.getClusterClientOptions();return clusterClientOptions != null ? clusterClientOptions.getTopologyRefreshOptions() : FALLBACK_OPTIONS;
}
状态检查触发条件:

java

private boolean isEnabled(RefreshTrigger refreshTrigger) {return getClusterTopologyRefreshOptions().getAdaptiveRefreshTriggers().contains(refreshTrigger);
}

3. 失败重连原理

重连事件处理:

java

@Override
public void onReconnectAttempt(int attempt) {if (isEnabled(RefreshTrigger.PERSISTENT_RECONNECTS) &&attempt >= getRefreshTriggersReconnectAttempts()) {if (indicateTopologyRefreshSignal()) {emitAdaptiveRefreshScheduledEvent();}}
}
工作流程:
  1. 当发生网络断开时,Lettuce 自动尝试重连

  2. 每次重连尝试都会调用 onReconnectAttempt 方法

  3. 检查是否启用了 PERSISTENT_RECONNECTS 触发器

  4. 检查重连次数是否达到阈值(refreshTriggersReconnectAttempts

  5. 如果满足条件,触发拓扑刷新事件

4. 拓扑刷新执行过程

刷新激活机制:

java

private void activateTopologyRefreshIfNeeded() {if (getOptions() instanceof ClusterClientOptions) {ClusterClientOptions options = (ClusterClientOptions) getOptions();ClusterTopologyRefreshOptions topologyRefreshOptions = options.getTopologyRefreshOptions();if (!topologyRefreshOptions.isPeriodicRefreshEnabled() || clusterTopologyRefreshActivated.get()) {return;}if (clusterTopologyRefreshActivated.compareAndSet(false, true)) {// 创建定时刷新任务ScheduledFuture<?> scheduledFuture = genericWorkerPool.scheduleAtFixedRate(clusterTopologyRefreshScheduler,options.getRefreshPeriod().toNanos(),options.getRefreshPeriod().toNanos(),TimeUnit.NANOSECONDS);clusterTopologyRefreshFuture.set(scheduledFuture);}}
}
刷新过程:
  1. 检查周期性刷新是否启用

  2. 确保只有一个刷新任务被激活(原子操作)

  3. 创建定时任务,按配置的时间间隔执行刷新

  4. 刷新时重新获取集群拓扑信息

  5. 更新客户端内部节点路由表

5. 其他触发机制

除了重连触发外,还有多种触发条件:

java

public enum RefreshTrigger {MOVED_REDIRECT,    // MOVED 重定向ASK_REDIRECT,      // ASK 重定向PERSISTENT_RECONNECTS, // 持续重连UNCOVERED_SLOT,    // 未覆盖的槽位UNKNOWN_NODE       // 未知节点
}

对应的事件处理方法:

java

@Override
public void onMovedRedirection() {if (isEnabled(RefreshTrigger.MOVED_REDIRECT)) {if (indicateTopologyRefreshSignal()) {emitAdaptiveRefreshScheduledEvent();}}
}

6. 配置生效关键点

在 Spring Boot 中确保配置生效的关键:

  1. 集群模式必须启用:仅当使用集群配置时相关逻辑才会执行

  2. 正确设置 ClientOptions:通过 LettuceClientConfigurationBuilderCustomizer 注入

  3. 配置拓扑刷新选项:必须包含 PERSISTENT_RECONNECTS 触发器

  4. 设置合理的重连阈值adaptiveRefreshTriggersReconnectAttempts

7. 完整工作流程

图表

代码

最佳实践建议:

  1. 多触发器组合配置:同时启用多种触发器提高集群感知能力

    java

    .enableAdaptiveRefreshTrigger(RefreshTrigger.PERSISTENT_RECONNECTS,RefreshTrigger.MOVED_REDIRECT,RefreshTrigger.UNKNOWN_NODE
    )
  2. 合理设置重连阈值:根据网络环境调整

    java

    .adaptiveRefreshTriggersReconnectAttempts(3) // 生产环境建议 3-5
  3. 配置超时保护:防止拓扑刷新阻塞

    java

    .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10))
  4. 启用周期性刷新:作为故障恢复的补充

    java

    .enablePeriodicRefresh(Duration.ofMinutes(10))

通过以上机制,Lettuce 能够在集群节点故障或网络问题时自动更新拓扑信息,保持客户端与集群的同步,实现高可用连接。

##配置重连

import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Configuration;
import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@Configuration
public class RedisClusterConfig {@Beanpublic LettuceClientConfiguration lettuceClientConfiguration() {// 👇 配置集群拓扑刷新选项ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder().enablePeriodicRefresh(true)  // 开启周期刷新.refreshPeriod(Duration.ofSeconds(30))  // 每30秒刷新一次.enableAllAdaptiveRefreshTriggers()
//                .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.ASK_REDIRECT)
//                .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT)
//                .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.UNCOVERED_SLOT)
//                .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.UNKNOWN_NODE)
//                .enableAdaptiveRefreshTrigger(ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS).adaptiveRefreshTriggersTimeout(Duration.ofSeconds(10)).refreshTriggersReconnectAttempts(5).build();return LettuceClientConfiguration.builder().clientOptions(ClusterClientOptions.builder().topologyRefreshOptions(topologyRefreshOptions).build()).build();}@Beanpublic LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties, LettuceClientConfiguration lettuceClientConfiguration) {RedisProperties.Cluster clusterProperties = redisProperties.getCluster();RedisClusterConfiguration config = new RedisClusterConfiguration(clusterProperties.getNodes());if (clusterProperties.getMaxRedirects() != null) {config.setMaxRedirects(clusterProperties.getMaxRedirects());}if (redisProperties.getPassword() != null) {config.setPassword(RedisPassword.of(redisProperties.getPassword()));}return new LettuceConnectionFactory(config, lettuceClientConfiguration);}// 可选:配置 RedisTemplate@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}}

##源码

@Bean@ConditionalOnMissingBean(RedisConnectionFactory.class)LettuceConnectionFactory redisConnectionFactory(ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,ClientResources clientResources) throws UnknownHostException {LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,getProperties().getLettuce().getPool());return createLettuceConnectionFactory(clientConfig);}private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {if (getSentinelConfig() != null) {return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);}if (getClusterConfiguration() != null) {return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);}return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);}public LettuceConnectionFactory(RedisClusterConfiguration clusterConfiguration,LettuceClientConfiguration clientConfig) {this(clientConfig);Assert.notNull(clusterConfiguration, "RedisClusterConfiguration must not be null!");this.configuration = clusterConfiguration;}//createClient创建client 
public void afterPropertiesSet() {this.client = createClient();this.connectionProvider = createConnectionProvider(client, CODEC);this.reactiveConnectionProvider = createConnectionProvider(client, LettuceReactiveRedisConnection.CODEC);if (isClusterAware()) {this.clusterCommandExecutor = new ClusterCommandExecutor(new LettuceClusterTopologyProvider((RedisClusterClient) client),new LettuceClusterConnection.LettuceClusterNodeResourceProvider(this.connectionProvider),EXCEPTION_TRANSLATION);}if (getEagerInitialization() && getShareNativeConnection()) {initConnection();}}//clusterClient设置configuration配置  clusterClient.setOptions(getClusterClientOptions(configuration));
protected AbstractRedisClient createClient() {if (isStaticMasterReplicaAware()) {RedisClient redisClient = clientConfiguration.getClientResources() //.map(RedisClient::create) //.orElseGet(RedisClient::create);clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);return redisClient;}if (isRedisSentinelAware()) {RedisURI redisURI = getSentinelRedisURI();RedisClient redisClient = clientConfiguration.getClientResources() //.map(clientResources -> RedisClient.create(clientResources, redisURI)) //.orElseGet(() -> RedisClient.create(redisURI));clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);return redisClient;}if (isClusterAware()) {List<RedisURI> initialUris = new ArrayList<>();ClusterConfiguration configuration = (ClusterConfiguration) this.configuration;for (RedisNode node : configuration.getClusterNodes()) {initialUris.add(createRedisURIAndApplySettings(node.getHost(), node.getPort()));}RedisClusterClient clusterClient = clientConfiguration.getClientResources() //.map(clientResources -> RedisClusterClient.create(clientResources, initialUris)) //.orElseGet(() -> RedisClusterClient.create(initialUris));clusterClient.setOptions(getClusterClientOptions(configuration));return clusterClient;}RedisURI uri = isDomainSocketAware()? createRedisSocketURIAndApplySettings(((DomainSocketConfiguration) configuration).getSocket()): createRedisURIAndApplySettings(getHostName(), getPort());RedisClient redisClient = clientConfiguration.getClientResources() //.map(clientResources -> RedisClient.create(clientResources, uri)) //.orElseGet(() -> RedisClient.create(uri));clientConfiguration.getClientOptions().ifPresent(redisClient::setOptions);return redisClient;}private ClusterTopologyRefreshOptions getClusterTopologyRefreshOptions() {ClusterClientOptions clusterClientOptions = redisClusterClient.getClusterClientOptions();if (clusterClientOptions != null) {return clusterClientOptions.getTopologyRefreshOptions();}return FALLBACK_OPTIONS;}public ClusterTopologyRefreshOptions getTopologyRefreshOptions() {return topologyRefreshOptions;}private boolean isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger refreshTrigger) {return getClusterTopologyRefreshOptions().getAdaptiveRefreshTriggers().contains(refreshTrigger);}@Overridepublic void onReconnectAttempt(int attempt) {if (isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger.PERSISTENT_RECONNECTS)&& attempt >= getClusterTopologyRefreshOptions().getRefreshTriggersReconnectAttempts()) {if (indicateTopologyRefreshSignal()) {emitAdaptiveRefreshScheduledEvent();}}}@Overridepublic void onMovedRedirection() {if (isEnabled(ClusterTopologyRefreshOptions.RefreshTrigger.MOVED_REDIRECT)) {if (indicateTopologyRefreshSignal()) {emitAdaptiveRefreshScheduledEvent();}}}private void activateTopologyRefreshIfNeeded() {if (getOptions() instanceof ClusterClientOptions) {ClusterClientOptions options = (ClusterClientOptions) getOptions();ClusterTopologyRefreshOptions topologyRefreshOptions = options.getTopologyRefreshOptions();if (!topologyRefreshOptions.isPeriodicRefreshEnabled() || clusterTopologyRefreshActivated.get()) {return;}if (clusterTopologyRefreshActivated.compareAndSet(false, true)) {ScheduledFuture<?> scheduledFuture = genericWorkerPool.scheduleAtFixedRate(clusterTopologyRefreshScheduler,options.getRefreshPeriod().toNanos(), options.getRefreshPeriod().toNanos(), TimeUnit.NANOSECONDS);clusterTopologyRefreshFuture.set(scheduledFuture);}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96148.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96148.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96148.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从ChatGPT到智能助手:Agent智能体如何颠覆AI应用

从ChatGPT到智能助手&#xff1a;Agent智能体如何颠覆AI应用 更多大模型知识分享&#xff0c;尽在>>>GitHub<<< Agent 智能体是什么 简单来说&#xff0c;Agent 智能体是一种能够感知环境&#xff0c;并根据自身目标自主采取行动的智能实体。它就像是一个拥…

Spring Boot应用实现图片资源服务

在这篇文章中&#xff0c;我们将介绍如何使用Spring Boot创建一个REST API来提供服务器上的静态图片资源。该API包括路径安全检查、文件存在性验证以及缓存控制等功能&#xff0c;并且代码包含详细的注释以帮助理解。Maven依赖 首先&#xff0c;在您的pom.xml文件中添加以下依赖…

Word 中 MathType 公式编号问题与解决

注&#xff1a;本文为 “Word 中 MathType 公式编号” 相关合辑。 图片清晰度受引文原图所限。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 【Word】解决 MathType 已插入公式按新章节开始编号的问题 Allan326 于 2020-03-25 15:30:08 发布 问题…

19. 大数据-产品概念

文章目录前言一、数据库1. 简介2. 使用场景3. 数据库类型4. 数据类型二、数据仓库1. 简介2. 使用场景3. 数据仓库架构三、数据平台1. 简介2. 使用场景3. 数据仓库架构四、数据中台1. 简介2. 使用场景3. 数据中台架构五、数据湖1. 简介2. 使用场景3. 数据湖架构六、总结1. 区别2…

python学习DAY46打卡

DAY 46 通道注意力(SE注意力) 内容&#xff1a; 不同CNN层的特征图&#xff1a;不同通道的特征图什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。通道注意力&#xff1a;模型的定义和插入的位置通道注意…

Ansible 中的文件包含与导入机制

Ansible 中的文件包含与导入机制本文介绍了在 Ansible 中如何通过模块化方式管理复杂的 Playbook&#xff0c;包括使用 include 和 import 系列语句来拆分和重用代码。概述 当 Playbook 变得冗长或复杂时&#xff0c;可以将其拆分为多个小文件以提高可管理性。Ansible 提供了模…

OpenCV-循环读取视频帧,对每一帧进行处理

原型代码 内存模型&#xff1a; 核心变量&#xff1a;frame&#xff0c;Numpy ndarray&#xff0c;每次会被覆盖&#xff0c;大小保持恒定import cv2video_path your_video.mp4cap cv2.VideoCapture(video_path)if not cap.isOpened():print("Cant open Video")exi…

决策树的学习(二)

一、整体框架本 PPT 聚焦机器学习中的决策树算法&#xff0c;围绕 “核心算法&#xff08;ID3、C4.5、CART&#xff09;→ 特殊问题&#xff08;连续值处理&#xff09;→ 优化策略&#xff08;剪枝&#xff09;→ 代码实现→ 课堂练习” 展开&#xff0c;系统补充决策树的进阶…

粗粮厂的基于spark的通用olap之间的同步工具项目

粗粮厂的基于spark的通用olap之间的同步工具项目1 项目背景2 项目实现2.1 实现原理2.2 细节要点3 抽样说明4 项目运行状态4.1 运行速度4.2 项目吞吐4.3 稳定性说的比较简单&#xff0c;有需要的可以留言&#xff0c;我不断补充完善1 项目背景 我们公司内部的需要一款&#xff…

C# 时间戳

在C#中&#xff0c;获取当前时间的毫秒级时间戳可以通过多种方式实现。以下是几种常见的方法&#xff1a;方法1&#xff1a;使用DateTime和DateTimeOffsetlong timestamp (long)(DateTimeOffset.Now.ToUnixTimeMilliseconds()); Console.WriteLine(timestamp);方法2&#xff1…

【牛客刷题】REAL792 小O的平面画圆

文章目录 一、题目介绍 1.1 输入描述 1.2 输出描述 1.3 示例 二、算法设计思路 2.1 核心问题分析 2.2 图解两个圆的位置关系 2.2.1. 相离 (Separate) 2.2.2. 外切 (Externally Tangent) 2.2.3. 相交 (Intersecting) 2.2.4. 内切 (Internally Tangent) 2.2.5. 包含 (Containing)…

uniapp:微信小程序使用Canvas 和Canvas 2D绘制图形

一、Canvas 画布 canvas 组件 提供了绘制界面&#xff0c;可以在之上进行任意绘制 功能描述 Canvas 画布。2.9.0 起支持一套新 Canvas 2D 接口&#xff08;需指定 type 属性&#xff09;&#xff0c;同时支持同层渲染&#xff0c;原有接口不再维护。 二、Canvas 和Canvas 2D 区…

word如何转换为pdf

pip install pywin32import os import win32com.client import pythoncom # 新增&#xff1a;用于处理COM线程 import sysdef docx_to_pdf(docx_path, pdf_pathNone):"""将Word文档转换为PDF格式&#xff0c;修复退出时的COM错误"""if not os.p…

服务器Linux防火墙怎样实现访问控制

在互联网世界里&#xff0c;Linux服务器就像一座城池&#xff0c;而防火墙便是城池的守卫者。没有防火墙&#xff0c;外部的任何流量都能毫无阻拦地进入服务器;而有了防火墙&#xff0c;就可以像设关卡一样&#xff0c;对进出城门的人进行盘查和控制。对企业运维人员来说&#…

【原创理论】Stochastic Coupled Dyadic System (SCDS):一个用于两性关系动力学建模的随机耦合系统框架

【原创理论】Stochastic Coupled Dyadic System (SCDS)&#xff1a;一个用于两性关系动力学建模的随机耦合系统框架 作者&#xff1a;[望月&#xff0c;GPT5,GPT-O3,Gemini2.5pro] 分类&#xff1a; 人工智能 理论模型 交叉学科 系统科学 人性 爱情 标签&#xff1a; 关系动力…

星图云开发者平台新功能速递 | 微服务管理器:无缝整合异构服务,释放云原生开发潜能

在构建现代数字化应用的过程中&#xff0c;开发者常常面临一个关键挑战&#xff1a;如何高效、安全地集成和复用既有的复杂服务或自有业务系统&#xff1f;这些服务可能是核心算法引擎、遗留业务逻辑模块&#xff0c;或是特定的SaaS能力。传统方式下&#xff0c;将它们融入新的…

数据结构:构建 (create) 一个二叉树

目录 问题的本质——什么信息才能唯一确定一棵树&#xff1f; 推导“最佳拍档”——哪两种遍历序列能行&#xff1f; 递归思想——如何构建一棵树&#xff1f; 第1步&#xff1a;确定整棵树的根节点 第2步&#xff1a;划分左右子树的成员 第3步&#xff1a;递归构建左右子…

【STM32】HAL库中的实现(五):ADC (模数转换)

什么是 ADC&#xff08;模数转换器&#xff09; ADC&#xff08;Analog to Digital Converter&#xff09;是将 模拟信号&#xff08;电压&#xff09;转换成数字信号&#xff08;数值&#xff09; 的器件。 在 STM32 中&#xff0c;ADC 通常具有以下特性&#xff1a;特性描述分…

智慧校园中IPTV融合对讲:构建高效沟通新生态

在智慧校园的建设浪潮里&#xff0c;IPTV融合对讲系统宛如一颗璀璨的新星&#xff0c;以其独特的功能和强大的优势&#xff0c;为校园的沟通与管理带来了全新的变革&#xff0c;构建起一个高效、便捷、智能的沟通新生态。从日常沟通层面来看&#xff0c;IPTV融合对讲系统打破了…

智能合约里的 “拒绝服务“ 攻击:让你的合约变成 “死机的手机“

你有没有遇到过手机突然卡死&#xff0c;点什么都没反应的情况&#xff1f;在区块链世界里&#xff0c;智能合约也可能遭遇类似的 "罢工"—— 这就是 "拒绝服务攻击"&#xff08;Denial of Service&#xff0c;简称 DoS&#xff09;。今天用大白话讲讲合约…