手写分布式配置中心(二)实现分布式配置中心的简单版本
这一篇文章比较简单,就是一个增删改查的服务端和一个获取配置的客户端,旨在搭建一个简单的配置中心架构,代码在 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&…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...