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()));}}

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

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

相关文章

复试 || 就业day01(2023.12.27)算法篇

文章目录 前言两数之和存在重复元素 II好数对的数目总持续时间可被 60 整除的歌曲 前言 &#x1f4ab;你好&#xff0c;我是辰chen&#xff0c;本文旨在准备考研复试或就业 &#x1f4ab;文章题目大多来自于 leetcode&#xff0c;当然也可能来自洛谷或其他刷题平台 &#x1f4a…

2023-12-27 Python PC获取鼠标位置,移动鼠标到相应的位置 定时自动模拟鼠标点击,用于简单测试app用

一、核心源码如下&#xff1a; import pyautogui import timepyautogui.moveTo(600, 800) for i in range(20):time.sleep(0.1)x, y pyautogui.position()print("mouse position:", x, y)pyautogui.click()二、定时自动模拟鼠标点击&#xff0c;模拟键盘按键 impo…

C语言-第十七周课堂总结-数组

找出矩阵中最大值所在的位置 程序解析-求矩阵的最大值 源程序段 二维数组 多维数组的空间想象 一维数组&#xff1a;一列长表或一个向量二维数组&#xff1a;一个表格或一个平面矩阵三维数组&#xff1a;三位空间的一个方阵多维数组&#xff1a;多维空间的一个数据矩阵 …

2019年全国学校POI数据

2019年全国学校POI数据 POI&#xff08;一般作为Point of Interest的缩写&#xff0c;也有Point of Information的说法&#xff09;&#xff0c;通常称作兴趣点&#xff0c;泛指互联网电子地图中的点类数据&#xff0c;基本包含名称、地址、坐标、类别四个属性&#xff1b;在GI…

如何利用树莓派与Nginx结合cpolar内网穿透工具实现公网访问内网web网站

文章目录 1. Nginx安装2. 安装cpolar3.配置域名访问Nginx4. 固定域名访问5. 配置静态站点 安装 Nginx&#xff08;发音为“engine-x”&#xff09;可以将您的树莓派变成一个强大的 Web 服务器&#xff0c;可以用于托管网站或 Web 应用程序。相比其他 Web 服务器&#xff0c;Ngi…

Flask 密码重设系统

Flask 密码重设系统【源码来自编程浪子Flask点餐小程序】 web/templates/user/reset_pwd.html {% extends "common/layout_main.html" %} {% block content %} {% include "common/tab_user.html" %} <div class"row m-t user_reset_pwd_wrap&q…

QML —— ProgressBar示例(附完整源码)

