手写分布式配置中心(二)实现分布式配置中心的简单版本
这一篇文章比较简单,就是一个增删改查的服务端和一个获取配置的客户端,旨在搭建一个简单的配置中心架构,代码在 https://gitee.com/summer-cat001/config-center
服务端
服务端选择用springboot 2.7.14搭建,设计了4个接口/config/insert、/config/update、/config/delete、/config/get。
Controller层
Controller层做了请求参数的校验,和对服务层的转发
@RestController
@RequestMapping("/config")
public class ConfigController {@Autowiredprivate ConfigService configService;@PostMapping("/insert")public Result<Void> insertConfig(@RequestBody ConfigVO configVO) {Result<ConfigBO> result = checkOpConfig(configVO);if (result.failed()) {return Result.resultToFail(result);}return configService.insertConfig(result.getData());}@PostMapping("/update")public Result<Void> updateConfig(@RequestBody ConfigVO configVO) {Result<ConfigBO> result = checkOpConfig(configVO);if (result.failed()) {return Result.resultToFail(result);}ConfigBO configBO = result.getData();long id = configVO.getId();if (id <= 0) {return Result.fail("配置id错误");}configBO.setId(id);return configService.updateConfig(configBO);}@PostMapping("/delete")public Result<Void> delConfig(@RequestBody ConfigVO configVO) {long id = configVO.getId();if (id <= 0) {return Result.fail("配置id错误");}return configService.delConfig(id, 0L);}@GetMapping("/get")public Result<List<ConfigVO>> getAllValidConfig() {Result<List<ConfigBO>> result = configService.getAllValidConfig();if (result.failed()) {return Result.resultToFail(result);}return Result.success(result.getData().stream().map(configBO -> {ConfigVO configVO = new ConfigVO();configVO.setId(configBO.getId());configVO.setName(configBO.getName());configVO.setConfigData(configBO.getConfigData());configVO.setCreateTime(DateUtil.date2str1(configBO.getCreateTime()));return configVO;}).collect(Collectors.toList()));}private Result<ConfigBO> checkOpConfig(ConfigVO configVO) {String name = configVO.getName();if (name == null || (name = name.trim()).length() == 0) {return Result.fail("配置名不能为空");}JSONObject configData = configVO.getConfigData();if (configData == null) {return Result.fail("配置内容不能为空");}ConfigBO configBO = new ConfigBO();configBO.setName(name);configBO.setConfigData(configData);return Result.success(configBO);}
}
Service层
Service层做了数据的转换和对dao层的调用,对于这个配置中心数据的存储,我做了两个模式,1是单机模式,2是集群模式。简单的来说就是一个存在数据库中,一个存在本地。根据配置文件中的config.center.mode来指定使用哪种模式
@Service
public class ConfigServiceImpl implements ConfigService {private ConfigDAO configDAO;@Autowiredprivate LocalConfigDAO localConfigDAO;@Value("${config.center.mode:0}")private int configCenterMode;@PostConstructpublic void init() {ConfigCenterModeEnum configCenterModeEnum = ConfigCenterModeEnum.getEnum(configCenterMode);if (configCenterModeEnum == null) {throw new IllegalArgumentException("配置config.center.mode错误");}if (configCenterModeEnum == ConfigCenterModeEnum.STANDALONE) {this.configDAO = localConfigDAO;}}@Overridepublic Result<Void> insertConfig(ConfigBO configBO) {List<ConfigDO> configList = configDAO.getAllValidConfig();if (configList.stream().anyMatch(c -> c.getName().equals(configBO.getName()))) {return Result.fail("配置名重复");}ConfigDO configDO = new ConfigDO();configDO.setName(configBO.getName());configDO.setConfigData(configBO.getConfigData().toJSONString());configDAO.insertConfigDO(configDO);return Result.success(null);}@Overridepublic Result<Void> updateConfig(ConfigBO configBO) {ConfigDO configDO = new ConfigDO();configDO.setId(configBO.getId());configDO.setName(configBO.getName());configDO.setConfigData(configBO.getConfigData().toJSONString());configDAO.updateConfig(configDO);return Result.success(null);}@Overridepublic Result<Void> delConfig(long id, long updateUid) {configDAO.delConfig(id, updateUid);return Result.success(null);}@Overridepublic Result<List<ConfigBO>> getAllValidConfig() {List<ConfigDO> configList = configDAO.getAllValidConfig();return Result.success(configList.stream().map(configDO -> {ConfigBO configBO = new ConfigBO();configBO.setId(configDO.getId());configBO.setName(configDO.getName());configBO.setCreateTime(configDO.getCreateTime());configBO.setConfigData(JSON.parseObject(configDO.getConfigData()));return configBO;}).collect(Collectors.toList()));}
}
DAO层
DAO层提供了一个接口com.config.center.dao.ConfigDAO。单机和集群模式分别实现这个接口,例如单机模式是com.config.center.dao.impl.LocalConfigDAO实现类(集群模式就是访问数据库,大家估计都用吐了,这个就不多介绍了)。
单机模式就是将配置文件存储到本地的一个路径中,这个路径根据配置文件的config.center.standalone.path配置来指定,保存的是以配置id为文件名.conf为后缀的文件。其中id是从1开始自增,增加配置接口用了锁,所以id不会重复
@Slf4j
@Repository
public class LocalConfigDAO implements ConfigDAO {private final Lock insertLock = new ReentrantLock();@Value("${config.center.standalone.path}")private String standalonePath;@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);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.setUpdateTime(LocalDateTime.now());c.setUpdateUid(configDO.getUpdateUid());c.setConfigData(configDO.getConfigData());return c;}).ifPresent(this::updateConfigDO);}@Overridepublic void delConfig(long id, long updateUid) {ConfigDO dbConfigDO = getConfig(id);Optional.ofNullable(dbConfigDO).map(c -> {c.setDeleted(true);c.setUpdateTime(LocalDateTime.now());c.setUpdateUid(updateUid);return c;}).ifPresent(this::updateConfigDO);}@Overridepublic ConfigDO getConfig(long id) {List<ConfigDO> configList = getAllConfig();return configList.stream().filter(c -> c.getId() == id).findFirst().orElse(null);}@Overridepublic List<ConfigDO> getAllValidConfig() {return getAllConfig().stream().filter(c -> !c.isDeleted()).collect(Collectors.toList());}@Overridepublic List<ConfigDO> getAllConfig() {File[] files;File folder = new File(standalonePath + "/config");if (!folder.exists() || (files = folder.listFiles()) == null) {return new ArrayList<>();}return Arrays.stream(files).map(File::getAbsolutePath).filter(p -> p.endsWith(".conf")).map(this::buildConfigDO).filter(Objects::nonNull).sorted(Comparator.comparing(ConfigDO::getId)).collect(Collectors.toList());}private synchronized ConfigDO buildConfigDO(String path) {try {byte[] bytes = Files.readAllBytes(Paths.get(path));String json = new String(bytes, StandardCharsets.UTF_8);return JSON.parseObject(json, ConfigDO.class);} catch (Exception e) {log.error("buildConfigDO error,path:{}", path, e);return null;}}private synchronized void updateConfigDO(ConfigDO configDO) {Path path = Paths.get(standalonePath + "/config/" + configDO.getId() + ".conf");if (Files.exists(path)) {try {Files.write(path, JSON.toJSONString(configDO).getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE);} catch (IOException e) {log.error("updateConfigDO error configDO:{}", configDO, e);}}}
}
效果
到这里就已经完成了服务端的构建了,简单吧,下面看看效果
新增配置



