当前位置: 首页 > news >正文

手写分布式配置中心(二)实现分布式配置中心的简单版本

这一篇文章比较简单,就是一个增删改查的服务端和一个获取配置的客户端,旨在搭建一个简单的配置中心架构,代码在 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对象的时候设置了配置的值,但是如果配置中心的配置发生变化后,客户端是无法感知的,为了解决这个问题需要加入配置自动刷新功能,这个我们在下一篇文章中介绍。

相关文章:

手写分布式配置中心(二)实现分布式配置中心的简单版本

这一篇文章比较简单&#xff0c;就是一个增删改查的服务端和一个获取配置的客户端&#xff0c;旨在搭建一个简单的配置中心架构&#xff0c;代码在 https://gitee.com/summer-cat001/config-center 服务端 服务端选择用springboot 2.7.14搭建&#xff0c;设计了4个接口/confi…...

跨境知识分享:什么是动态IP?和静态IP有什么区别?

对于我们跨境人来说&#xff0c;清楚地了解IP地址、代理IP等这些基础知识&#xff0c;并学会正确地使用IP地址对于保障店铺的安全性和稳定性至关重要&#xff0c;尤其是理解动态IP和静态IP之间的区别&#xff0c;以及如何利用这些知识来防止账号关联&#xff0c;对于每个电商卖…...

liunx安装jdk、redis、nginx

jdk安装 下载jdk,解压。 sudo tar -zxvf /usr/local/jdk-8u321-linux-x64.tar.gz -C /usr/local/ 在/etc/profile文件中的&#xff0c;我们只需要编辑一下&#xff0c;在文件的最后加上java变量的有关配置&#xff08;其他内容不要动&#xff09;。 export JAVA_HOME/usr/l…...

【C++】STL学习之旅——初识STL,认识string类

string类 1 STL 简介2 STL怎么学习3 STL缺陷4 string4.1 初识 string4.2 初步使用构造函数成员函数 5 小试牛刀Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读&#xff01;&#xff01;&#xff01;下一篇文章见&#xff01;&#xff01;&#xff01; 1 STL 简介 …...

Java学习笔记002——类的修饰符

在Java语言中&#xff0c;类的访问修饰符决定了其它类能够访问该类的方式。类有如下4种访问修饰符&#xff0c;在创建类时用于类的声明&#xff1a; 1、public: 当一个类被声明为public时&#xff0c;它可以从任何其他类中被访问&#xff0c;无论这些类位于哪个包中。通常&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 那么便不能随便在这里面增加文件了,增加了签名分块&#xff08;不然签名信息存哪里&#xff09;这里涉及一个文件概念 …...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Scroll容器组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Scroll容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Scroll容器组件 可滚动的容器组件&#xff0c;当子组件的布局尺寸超过父组件…...

FreeRTOS操作系统学习——FreeRTOS工程创建

FreeROTS工程创建 详细步骤 如无特殊情况&#xff0c;大部人都要配置为外部高速时钟 另外&#xff0c;本实验使用了FreeRTOS&#xff0c;FreeRTOS的时基使用的是Systick&#xff0c;而 STM32CubeMX中默认的HAL库时基也是Systick&#xff0c;为了避免可能的冲突&#xff0c;最…...

6. 使用 Spring Boot进行开发(Developing with Spring Boot)

6. 使用 Spring Boot进行开发&#xff08;Developing with Spring Boot&#xff09; 本节详细介绍了如何使用Spring Boot。它涵盖考虑构建系统、自动配置以及如何运行应用程序等主题。我们还介绍一些 Spring Boot 最新做法。虽然 Spring Boot 没有什么特别之处&#xff08;它只…...

IP地址工具,判断IP是否在指定范围内(支持ipv6)

常用方法&#xff0c;判断一个ip是否在指定的ip范围内&#xff0c;范围可能包括起始ip范围或者掩码形式&#xff0c;无其它依赖&#xff0c; package com.yk.ip;import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import jav…...

Redis 之六:Redis 的哨兵模式(Sentinel)

Redis 哨兵&#xff08;Sentinel&#xff09;模式是一种高可用性解决方案&#xff0c;用于监控和自动故障转移的集群系统。 在 Redis Sentinel 架构中&#xff0c;哨兵是一组运行在特殊模式下的 Redis 进程&#xff0c;它们可以监控一个或多个主从复制结构中的 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&#xff1b; 在Linux目标板和Windows PC上运行iperf进行测试&#xff1b; 二、调试 1、查询目标板上的iperf 使用终端助手连接目标板&#xff0c;然后输入命令查询iperf的版本&#xff1a; rootrk3566-buildroot:~# iperf -v iperf version …...

【无标题】计算机主要应用于哪些领域

科学计算&#xff08;或称为数值计算&#xff09;、数据处理&#xff08;信息管理&#xff09;、辅助工程、生产自动化、人工智能。1、科学计算&#xff08;或称为数值计算&#xff09;&#xff1a;早期的计算机主要用于科学计算。目前&#xff0c;科学计算仍然是计算机应用的一…...

力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)

目录 &#x1f6a9;了解题意 &#x1f6a9;算法分析 第一种方法&#xff1a;双指针 &#x1f6a9;代码实现一 第二种方法&#xff1a;三指针 &#x1f6a9;代码实现二 &#x1f6a9;了解题意 本题将整数0&#xff0c;1&#xff0c;2代表红白篮&#xff0c;nums中的整数并…...

基于Java SSM springboot+VUE+redis实现的前后端分类版网上商城项目

基于Java SSM springbootVUEredis实现的前后端分类版网上商城项目 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐…...

01——什么是人工智能

学习形式分类 1) 有监督学习 有监督学习&#xff08;supervised learning&#xff09;&#xff0c;需要你事先需要准备好要输入数据&#xff08;训练样本&#xff09;与真实的输出结果&#xff08;参考答案)&#xff0c;然后通过计算机的学习得到一个预测模型&#xff0c;再用…...

AI国漫女神这样画!傻瓜式的云端SD(stable diffusion)部署教程 - 白嫖4090、无需代码、一键启动

一、前言&#xff1a;为什么要云端部署stable diffusion? 前段时间一直在用midjourney进行Ai绘画&#xff0c;但是发现Midjourney绘画的限制有点多很死板&#xff0c;很多图都不能画&#xff0c;懂得都懂。想使用Midjourney可以看这个&#xff1a;&#xff08; 【推荐】Midjo…...

消息队列、共享内存、信号灯

IPC&#xff08;Inter-Process Communication&#xff0c;进程间通信&#xff09; 常用的 IPC 对象包括管道&#xff08;pipe&#xff09;、消息队列&#xff08;message queue&#xff09;、信号量&#xff08;semaphore&#xff09;和共享内存&#xff08;shared memory&…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官

。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量&#xff1a;setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...

pycharm 设置环境出错

pycharm 设置环境出错 pycharm 新建项目&#xff0c;设置虚拟环境&#xff0c;出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...