示例 - 效果 实例 - 源码 import QtQuick 2.12 import QtQuick.Window 2.12import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5Window {id: rootIdvisible: truewidth: 640height: 480title: qsTr("Hello World")Column{spacing: 40anchors.centerIn: parent…

AE学习笔记十--------------3Dmax协作-三维场景合成

3Dmax协作-三维场景合成 步骤&#xff1a; 1、导入资源&#xff08;.rpf文件---多像素文件&#xff09;阿尔法通道 猜测 2、.rpf为合层-----&#xff08;右键&#xff09;关键帧辅助------rpf摄像机导入 &#xff08;目的&#xff1a;导入其他素材&#xff0c;可以根据rpf摄…

车路协同中 CUDA 鱼眼相机矫正、检测、追踪

在车路协同中,鱼眼一般用来补充杆件下方的盲区,需要实现目标检测、追踪、定位。在目标追踪任务中,通常的球机或者枪机方案,无法避免人群遮挡的问题,从而导致较高的ID Swich,造成追踪不稳定。但是鱼眼相机的顶视角安装方式,天然缓解了遮挡的问题,从而实现杆件下方的盲区…

携手共进 探索生命|清华大学创融同学会走进生命系 共话细胞科技新未来

携手共进 探索生命&#xff5c;清华大学创融同学会走进生命系 共话细胞科技新未来 探索细胞产业新高度&#xff0c;赋予生命健康更多保障&#xff01;日前&#xff0c;清华大学创融同学会一行莅临全生命周期健康管理中心——生命系参观交流。生命系领导以及全体员工对来访贵宾…

mysql-5.6.16的内存泄漏问题

一、背景 有一台物理机上一个版本为5.6.16的从库出现了内存的增高&#xff0c;观测其日志可以发现&#xff0c;这台数据库已经oom很多次了&#xff0c;并且stop slave的时候会卡住非常长的时间才能停止 二、根本原因 上述的现象可以看到是一个明显的内存泄漏现象&#xff0c…

单例模式的双重检查锁定是什么?

单例模式的双重检查锁定是什么&#xff1f; 单例模式是一种常见的设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点。双重检查锁定&#xff08;Double-Checked Locking&#xff09;是一种在单例模式中使用的性能优化技术。 在传统的单例模式…

【10】ES6:Promise 对象

一、同步和异步 1、JS 是单线程语言 JavaScript 是一门单线程的语言&#xff0c;因此同一个时间只能做一件事情&#xff0c;这意味着所有任务都需要排队&#xff0c;前一个任务执行完&#xff0c;才会执行下一个任务。但是&#xff0c;如果前一个任务的执行时间很长&#xff…

Python3 基本数据类型

Python 中的变量不需要声明。每个变量在使用前都必须赋值&#xff0c;变量赋值以后该变量才会被创建。 在 Python 中&#xff0c;变量就是变量&#xff0c;它没有类型&#xff0c;我们所说的"类型"是变量所指的内存中对象的类型。 等号&#xff08;&#xff09;用来…

关于时间与空间复杂度的学习

关于时间与空间复杂度的学习 算法时间复杂度定义标准算法度量单位渐近记号1、Θ&#xff08;big-theta&#xff09;2、O&#xff08;big-oh&#xff09;3、Ω&#xff08;big-omege&#xff09; 推导时间复杂度步骤与法则步骤法则 示例1.常数阶2、线性阶3、对数阶4、平方阶5、立…

『精』CSS 小技巧之BEM规范

『精』CSS 小技巧之BEM规范 文章目录 『精』CSS 小技巧之BEM规范一、什么是BEM&#xff1f;二、BEM要怎么用&#xff1f;三、不用BEM会少个胳膊吗&#xff1f;&#x1f48a;四、Sass与BEM的结合&#x1f388;五、块与修饰符应放在一块&#x1f47f;参考资料&#x1f498;推荐博…

100000000!文心一言披露最新用户规模

“文心一言用户规模突破1亿。” 12月28日&#xff0c;百度首席技术官、深度学习技术及应用国家工程研究中心主任王海峰在第十届WAVE SUMMIT深度学习开发者大会上宣布。会上&#xff0c;王海峰以《文心加飞桨&#xff0c;翩然赴星河》为题作了主旨演讲&#xff0c;分享了飞桨和文…

Page17~19 samp2_1, 项目各个文件的含义,增加一个关闭按钮

创建一个Widget Application项目samp2_1,选择QWidget作为窗体基类&#xff0c;并选中Generate form复选框 创建好之后&#xff0c;Shadow build的对勾去掉 创建好的项目文件目录树 这个项目包含以下一些文件&#xff1a; 项目组织文件samp2_1.pro, 存储项目设置的文件 主程序…

VStudio2022导出Qt项目在Linux的Qtcreator中运行修复错误记录

公司项目中的代码在VStudio2022中编写&#xff0c;交给我需要移植Linux的Qtcreator中&#xff0c;记录一下移植过程中的遇到的坑&#xff0c;按照错误顺序由高到低记录一下&#xff0c;边尝试边解决边记录&#xff0c;写作方面没有逻辑&#xff0c;每个人项目环境不一样&#x…

淘宝API与大数据技术在电商行业的应用研究

一、引言 随着电商行业的迅猛发展&#xff0c;数据已经成为驱动业务增长和创新的关键因素。淘宝作为中国电商市场的巨头&#xff0c;其API&#xff08;应用程序接口&#xff09;为电商行业提供了丰富的数据资源。结合大数据技术&#xff0c;这些数据可以被深度挖掘和利用&…