获取所有有效配置

修改配置


删除配置


客户端
客户端就更简单了,就是在启动时通过http调用上面的/config/get接口获取配置,并且赋值给对象的成员变量,之后直接使用这个成员变量即可
public class ConfigCenterClient {/*** 服务端地址*/private String url;public List<ConfigVO> getAllValidConfig() {HttpRespBO httpRespBO = HttpUtil.httpGet(url + "/config/get");if (!httpRespBO.success()) {throw new IllegalArgumentException("获取配置失败:code:" + httpRespBO.getCode() + ",msg:" + httpRespBO.getMessage());}if (httpRespBO.getBody() == null) {throw new IllegalArgumentException("获取配置失败 body is null:code:" + httpRespBO.getCode() + ",msg:" + httpRespBO.getMessage());}Result<?> result = JSON.parseObject(new String(httpRespBO.getBody(), StandardCharsets.UTF_8), Result.class);if (result.failed()) {throw new IllegalArgumentException("获取配置失败 result:" + result);}return JSON.parseArray(JSON.toJSONString(result.getData()), ConfigVO.class);}public void setUrl(String url) {this.url = url;}}
public class ClientTest {private String userName;private String userAge;private List<Object> education;public ClientTest() {ConfigCenterClient configCenterClient = new ConfigCenterClient();configCenterClient.setUrl("http://localhost:8088");List<ConfigVO> configList = configCenterClient.getAllValidConfig();configList.stream().map(ConfigVO::getConfigData).map(c -> c.getJSONObject("user")).findFirst().ifPresent(user -> {this.userName = user.getString("name");this.userAge = user.getString("age");this.education = user.getJSONArray("education");});}public String toString() {return "姓名:" + userName + ",年龄:" + userAge + ",教育经历:" + education;}public static void main(String[] args) {ClientTest clientTest = new ClientTest();System.out.println(clientTest);}
}

