手写分布式配置中心(三)增加实时刷新功能(短轮询)
要实现配置自动实时刷新,需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center
服务端改造
服务端增加一个版本号version,新增配置的时候为1,每次更新配置就加1。
@Overridepublic long insertConfigDO(ConfigDO configDO) {insertLock.lock();try {long id = 1;List<ConfigDO> configList = getAllConfig();if (!configList.isEmpty()) {id = configList.get(configList.size() - 1).getId() + 1;}configDO.setId(id);configDO.setVersion(1);Optional.of(configDO).filter(c -> c.getCreateTime() == null).ifPresent(c -> c.setCreateTime(LocalDateTime.now()));String configPathStr = standalonePath + "/config";Files.createDirectories(Paths.get(configPathStr));Path path = Paths.get(configPathStr + "/" + id + ".conf");Files.write(path, JSON.toJSONString(configDO).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW);return id;} catch (Exception e) {throw new RuntimeException(e);} finally {insertLock.unlock();}}@Overridepublic void updateConfig(ConfigDO configDO) {ConfigDO dbConfigDO = getConfig(configDO.getId());Optional.ofNullable(dbConfigDO).map(c -> {c.setName(configDO.getName());c.setVersion(c.getVersion() + 1);c.setUpdateTime(LocalDateTime.now());c.setUpdateUid(configDO.getUpdateUid());c.setConfigData(configDO.getConfigData());return c;}).ifPresent(this::updateConfigDO);}
再增加一个接口判断verion是否发生变化
@GetMapping("/change/get")public Result<List<ConfigVO>> getChangeConfig(@RequestBody Map<Long, Integer> configIdMap) {if (configIdMap == null || configIdMap.isEmpty()) {return Result.fail("配置参数错误");}Result<List<ConfigBO>> result = configService.getAllValidConfig();if (result.failed()) {return Result.resultToFail(result);}return Result.success(result.getData().stream().filter(c -> configIdMap.containsKey(c.getId())).filter(c -> c.getVersion() > configIdMap.get(c.getId())).map(this::configBO2ConfigVO).collect(Collectors.toList()));}
客户端改造
客户端对获取到的配置,做了一下改造,把json转换成了property格式,即user.name=xxx。并且存储到了一个以配置ID为key,配置对象ConfigBO为value的map configMap里。具体的结构如下
@Data
public class ConfigBO {/*** 配置id*/private long id;/*** 配置版本号*/private int version;/*** 配置项列表*/private List<ConfigDataBO> configDataList;
}
@Data
public class ConfigDataBO {/*** 配置key*/private String key;/*** 配置值*/private String value;/*** 自动刷新的bean字段列表*/List<RefreshFieldBO> refreshFieldList;public void addRefreshField(RefreshFieldBO refreshFieldBO) {Optional.ofNullable(refreshFieldList).orElseGet(() -> refreshFieldList = new ArrayList<>()).add(refreshFieldBO);}
}
@Data
@AllArgsConstructor
public class RefreshFieldBO {/*** 对象实例*/private Object bean;/*** 字段*/private Field field;
}
获取配置和之前一样,只不过调用的位置改成了ConfigCenterClient中,将配置转换成<配置key,配置值>的map提供给外部程序调用
public ConfigCenterClient(String url) {this.url = url;//将配置中心的配置转换成property格式,即user.name=xxxList<ConfigVO> configList = getAllValidConfig();this.configMap = Optional.ofNullable(configList).map(list -> list.stream().map(configVO -> {Map<String, Object> result = new HashMap<>();DataTransUtil.buildFlattenedMap(result, configVO.getConfigData(), "");ConfigBO configBO = new ConfigBO();configBO.setId(configVO.getId());configBO.setVersion(configVO.getVersion());configBO.setConfigDataList(result.entrySet().stream().map(e -> {ConfigDataBO configDataBO = new ConfigDataBO();configDataBO.setKey(e.getKey());configDataBO.setValue(e.getValue().toString());return configDataBO;}).collect(Collectors.toList()));return configBO;}).collect(Collectors.toMap(ConfigBO::getId, Function.identity(), (k1, k2) -> k1))).orElseGet(HashMap::new);}
public Map<String, String> getConfigProperty() {return configMap.values().stream().map(ConfigBO::getConfigDataList).filter(Objects::nonNull).flatMap(List::stream).collect(Collectors.toMap(ConfigDataBO::getKey, ConfigDataBO::getValue, (k1, k2) -> k1));}
使用方式
public class ClientTest {private String userName;private String userAge;private List<Object> education;public ClientTest() {ConfigCenterClient configCenterClient = new ConfigCenterClient("http://localhost:8088");Map<String, String> configProperty = configCenterClient.getConfigProperty();this.userName = configProperty.get("user.name");this.userAge = configProperty.get("user.age");this.education = new ArrayList<>();int i = 0;while (configProperty.containsKey("user.education[" + i + "]")) {education.add(configProperty.get("user.education[" + (i++) + "]"));}}public String toString() {return "姓名:" + userName + ",年龄:" + userAge + ",教育经历:" + education;}public static void main(String[] args) {ClientTest clientTest = new ClientTest();System.out.println(clientTest);}
}
好了改造完毕,下面开始进入正题
短轮询
短轮询就是客户端不断的去请求/config/change/get接口判断配置是否发生了变化,如果发生了变化返回给客户端,客户端拿到新配置后通过反射修改对象的成员变量
首先将需要实时刷新的配置加入到自动刷新的bean字段列表中,然后启动一个定时任务1秒钟访问一次/config/change/get接口,如果有变化,更新本地配置map,并刷新对象中的配置成员变量
public void addRefreshField(String key, RefreshFieldBO refreshFieldBO) {configMap.values().stream().map(ConfigBO::getConfigDataList).filter(Objects::nonNull).flatMap(List::stream).filter(configDataBO -> configDataBO.getKey().equals(key)).findFirst().ifPresent(configDataBO -> configDataBO.addRefreshField(refreshFieldBO));}
public void startShortPolling() {Thread thread = new Thread(() -> {while (!Thread.interrupted()) {try {Thread.sleep(1000);Map<Long, List<ConfigDataBO>> refreshConfigMap = new HashMap<>();configMap.values().forEach(configBO -> {Optional.ofNullable(configBO.getConfigDataList()).ifPresent(cdList -> cdList.stream().filter(cd -> cd.getRefreshFieldList() != null && !cd.getRefreshFieldList().isEmpty()).forEach(refreshConfigMap.computeIfAbsent(configBO.getId(), k1 -> new ArrayList<>())::add));});if (refreshConfigMap.isEmpty()) {return;}Map<String, Integer> configIdMap = refreshConfigMap.keySet().stream().collect(Collectors.toMap(String::valueOf, configId -> configMap.get(configId).getVersion()));HttpRespBO httpRespBO = HttpUtil.httpPostJson(url + "/config/change/get", JSON.toJSONString(configIdMap));List<ConfigVO> configList = httpResp2ConfigVOList(httpRespBO);if (configList.isEmpty()) {continue;}configList.forEach(configVO -> {Map<String, Object> result = new HashMap<>();DataTransUtil.buildFlattenedMap(result, configVO.getConfigData(), "");ConfigBO configBO = this.configMap.get(configVO.getId());configBO.setVersion(configVO.getVersion());List<ConfigDataBO> configDataList = configBO.getConfigDataList();Map<String, ConfigDataBO> configDataMap = configDataList.stream().collect(Collectors.toMap(ConfigDataBO::getKey, Function.identity()));result.forEach((key, value) -> {ConfigDataBO configDataBO = configDataMap.get(key);if (configDataBO == null) {configDataList.add(new ConfigDataBO(key, value.toString()));} else {configDataBO.setValue(value.toString());List<RefreshFieldBO> refreshFieldList = configDataBO.getRefreshFieldList();if (refreshFieldList == null) {refreshFieldList = new ArrayList<>();configDataBO.setRefreshFieldList(refreshFieldList);}refreshFieldList.forEach(refreshFieldBO -> {try {Field field = refreshFieldBO.getField();field.setAccessible(true);field.set(refreshFieldBO.getBean(), value.toString());} catch (Exception e) {log.error("startShortPolling set Field error", e);}});}});});} catch (Exception e) {log.error("startShortPolling error", e);}}});thread.setName("startShortPolling");thread.setDaemon(true);thread.start();}
public class ClientTest {private String userName;private String userAge;private List<Object> education;public ClientTest() throws NoSuchFieldException {ConfigCenterClient configCenterClient = new ConfigCenterClient("http://localhost:8088");Map<String, String> configProperty = configCenterClient.getConfigProperty();this.userName = configProperty.get("user.name");this.userAge = configProperty.get("user.age");this.education = new ArrayList<>();int i = 0;while (configProperty.containsKey("user.education[" + i + "]")) {education.add(configProperty.get("user.education[" + (i++) + "]"));}configCenterClient.addRefreshField("user.name", new RefreshFieldBO(this, ClientTest.class.getDeclaredField("userName")));configCenterClient.startShortPolling();}public String toString() {return "姓名:" + userName + ",年龄:" + userAge + ",教育经历:" + education;}public static void main(String[] args) throws NoSuchFieldException, InterruptedException {ClientTest clientTest = new ClientTest();while (!Thread.interrupted()) {System.out.println(clientTest);Thread.sleep(1000);}}
}
效果
修改配置
相关文章:

手写分布式配置中心(三)增加实时刷新功能(短轮询)
要实现配置自动实时刷新,需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center 服务端改造 服务端增加一个版本号version,新增配置的时候为1,每次更新配置就加1。 Overridepublic long insertConfigDO(…...

【RabbitMQ】WorkQueue
📝个人主页:五敷有你 🔥系列专栏:MQ ⛺️稳中求进,晒太阳 Work Queues Work queues任务模型,简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息 当消息处理比较耗时的时候&…...
国内免费好用 Chat GPT推荐
无论您是寻找技术洞见还是灵感激发,此网站是您的绝佳去处。探索着名作家的精彩观点和创意解决方案,它不仅是知识的源泉,更是思维的驱动力。在这里,您将发现无尽的学习资源和启发,助您不断前行这是一款基于OpenAi开发的…...

基于springboot实现在线考试系统项目【项目源码+论文说明】
基于springboot实现在线考试系统演示 摘要 时代在变化,科技技术以无法预测的速度在达到新的高度,并且被应用于社会生活的各个领域,随着生活的加快,也使很多潜在的点逐渐突显出来,社会对于人才的要总是非常迫切的&…...

golang中go build 后读取配置文件
golang打包后读取配置文件 在用go写代码的时候,为了好用经常使用go build 打包,如果我们用到了配置文件,就总是导致不能找到文件所在位置了出现bug,所以以下代码就解决了这个问题。 核心代码: file, err : exec.Look…...
为raspberrypi编译bpftrace调试工具
基于eBPF的嵌入式应用调试 笔者之前写过几篇有关于使用eBPF调试Linux内核和应用的博客,其中提到,在嵌入式设备上使用BCC或bpftrace是不可行的;主要原因在于嵌入式设备的资源有限,而这两个调试工具依赖python/clang/llvm等库&…...

分段线性化问题探析
目录 1 使用0-1变量将分段函数转换为线性约束 2 连续函数采用分段线性化示例 3 matlab程序测试 4 matlab测试结果说明 5 分段线性化应用 1 使用0-1变量将分段函数转换为线性约束 2 连续函数采用分段线性化示例 3 matlab程序测试 clc;clear all; gn10;tn1; x_pfsdpvar(1, t…...
从零学算法2917
2917.给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数: 只有在 nums 中,至少存在 k 个元素的第 i 位值为 1 ,那么 K-or 中的第 i 位的值才是 1 。 返回 nums 的 K-or 值。 注意 …...

[HackMyVM] 靶场 Wave
kali:192.168.56.104 主机发现 arp-scan -l # arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:d2:e0:49, IPv4: 192.168.56.104 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.1 0a:00:27:00:00:05 (Un…...

云渲染平台都开始涨价了?2024年性价比高的云渲染平台推荐
最近部分云渲染平台开始涨价,不论是通过调整机器性能,还是直接提价,都会对成本产生影响。这对已经习惯了平台价格的用户来说,并不是一件好事。这里举一些例子: 比如平台A,原“首小时渲染0.66元模式”已经下…...
搜索-BFS Meteor Shower S(流星雨)
Meteor Shower S(流星雨) 题目连接 题目描述 贝茜听说一场特别的流星雨即将到来:这些流星会撞向地球,并摧毁它们所撞击的任何东西。她为自己的安全感到焦虑,发誓要找到一个安全的地方(一个永远不会被流星…...
RabbitMQ实战:Springboot集成RabbitMQ并验证五种消息模型
这目录 一、添加依赖二、配置文件中添加RabbitMQ访问配置三、消息生产者代码四、消息消费者代码五、验证参考资料 一、添加依赖 <!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>s…...

配置与管理防火墙
配置与管理防火墙 1,概念:设置在不同网络或网络安全域之间的一系列部件的组合。 2,功能:保护内网中易手攻击的服务;控制内外网之间网络系统的访问;隐藏内网的IP地址及结构的细节,提高网络保护…...

【SpringBoot】-- 实现本地文件/图片上传到服务器生成url地址
在java项目中你可能会有以下需求:用户上传本地图片,然后展示在网页上。本篇文章将使用阿里云oss实现上传图片到oss,oss生成url。 一、准备工作 首先进入阿里云,按如下操作 进入创建页面,修改读写权限为公共读 然后进…...

计算机基础专升本笔记十四-计算机网络基础(一)
计算机基础专升本笔记十四-计算机网络基础(一) 一、计算机网络的发展历程 第一代计算机网络(数据通信) 以数据通信为主的第一代计算机网络。主要是指美国军方用于防控系统的一种联机系统。它只是计算机网络的雏形。 第二代计算…...
【华为OD机试】转盘寿司【C卷|100分】
【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 寿司店周年庆,正在举办优惠活动回馈新老客户。 寿司转盘上总共有 n 盘寿司,prices[i] 是第 i 盘寿司的价格, 如果客户选择了第 i 盘寿司,寿司店免费赠送客户距离第 i 盘寿司最近的下一…...
使用Node JS获取WI-FI密码
演示效果 全局安装wifi-password-cli依赖 shell 复制代码 npm install wifi-password-cli -g # or npx wifi-password-cli 使用 shell 复制代码 $ wifi-password [network-name] $ wifi-password 12345678 $ wifi-password 办公室wifi a1234b2345 觉得Node.js很神奇是…...
先缓存第二集抖音接入 ,最近加班猛,就分享简单的知识,如何使用:关于使用replace的用法正则表达式
1、需求:比如在cocos creator策划让你制作一个预制体,标题要读取配置,然后中间显示的内容要滚动的,要做成一个通用的,然后给到的配置表是这样子的: 配置表:假设字段是这样子的 content "内容标题&…...

企微hook源码
企微hook源码已经在QQ群内开源。速度进群下载,避免和谐。 QQ群:649480745...

vsphere虚拟机迁移是灰色如何解决
vsphere虚拟机迁移是灰色如何解决 问题描述: 在vsphere中,迁移虚拟机时迁移按钮是灰色,无法迁移,关机之后也无法迁移 虚拟机按钮为灰色 找到虚拟机存储对应的位置,查询是否有.vmx虚拟机文件 查询中发现有.vmx文件存…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...

算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...

论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...