当前位置: 首页 > news >正文

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 是一个分布式键值对存储&#xff0c;设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁&#xff0c;leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用&#xff0c;持久性数据存储和检索而准备。 以下代码实现的主要业务是&#xf…...

JavaScript元素根据父级元素宽高缩放

/*** 等比缩放* param wrap 外部容器* param container 待缩放的容器* returns {{width: number, height: number}}* 返回值&#xff1a;width:宽度, height:高度*/aspectRatio(wrap: any, container: any) {// w h / ratio, h w * ratioconst wrapW wrap.width;const wrapH…...

易趋产品升级(EasyTrack 11_V1.3) | 集成飞书、WPS、个性化设置,增强团队协作和用户体验

企业在项目管理过程中&#xff0c;经常会遇到项目信息同步不及时、沟通障碍以及管理软件使用不便捷等难题&#xff0c;导致团队协作效率低下。这种情况下&#xff0c;如果使用了多个办公软件&#xff08;如&#xff1a;钉钉、企业微信、项目管理软件等&#xff09;&#xff0c;…...

帆软FineBi V6版本经验总结

帆软FineBi V6版本经验总结 BI分析出现背景 ​ 现在是一个大数据的时代&#xff0c;每时每刻都有海量的明细数据出现。这时大数据时代用户思维是&#xff1a;1、数据的爆炸式增长&#xff0c;人们比起明细数据&#xff0c;更在意样本的整体特征、相互关系。2、基于明细的“小…...

03.MySQL的体系架构

MySQL的体系架构 一、MySQL简介二、MySQL的体系架构三、MySQL的内存结构四、MySQL的文件结构 一、MySQL简介 MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典MySQL AB公司开发&#xff0c;后被Sun公司收购&#xff0c;Sun公司被Oracle…...

随笔笔记-2023

随笔 computed 是基于他们的依赖进行缓存的&#xff0c;。如果要随时计算 new Date().now&#xff08;因为不是响应式的&#xff09;,那么需要用 computed。 如果不希望用缓存那么就用 methods 字符与字节 1 字节8 位1B8 bit;1KB 1024B,1MB1024KB1024*1024B 编码&#xff1a;…...

2023.12.31 Python 词频统计

练习&#xff1a;使用Python中的filter、map、reduce实现词频统计 样例数据&#xff1a; hello world java python java java hadoop spark spark python 需求分析&#xff1a; 1- 文件中有如上的示例数据 2- 读取文件内容。可以通过readline() 3- 将一行内容切分得到多个单…...

day12--java高级编程:网络通讯

5 Day19–网络通信(Socket通信) 说明&#xff1a; io流是跟本地的文件进行数据的传输&#xff0c;读或者写。网络通信&#xff1a;数据在网络中进行的传输。 本章专题与脉络 1. 网络编程概述 Java是 Internet 上的语言&#xff0c;它从语言级上提供了对网络应用程序的支持&…...

MongoDB聚合:$out

$out阶段将聚合管道产生的文档写入到指定的集合&#xff0c;从MongoDB4.4开始&#xff0c;支持指定数据库。$out阶段必须放在聚合管道的最后&#xff0c;支持聚合结果任意大小的数据集。 警告&#xff1a; 如果指定的集合已经存在则会被替换。 语法 用法 1&#xff1a; 定数…...

一次奇葩的spin_lock_irq / spin_unlock_irq使用不当导致的系统卡死分析

这是在调试内核block层时遇到的一例奇葩的soft lock锁死问题(内核版本centos 8.3&#xff0c;4.18.0-240)&#xff0c;现场如下&#xff1a; [ 760.247152] watchdog: BUG: soft lockup - CPU#0 stuck for 23s! [kworker/0:1:2635]……………..[ 760.247184] CPU: 0 PID: 26…...

公司创建百度百科需要哪些内容?

一个公司或是一个品牌想要让自己更有身份&#xff0c;更有知名度&#xff0c;更有含金量&#xff0c;百度百科词条是必不可少的。通过百度百科展示公司的详细信息&#xff0c;有助于增强用户对公司的信任感&#xff0c;提高企业形象。通过百度百科展示公司的发展历程、领导团队…...

qt中信号槽第五个参数

文章目录 connent函数第五个参数的作用自动连接(Qt::AutoConnection)直接连接(Qt::DirectConnection - 同步)同线程不同线程 队列连接(Qt::QueuedConnection - 异步)同一线程不同线程 锁定队列连接(Qt::BlockingQueuedConnection) connent函数第五个参数的作用 connect(const …...

模式识别与机器学习-SVM(线性支持向量机)

线性支持向量机 线性支持向量机间隔距离学习的对偶算法算法:线性可分支持向量机学习算法线性可分支持向量机例子 谨以此博客作为复习期间的记录 线性支持向量机 在以上四条线中&#xff0c;都可以作为分割平面&#xff0c;误差率也都为0。但是那个分割平面效果更好呢&#xff1…...

【并行计算】GPU,CUDA

一、CUDA层次结构 1.kernel核函数 一个CUDA程序是一个kernel核函数被GPU的多个计算单元并行执行的过程&#xff0c;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&#xff08;Simultaneous Localization and Mapping&#xff0c;即同时定位与地图构建&#xff09;是一种使机器人或自动驾驶汽车能够在未知环境中建立地图的同时定位自身位置的技术。回环检测&#xff08;Loop Closure Detectio…...

ubuntu 通过文件设置静态IP、DNS、网关

1. 确定网络接口名称 首先&#xff0c;使用 ip a 命令确定您要配置的网络接口名称。 2. 编辑 Netplan 配置文件 使用文本编辑器&#xff08;如 nano&#xff09;打开或创建 Netplan 配置文件&#xff1a; sudo nano /etc/netplan/01-netcfg.yaml3. 输入 Netplan 配置 在编…...

mapboxgl 中热力图的实现以及给热力图点增加鼠标移上 popup 效果

文章目录 概要效果预览技术思路技术细节小结 概要 本篇文章还是关于最近做到的 mapboxgl 地图展开的。 借鉴官方示例&#xff1a;https://iclient.supermap.io/examples/mapboxgl/editor.html#heatMapLayer 效果预览 技术思路 将接口数据渲染到地图中形成热力图。还需要将热…...

golang并发安全-sync.map

sync.map解决的问题 golang 原生map是存在并发读写的问题&#xff0c;在并发读写时候会抛出异常 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 参数说明&#xff1a; -DgroupId 组织Id&#xff08;项目包名&#xff09; -DartifactId 项目名称或模块…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

MySQL 主从同步异常处理

阅读原文&#xff1a;https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主&#xff0c;遇到的这个错误&#xff1a; Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一&#xff0c;通常表示&#xff…...

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...