这样整个配置中心的简单版本就完成了,不过这样只是在new对象的时候设置了配置的值,但是如果配置中心的配置发生变化后,客户端是无法感知的,为了解决这个问题需要加入配置自动刷新功能,这个我们在下一篇文章中介绍。
相关文章:
手写分布式配置中心(二)实现分布式配置中心的简单版本
这一篇文章比较简单,就是一个增删改查的服务端和一个获取配置的客户端,旨在搭建一个简单的配置中心架构,代码在 https://gitee.com/summer-cat001/config-center 服务端 服务端选择用springboot 2.7.14搭建,设计了4个接口/confi…...
跨境知识分享:什么是动态IP?和静态IP有什么区别?
对于我们跨境人来说,清楚地了解IP地址、代理IP等这些基础知识,并学会正确地使用IP地址对于保障店铺的安全性和稳定性至关重要,尤其是理解动态IP和静态IP之间的区别,以及如何利用这些知识来防止账号关联,对于每个电商卖…...
liunx安装jdk、redis、nginx
jdk安装 下载jdk,解压。 sudo tar -zxvf /usr/local/jdk-8u321-linux-x64.tar.gz -C /usr/local/ 在/etc/profile文件中的,我们只需要编辑一下,在文件的最后加上java变量的有关配置(其他内容不要动)。 export JAVA_HOME/usr/l…...
【C++】STL学习之旅——初识STL,认识string类
string类 1 STL 简介2 STL怎么学习3 STL缺陷4 string4.1 初识 string4.2 初步使用构造函数成员函数 5 小试牛刀Thanks♪(・ω・)ノ谢谢阅读!!!下一篇文章见!!! 1 STL 简介 …...
Java学习笔记002——类的修饰符
在Java语言中,类的访问修饰符决定了其它类能够访问该类的方式。类有如下4种访问修饰符,在创建类时用于类的声明: 1、public: 当一个类被声明为public时,它可以从任何其他类中被访问,无论这些类位于哪个包中。通常&am…...
华为交换机常见命令总结
文章目录 查看MAC地址清除MAC地址修改MAC地址的老化时间查询STP信息查询接口与vlan的信息 查看MAC地址 查看所有MAC地址表项 display mac-address 查看静态MAC地址表项 display mac-address static //会把动态的过滤掉 查看动态MAC地址表项 display mac-address dynamic //会…...
Android 签名机制
V1是内部文件单个签 但是增加apk文件目录下面随意增加文件并不会有影响,它只关心meta-info文件 mf汇总清单的各个文件sha256 V2 整个APK文件,按文件进行hash 那么便不能随便在这里面增加文件了,增加了签名分块(不然签名信息存哪里)这里涉及一个文件概念 …...
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Scroll容器组件
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Scroll容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Scroll容器组件 可滚动的容器组件,当子组件的布局尺寸超过父组件…...
FreeRTOS操作系统学习——FreeRTOS工程创建
FreeROTS工程创建 详细步骤 如无特殊情况,大部人都要配置为外部高速时钟 另外,本实验使用了FreeRTOS,FreeRTOS的时基使用的是Systick,而 STM32CubeMX中默认的HAL库时基也是Systick,为了避免可能的冲突,最…...
6. 使用 Spring Boot进行开发(Developing with Spring Boot)
6. 使用 Spring Boot进行开发(Developing with Spring Boot) 本节详细介绍了如何使用Spring Boot。它涵盖考虑构建系统、自动配置以及如何运行应用程序等主题。我们还介绍一些 Spring Boot 最新做法。虽然 Spring Boot 没有什么特别之处(它只…...
IP地址工具,判断IP是否在指定范围内(支持ipv6)
常用方法,判断一个ip是否在指定的ip范围内,范围可能包括起始ip范围或者掩码形式,无其它依赖, package com.yk.ip;import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import jav…...
Redis 之六:Redis 的哨兵模式(Sentinel)
Redis 哨兵(Sentinel)模式是一种高可用性解决方案,用于监控和自动故障转移的集群系统。 在 Redis Sentinel 架构中,哨兵是一组运行在特殊模式下的 Redis 进程,它们可以监控一个或多个主从复制结构中的 Redis 主服务器以…...
总线要点笔记
1. AXI/AHB/APB差异 AMBA (Advanced Microcontroller Bus Architecture) 高级处理器总线架构 AHB (Advanced High-performance Bus) 高级高性能总线 ASB (Advanced System Bus) 高级系统总线 APB (Advanced Peripheral Bus) 高级外围总线 AXI (Advanced eXtensible Interface) …...
RK3566 linux iperf网络测试
一、开发环境 系统:buildroot; 在Linux目标板和Windows PC上运行iperf进行测试; 二、调试 1、查询目标板上的iperf 使用终端助手连接目标板,然后输入命令查询iperf的版本: rootrk3566-buildroot:~# iperf -v iperf version …...
【无标题】计算机主要应用于哪些领域
科学计算(或称为数值计算)、数据处理(信息管理)、辅助工程、生产自动化、人工智能。1、科学计算(或称为数值计算):早期的计算机主要用于科学计算。目前,科学计算仍然是计算机应用的一…...
力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)
目录 🚩了解题意 🚩算法分析 第一种方法:双指针 🚩代码实现一 第二种方法:三指针 🚩代码实现二 🚩了解题意 本题将整数0,1,2代表红白篮,nums中的整数并…...
基于Java SSM springboot+VUE+redis实现的前后端分类版网上商城项目
基于Java SSM springbootVUEredis实现的前后端分类版网上商城项目 博主介绍:多年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐…...
01——什么是人工智能
学习形式分类 1) 有监督学习 有监督学习(supervised learning),需要你事先需要准备好要输入数据(训练样本)与真实的输出结果(参考答案),然后通过计算机的学习得到一个预测模型,再用…...
AI国漫女神这样画!傻瓜式的云端SD(stable diffusion)部署教程 - 白嫖4090、无需代码、一键启动
一、前言:为什么要云端部署stable diffusion? 前段时间一直在用midjourney进行Ai绘画,但是发现Midjourney绘画的限制有点多很死板,很多图都不能画,懂得都懂。想使用Midjourney可以看这个:( 【推荐】Midjo…...
消息队列、共享内存、信号灯
IPC(Inter-Process Communication,进程间通信) 常用的 IPC 对象包括管道(pipe)、消息队列(message queue)、信号量(semaphore)和共享内存(shared memory&…...
3步构建专业级无人机应用:DJI Android SDK V5实战指南
3步构建专业级无人机应用:DJI Android SDK V5实战指南 【免费下载链接】Mobile-SDK-Android-V5 MSDK V5 Sample 项目地址: https://gitcode.com/gh_mirrors/mo/Mobile-SDK-Android-V5 想要快速开发功能完善的无人机应用?DJI Android SDK V5为开发…...
别再死记硬背占空比了!用STM32 HAL库驱动MG90S舵机,我总结了这份避坑指南
STM32 HAL库驱动MG90S舵机:从参数计算到实战调试的全方位指南 刚接触STM32和舵机的新手们,是否曾被PWM配置中的各种参数搞得晕头转向?明明按照教程设置了占空比,舵机却纹丝不动;或者角度总是偏差几度,调试…...
保姆级教程:在Windows 11上用Mosquitto搭建你的第一个MQTT服务器(含开机自启和用户管理)
Windows 11环境下Mosquitto MQTT服务器全流程部署指南 在物联网项目开发初期,本地搭建MQTT服务器进行原型测试是每个开发者都会经历的环节。作为轻量级的消息传输协议,MQTT凭借其低功耗、低带宽占用和高效的发布/订阅机制,已成为智能家居、工…...
别再只把 AI 当聊天框了!探索 Google DeepMind 的 `agy` 命令行工具与人机协同新姿势
别再只把 AI 当聊天框了!探索 Google DeepMind 的 agy 命令行工具与人机协同新姿势 在 AI 辅助编程(AI Coding)卷到飞起的今天,大部分开发者最习惯的可能还是在 IDE 侧边栏里装个插件,或者在网页端和 AI 缝缝补补地复制…...
从URP到Built-in:手把手教你迁移Unity第三人称模板并成功换人(解决Shader报错)
从URP到Built-in:Unity第三人称模板迁移全流程实战指南 当你在Unity中打开官方提供的Third Person模板,准备将其应用到自己的项目时,可能会遇到一个棘手的问题——这个模板是基于URP(Universal Render Pipeline)设计的…...
「阅读」APP书源配置与管理完整指南:从新手到高级用户的实用教程
「阅读」APP书源配置与管理完整指南:从新手到高级用户的实用教程 【免费下载链接】Yuedu 📚「阅读」自用书源分享 项目地址: https://gitcode.com/gh_mirrors/yu/Yuedu 「阅读」APP作为一款开源的小说阅读工具,其核心功能依赖于书源的…...
Ryujinx终极指南:免费开源Switch模拟器快速入门与性能优化
Ryujinx终极指南:免费开源Switch模拟器快速入门与性能优化 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx Ryujinx是一款基于C#开发的开源Nintendo Switch模拟器࿰…...
Windows字体自定义终极指南:用No!! MeiryoUI轻松美化系统界面
Windows字体自定义终极指南:用No!! MeiryoUI轻松美化系统界面 【免费下载链接】noMeiryoUI No!! MeiryoUI is Windows system font setting tool on Windows 8.1/10/11. 项目地址: https://gitcode.com/gh_mirrors/no/noMeiryoUI 还在为Windows系统千篇一律的…...
地质建模革命:如何使用GemPy在10分钟内构建专业3D地质模型 [特殊字符]
地质建模革命:如何使用GemPy在10分钟内构建专业3D地质模型 🚀 【免费下载链接】gempy GemPy is an open-source, Python-based 3-D structural geological modeling software, which allows the implicit (i.e. automatic) creation of complex geologic…...
ChromeKeePass终极指南:如何在Chrome浏览器中实现KeePass密码自动填充
ChromeKeePass终极指南:如何在Chrome浏览器中实现KeePass密码自动填充 【免费下载链接】ChromeKeePass Chrome extensions for automatically filling credentials from KeePass 项目地址: https://gitcode.com/gh_mirrors/ch/ChromeKeePass ChromeKeePass是…...
