当前位置: 首页 > 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 项目名称或模块…...

Windows APK安装器完整指南:无需安卓手机直接安装应用

Windows APK安装器完整指南&#xff1a;无需安卓手机直接安装应用 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 想要在Windows电脑上直接安装Android应用吗&#xff…...

Linux驱动开发避坑指南:手把手教你实现三种mmap内存映射(附完整代码)

Linux驱动开发实战&#xff1a;三种mmap内存映射方案深度解析与性能对比 在嵌入式系统和图形处理领域&#xff0c;直接访问内核内存的需求日益增长。想象一下这样的场景&#xff1a;你正在开发一个视频处理驱动&#xff0c;需要将摄像头采集的高清帧数据传输到用户空间进行实时…...

在Windows上运行iOS应用:ipasim模拟器完整指南与最佳实践

在Windows上运行iOS应用&#xff1a;ipasim模拟器完整指南与最佳实践 【免费下载链接】ipasim iOS emulator for Windows 项目地址: https://gitcode.com/gh_mirrors/ip/ipasim 想在Windows电脑上体验iPhone应用吗&#xff1f;厌倦了为iOS开发而购买昂贵的苹果设备&…...

基于深度学习的YOLOv8瞳孔识别+眼球识别与直径计算(代码+数据集+教程)

编写一个完整的从训练到推理YOLOv8瞳孔眼球识别与直径计算的指南&#xff0c;并包括模型转化和web界面交互式的实现&#xff0c;是一个相当庞大的项目。 1. 数据准备收集数据 对于瞳孔和眼球的检测&#xff0c;您需要收集大量的标注图像&#xff0c;这些图像应该包含不同光照条…...

Stack-on-a-budget:开发者必备的免费服务资源大全终极指南 [特殊字符]

Stack-on-a-budget&#xff1a;开发者必备的免费服务资源大全终极指南 &#x1f680; 【免费下载链接】stack-on-a-budget A collection of services with great free tiers for developers on a budget. Sponsored by Mockoon, the best mock API tool. https://mockoon.com …...

产品竞争策略方法论:构建“差异化 + 结构化 + 系统化”的竞争优势

目录 一、问题与背景 二、本文将系统讲解 三、产品竞争的本质与底层逻辑 3.1 竞争的本质 3.2 竞争的三层结构(必须理解) 3.3 IoT竞争的特殊性 四、IoT产品竞争结构模型(核心框架) 4.1 五层竞争模型(核心体系) 4.2 竞争演进路径 五、五大竞争策略模型(核心方法…...

浏览器扩展开发实战:KeepChatGPT会话保持原理与实现

1. 项目概述&#xff1a;一个浏览器扩展的诞生与使命 最近在和一些做AI应用开发的朋友交流时&#xff0c;大家普遍反映了一个痛点&#xff1a;在使用一些大型语言模型&#xff08;LLM&#xff09;的在线服务时&#xff0c;对话经常会被意外中断。这种中断可能源于网络波动、服…...

基于MCP协议与本地全文检索的电子元件文档AI查询系统

1. 项目概述&#xff1a;为LLM构建一个本地化的电子元件文档搜索引擎如果你是一名嵌入式工程师、硬件开发者&#xff0c;或者像我一样&#xff0c;经常需要和德州仪器&#xff08;TI&#xff09;、意法半导体&#xff08;ST&#xff09;、亚德诺&#xff08;ADI&#xff09;这些…...

【限时开放】DeepSeek内部调试工具集首次对外披露:含Request ID全链路追踪、模型响应热力图与异常模式识别器

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;DeepSeek API接入开发教程 DeepSeek 提供了稳定、高性能的大模型 API 接口&#xff0c;支持文本生成、对话补全与函数调用等多种能力。接入前需在官方控制台申请 API Key&#xff0c;并确保账户已开通对…...

AI 搜索重新重视来源:内容平台的新机会不是被点击,而是被正确引用

生成式搜索刚出现时&#xff0c;很多内容创作者最担心的问题是&#xff1a;如果答案直接出现在搜索页&#xff0c;用户还会不会点进原文&#xff1f;这个担心并不多余。AI Overviews、AI Mode 和各类答案引擎&#xff0c;确实改变了“搜索结果页到网页”的传统路径。但现在更值…...