SpringBoot集成etcd,实现实时监听,实现配置中心
etcd 是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write
barriers)来实现可靠的分布式协作。etcd集群是为高可用,持久性数据存储和检索而准备。
以下代码实现的主要业务是:通过etcd自带监听功能,动态将监听的key进行缓存到本地缓存,达到实时监听key的变化,并且不需要多次的网络请求。
Pom依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- cache 缓存 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- jetcd-core --><dependency><groupId>io.etcd</groupId><artifactId>jetcd-core</artifactId><version>0.7.6</version></dependency>
yaml配置
etcd:watch-key-prefix: yn-demoendpoints:- http://127.0.0.1:2379- http://127.0.0.1:2380
参数说明:
watch-key-prefix: 参数用于限制服务监听的前缀key
endpoints:etcd的连接url
配置类
EtcdProperties (etcd 属性配置)
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.net.URI;/*** etcd 属性配置** @author yunnuo* @date 2023-09-25*/
@Data
@Component
@ConfigurationProperties(prefix = "etcd")
public class EtcdProperties {/*** etcd url*/private List<URI> endpoints;/*** 监听key的前缀*/private String watchKeyPrefix;}
EtcdConfig(配置类)
import io.etcd.jetcd.Client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;/*** etcd 配置类** @author yunnuo * @date 2023-09-25*/
@Configuration
public class EtcdConfig {@Resourceprivate EtcdProperties etcdProperties;@Beanpublic Client etcdClient(){return Client.builder().endpoints(etcdProperties.getEndpoints()).build();}}
etcd 实现监听功能(核心)
监听器
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ukayunnuo.config.EtcdProperties;
import com.ukayunnuo.enums.WatchKeyStatus;
import io.etcd.jetcd.*;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.options.WatchOption;
import io.etcd.jetcd.watch.WatchEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;/*** etcd 监听器** @author yunnuo <a href="2552846359@qq.com">Email: 2552846359@qq.com</a>* @date 2023-09-25*/
@Slf4j
@Component
public class EtcdKeyWatcher {@Resourceprivate Client etcdClient;@Resourceprivate EtcdProperties etcdProperties;private final Cache watchedKeysCache;public static final String CACHE_ETCD_KEYS_FILED = "etcdKeys";public EtcdKeyWatcher(CacheManager cacheManager) {this.watchedKeysCache = cacheManager.getCache(CACHE_ETCD_KEYS_FILED);}/*** 监听并存储到缓存** @param key 配置key* @return 监听结果*/public WatchKeyStatus watchKeyHandlerAndCache(String key) {if (Objects.nonNull(watchedKeysCache.get(key))) {return WatchKeyStatus.NO_NEED_MONITOR;}if (StrUtil.isBlank(etcdProperties.getWatchKeyPrefix())) {return WatchKeyStatus.NO_MONITOR;}boolean keyPrefixFlag = Arrays.stream(etcdProperties.getWatchKeyPrefix().split(",")).filter(StrUtil::isNotBlank).map(String::trim).anyMatch(prefix -> key.substring(0, key.indexOf(".")).equals(prefix));if (Boolean.FALSE.equals(keyPrefixFlag)) {String value = getValueForKVClient(key);if (StrUtil.isNotBlank(value)) {// 直接缓存, 不进行监听watchedKeysCache.put(key, value);return WatchKeyStatus.CACHE_NO_MONITOR;}return WatchKeyStatus.FAILED;}WatchOption watchOption = WatchOption.builder().withRange(ByteSequence.from(key, StandardCharsets.UTF_8)).build();Watch.Listener listener = Watch.listener(res -> {for (WatchEvent event : res.getEvents()) {log.info("Watch.listener event:{}", JSONObject.toJSONString(event));KeyValue keyValue = event.getKeyValue();if (Objects.nonNull(keyValue)) {// 将监听的键缓存到本地缓存中watchedKeysCache.put(keyValue.getKey().toString(StandardCharsets.UTF_8), keyValue.getValue().toString(StandardCharsets.UTF_8));log.info("watchClient.watch succeed! key:{}", key);}}});Watch watchClient = etcdClient.getWatchClient();watchClient.watch(ByteSequence.from(key, StandardCharsets.UTF_8), watchOption, listener);return WatchKeyStatus.SUCCEEDED;}/*** 获取 etcd中的 key值* @param key 配置key* @return 结果*/public String getValueForKVClient(String key) {KV kvClient = etcdClient.getKVClient();ByteSequence keyByteSequence = ByteSequence.from(key, StandardCharsets.UTF_8);GetResponse response;try {response = kvClient.get(keyByteSequence).get();} catch (Exception e) {// 处理异常情况log.error("etcdClient.getKVClient error! key:{}, e:{}", key, e.getMessage(), e);return null;}if (response.getKvs().isEmpty()) {return null;}return response.getKvs().get(0).getValue().toString(StandardCharsets.UTF_8);}}
监听枚举类
/*** 监听key 状态枚举** @author yunnuo <a href="2552846359@qq.com">Email: 2552846359@qq.com</a>* @date 2023-09-26*/
public enum WatchKeyStatus {/*** 监听成功*/SUCCEEDED,/*** 监听失败*/FAILED,/*** 无需再次监听*/NO_NEED_MONITOR,/*** 不监听*/NO_MONITOR,/*** 走缓存,但是没有进行监听*/CACHE_NO_MONITOR,;
}
etcd 工具类
import com.ukayunnuo.enums.WatchKeyStatus;
import com.ukayunnuo.watcher.EtcdKeyWatcher;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.kv.PutResponse;
import io.netty.util.internal.StringUtil;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;/*** etcd 处理工具类** @author yunnuo <a href="2552846359@qq.com">Email: 2552846359@qq.com</a>* @date 2023-09-26*/
@Component
public class EtcdHandleUtil {@Resourceprivate Client etcdClient;@Resourceprivate EtcdKeyWatcher etcdKeyWatcher;private final Cache watchedKeysCache;public static final String CACHE_ETCD_KEYS_FILED = "etcdKeys";public EtcdHandleUtil(CacheManager cacheManager) {this.watchedKeysCache = cacheManager.getCache(CACHE_ETCD_KEYS_FILED);}/*** 监听并缓存** @param key key* @return 监听结果*/public WatchKeyStatus watchKeyHandlerAndCache(String key) {return etcdKeyWatcher.watchKeyHandlerAndCache(key);}/*** put Key** @param key key* @param value 值* @return 结果*/public CompletableFuture<PutResponse> put(String key, String value) {return etcdClient.getKVClient().put(ByteSequence.from(key, StandardCharsets.UTF_8), ByteSequence.from(value, StandardCharsets.UTF_8));}/*** 获取值** @param key key* @return 结果*/public String get(String key) {Optional<Cache.ValueWrapper> valueWrapper = Optional.ofNullable(watchedKeysCache.get(key));if (valueWrapper.isPresent()) {return Objects.requireNonNull(valueWrapper.get().get()).toString();}return StringUtil.EMPTY_STRING;}/*** 获取值** @param key key* @return 结果*/@Nullablepublic <T> T get(String key, @Nullable Class<T> type) {return watchedKeysCache.get(key, type);}/*** 获取值** @param key key* @return 结果*/@Nullablepublic <T> T get(String key, Callable<T> valueLoader) {return watchedKeysCache.get(key, valueLoader);}
}
进行测试
请求dto
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;/*** ETCD test req** @author yunnuo <a href="2552846359@qq.com">Email: 2552846359@qq.com</a>* @date 2023-09-26*/
@Data
public class EtcdReq {private String key;private String value;@Overridepublic String toString() {return JSONObject.toJSONString(this);}
}
controller API接口测试
/*** 测试类** @author yunnuo <a href="2552846359@qq.com">Email: 2552846359@qq.com</a>* @date 2023-09-26*/
@Slf4j
@RequestMapping("/etcd/demo")
@RestController
public class EtcdTestController {@Resourceprivate EtcdHandleUtil etcdHandleUtil;@PostMapping("/pushTest")public Result<PutResponse> pushTest(@RequestBody EtcdReq req) throws ExecutionException, InterruptedException {PutResponse putResponse = etcdHandleUtil.put(req.getKey(), req.getValue()).get();WatchKeyStatus watchKeyStatus = etcdHandleUtil.watchKeyHandlerAndCache(req.getKey());log.info("pushTest req:{}, putResponse:{}, watchKeyStatus:{}", req, JSONObject.toJSONString(putResponse), watchKeyStatus);return Result.success(putResponse);}@PostMapping("/get")public Result<String> get(@RequestBody EtcdReq req) {return Result.success(etcdHandleUtil.get(req.getKey()));}}
相关文章:
SpringBoot集成etcd,实现实时监听,实现配置中心
etcd 是一个分布式键值对存储,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用,持久性数据存储和检索而准备。 以下代码实现的主要业务是…...
JavaScript元素根据父级元素宽高缩放
/*** 等比缩放* param wrap 外部容器* param container 待缩放的容器* returns {{width: number, height: number}}* 返回值:width:宽度, height:高度*/aspectRatio(wrap: any, container: any) {// w h / ratio, h w * ratioconst wrapW wrap.width;const wrapH…...
易趋产品升级(EasyTrack 11_V1.3) | 集成飞书、WPS、个性化设置,增强团队协作和用户体验
企业在项目管理过程中,经常会遇到项目信息同步不及时、沟通障碍以及管理软件使用不便捷等难题,导致团队协作效率低下。这种情况下,如果使用了多个办公软件(如:钉钉、企业微信、项目管理软件等),…...
帆软FineBi V6版本经验总结
帆软FineBi V6版本经验总结 BI分析出现背景 现在是一个大数据的时代,每时每刻都有海量的明细数据出现。这时大数据时代用户思维是:1、数据的爆炸式增长,人们比起明细数据,更在意样本的整体特征、相互关系。2、基于明细的“小…...
03.MySQL的体系架构
MySQL的体系架构 一、MySQL简介二、MySQL的体系架构三、MySQL的内存结构四、MySQL的文件结构 一、MySQL简介 MySQL是一个开源的关系型数据库管理系统(RDBMS),由瑞典MySQL AB公司开发,后被Sun公司收购,Sun公司被Oracle…...
随笔笔记-2023
随笔 computed 是基于他们的依赖进行缓存的,。如果要随时计算 new Date().now(因为不是响应式的),那么需要用 computed。 如果不希望用缓存那么就用 methods 字符与字节 1 字节8 位1B8 bit;1KB 1024B,1MB1024KB1024*1024B 编码:…...
2023.12.31 Python 词频统计
练习:使用Python中的filter、map、reduce实现词频统计 样例数据: hello world java python java java hadoop spark spark python 需求分析: 1- 文件中有如上的示例数据 2- 读取文件内容。可以通过readline() 3- 将一行内容切分得到多个单…...
day12--java高级编程:网络通讯
5 Day19–网络通信(Socket通信) 说明: io流是跟本地的文件进行数据的传输,读或者写。网络通信:数据在网络中进行的传输。 本章专题与脉络 1. 网络编程概述 Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持&…...
MongoDB聚合:$out
$out阶段将聚合管道产生的文档写入到指定的集合,从MongoDB4.4开始,支持指定数据库。$out阶段必须放在聚合管道的最后,支持聚合结果任意大小的数据集。 警告: 如果指定的集合已经存在则会被替换。 语法 用法 1: 定数…...
一次奇葩的spin_lock_irq / spin_unlock_irq使用不当导致的系统卡死分析
这是在调试内核block层时遇到的一例奇葩的soft lock锁死问题(内核版本centos 8.3,4.18.0-240),现场如下: [ 760.247152] watchdog: BUG: soft lockup - CPU#0 stuck for 23s! [kworker/0:1:2635]……………..[ 760.247184] CPU: 0 PID: 26…...
公司创建百度百科需要哪些内容?
一个公司或是一个品牌想要让自己更有身份,更有知名度,更有含金量,百度百科词条是必不可少的。通过百度百科展示公司的详细信息,有助于增强用户对公司的信任感,提高企业形象。通过百度百科展示公司的发展历程、领导团队…...
qt中信号槽第五个参数
文章目录 connent函数第五个参数的作用自动连接(Qt::AutoConnection)直接连接(Qt::DirectConnection - 同步)同线程不同线程 队列连接(Qt::QueuedConnection - 异步)同一线程不同线程 锁定队列连接(Qt::BlockingQueuedConnection) connent函数第五个参数的作用 connect(const …...
模式识别与机器学习-SVM(线性支持向量机)
线性支持向量机 线性支持向量机间隔距离学习的对偶算法算法:线性可分支持向量机学习算法线性可分支持向量机例子 谨以此博客作为复习期间的记录 线性支持向量机 在以上四条线中,都可以作为分割平面,误差率也都为0。但是那个分割平面效果更好呢࿱…...
【并行计算】GPU,CUDA
一、CUDA层次结构 1.kernel核函数 一个CUDA程序是一个kernel核函数被GPU的多个计算单元并行执行的过程,CUDA给了如下抽象 dim3 threadsPerBlock(4, 3, 1); dim3 numBlocks(3, 2, 1); matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C); 2.G…...
计算机网络教案——计算机网络设备章节
第五章 计算机网络设备 一、教学目标: 1. 了解计算机网络的主要设备 2. 了解计算机网络设备的主要原理 3. 掌握计算机网络设备的基本用途 4. 掌握计算机网络设备的使用常识 二、教学重点、难点 计算机网络设备的主要原理 三、技能培训重点、难点 计算机网络设备的使用…...
什么是SLAM中的回环检测,如果没有回环检测会怎样
目录 什么是回环检测 如果没有回环检测 SLAM(Simultaneous Localization and Mapping,即同时定位与地图构建)是一种使机器人或自动驾驶汽车能够在未知环境中建立地图的同时定位自身位置的技术。回环检测(Loop Closure Detectio…...
ubuntu 通过文件设置静态IP、DNS、网关
1. 确定网络接口名称 首先,使用 ip a 命令确定您要配置的网络接口名称。 2. 编辑 Netplan 配置文件 使用文本编辑器(如 nano)打开或创建 Netplan 配置文件: sudo nano /etc/netplan/01-netcfg.yaml3. 输入 Netplan 配置 在编…...
mapboxgl 中热力图的实现以及给热力图点增加鼠标移上 popup 效果
文章目录 概要效果预览技术思路技术细节小结 概要 本篇文章还是关于最近做到的 mapboxgl 地图展开的。 借鉴官方示例:https://iclient.supermap.io/examples/mapboxgl/editor.html#heatMapLayer 效果预览 技术思路 将接口数据渲染到地图中形成热力图。还需要将热…...
golang并发安全-sync.map
sync.map解决的问题 golang 原生map是存在并发读写的问题,在并发读写时候会抛出异常 func main() {mT : make(map[int]int)g1 : []int{1, 2, 3, 4, 5, 6}g2 : []int{4, 5, 6, 7, 8, 9}go func() {for i : range g1 {mT[i] i}}()go func() {for i : range g2 {mT[…...
开发第一个SpringBoot程序
使用命令创建Maven工程 mvn archetype:generate -DgroupIdorg.sang -DartifactIdchapter01 -DarchetypeArtifactIdmaven-archetype-quickstart -DinteractiveModefalse 参数说明: -DgroupId 组织Id(项目包名) -DartifactId 项目名称或模块…...
告别手打公式!用SimpleTex截图转LaTeX+Axmath微调+Typora排版的保姆级教程
数学公式高效处理全流程:从截图识别到专业排版 每次在论文或笔记中插入复杂的数学公式时,你是否也经历过这样的痛苦?反复核对LaTeX代码中的每个括号,调整上下标位置,或是为了一个特殊符号翻遍文档。传统的手动输入方式…...
Miniconda环境迁移实战:如何将CentOS装好的Python环境打包到其他服务器?
Miniconda环境迁移实战:跨服务器Python环境无缝转移指南 当你在CentOS服务器上精心配置了一个完美的Python数据分析环境,却需要在另一台服务器上复现时,难道要重新经历一遍繁琐的安装过程?本文将揭示两种高效可靠的Miniconda环境迁…...
从SWF中提取供应链安全控制:JPEXS Free Flash Decompiler安全研究
从SWF中提取供应链安全控制:JPEXS Free Flash Decompiler安全研究 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler JPEXS Free Flash Decompiler是一款强大的开源工具&#x…...
AI的“血管”:从大模型需求看6G、高速光纤与智算中心网络的技术变革
大模型训练与推理的爆发,正以前所未有的力度重塑通信网络基础设施。6G、高速光纤、智算中心网络,正成为AI基础设施的“血管”,承载着算力的血液,决定智能的极限。当GPT-5.4的推理能力逼近人类专家,当Sora可以生成一分钟…...
AI驱动关键词优化的SEO未来趋势与实际应用解析
本文旨在探讨AI在搜索引擎优化(SEO),特别是关键词优化领域的重要角色。文章分析了AI技术如何通过数据分析和用户行为洞察,帮助企业制定更加有效的关键词策略。AI能够实时监测市场趋势,识别用户意图,并根据这…...
Matlab中的QRBiGRU分位数回归双向门控循环单元模型:多图输出与多指标评估的时间序列区间预测
Matlab实现基于QRBiGRU分位数回归双向门控循环单元的时间序列区间预测模型: 1.Matlab实现基于QRBiGRU分位数回归双向门控循环单元的时间序列区间预测模型 2.多图输出、多指标输出(MAE、RMSE、MSE、R2),多输入单输出,含不同置信区间图、概率密…...
从51job爬虫案例出发,聊聊如何用Selenium优雅地绕过前端反爬机制
从51job爬虫案例解析Selenium反反爬高阶策略 当招聘网站的前端技术不断升级,传统爬虫手段逐渐失效时,如何让自动化工具的行为更像真实用户?这个问题困扰着许多中高级开发者。以51job为例,其动态加载、URL不变的设计让常规爬虫束手…...
OpenClaw知识库集成:Qwen3-VL:30B连接飞书文档中心
OpenClaw知识库集成:Qwen3-VL:30B连接飞书文档中心 1. 为什么需要智能文档助手 上个月整理季度技术文档时,我对着飞书里上百个分散的文档链接发愁——每次找资料都要在搜索框反复尝试关键词,遇到表格和图表更要逐页核对。直到发现OpenClaw能…...
提升钱包开发效率:用快马AI一键生成imToken风格的高复用UI组件
提升钱包开发效率:用快马AI一键生成imToken风格的高复用UI组件 开发钱包类应用时,最让人头疼的就是那些重复性的UI组件和交互逻辑。每次新项目都要从零开始写资产卡片、交易记录列表、二维码弹窗这些基础组件,不仅耗时耗力,还容易…...
Burp Suite实战进阶:用LingJing内置的burp-labs靶机打通从入门到专家22关(含解题思路)
Burp Suite实战进阶:用LingJing内置的burp-labs靶机打通从入门到专家22关(含解题思路) 在网络安全领域,Burp Suite无疑是渗透测试工程师最得力的工具之一。然而,很多学习者在掌握了基础操作后,往往会陷入&q…...
