手写分布式配置中心(三)增加实时刷新功能(短轮询)

news2025/6/16 8:31:23

要实现配置自动实时刷新,需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center​​​​​​​

服务端改造

服务端增加一个版本号version,新增配置的时候为1,每次更新配置就加1。

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

    @Override
    public 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=xxx
        List<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);
        }
    }
}

效果

修改配置

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

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

相关文章

技术指标和振荡器大全(二)

原文&#xff1a;stockcharts.com/school/doku.php?idchart_school:technical_indicators 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 成交量加权平均价格&#xff08;VWAP&#xff09; 目录 成交量加权平均价格&#xff08;VWAP&#xff09; 介绍 Tick 与 Minu…

项目中spring security与jwt.腾讯面试分享

写这篇文章是为了记录我面试pcg时平时没有留意或者钻研的地方。 面试是根据项目问的问题&#xff1a; 为什么采用jwt存储token&#xff1f; 我的项目是微服务项目&#xff0c;里面部署了资源服务和认证服务&#xff0c;这里选择jwt作为token一方面是可以存储用户的信息&#…

【DPDK】基于dpdk实现用户态UDP网络协议栈

文章目录 一.背景及导言二.协议栈架构设计1. 数据包接收和发送引擎2. 协议解析3. 数据包处理逻辑 三.网络函数编写1.socket2.bind3.recvfrom4.sendto5.close 四.总结 一.背景及导言 在当今数字化的世界中&#xff0c;网络通信的高性能和低延迟对于许多应用至关重要。而用户态网…

C++指针(四)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

文件批量重命名神器:按长度与区间智能管理,让文件整理更高效!

在数字化时代&#xff0c;电脑中堆积如山的文件常常让我们头疼不已。命名不规范、杂乱无章的文件不仅占用了大量的存储空间&#xff0c;更在关键时刻让我们难以迅速找到所需内容。现在&#xff0c;有了这款文件批量改名神器&#xff0c;一切烦恼将烟消云散&#xff01; 首先&a…

时隔n年再度会看Vue,Git

时隔n年再度会看Vue,Git 曾经沧海难为水&#xff0c;除却巫山不是云。不知道这句话用在这里合不合适&#xff0c;好多东西在记忆中都淡化了。但是互联网确是有记忆的。研究以前项目的时候&#xff0c;翻看到gitee码云上托管的项目&#xff0c;就像是自己的孩子重新又回来了一样…

观其大略之HybridCLR学习笔记

问题背景 1 现有热更方案的开发效率、性能没有到达极限&#xff0c;还有提升的空间 2 ios多平台政策导致热更新受限问题&#xff0c;ios禁止jit。根据我查找的资料&#xff0c;ios的代码段启动的时候就确定了&#xff0c;不能增加新的代码段。IOS封了内存&#xff08;或者堆&…

如何摆脱水印困扰?三款神器助您清爽无烦恼!

水印常常成为我们图片处理的一大难题&#xff0c;让我们苦恼不已。那么&#xff0c;如何能轻松摆脱这些烦人的水印呢&#xff1f;本文将向您推荐三款强大的去水印工具&#xff0c;让您清爽无烦恼&#xff0c;图片重焕光彩&#xff01; 1. 水印云 如何快速而准确地去除各类水印…

Stable Diffusion 解析:探寻 AI 绘画背后的科技神秘

AI 绘画发展史 在谈论 Stable Diffusion 之前&#xff0c;有必要先了解 AI 绘画的发展历程。 早在 2012 年&#xff0c;华人科学家吴恩达领导的团队训练出了当时世界上最大的深度学习网络。这个网络能够自主学习识别猫等物体&#xff0c;并在短短三天时间内绘制出了一张模糊但…

【RK3288 Android6, T8PRO 快捷按键 gpio 配置上拉输入】

文章目录 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】需求开发过程尝试找到没有用的上拉gpio尝试修改pwm1的gpio的默认上拉模式 改动 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】 需求 T8pro想要模仿T10 的 快捷按键&#xff…

嵌入式开发的常用软件、学习资源网站推荐

1、软件推荐 1.1、文本编辑软件 ——Notepad 1、适合编写和查看文本文件&#xff0c;也可以安装插件来查看二进制文件、对比文件 2、参考博客&#xff1a;《Notepad实用小技巧》&#xff1b; 1.2、PDF文件阅读软件——福昕PDF阅读器 福昕PDF阅读器&#xff0c;在官网就可以下载…

电商店群系统的搭建需要用到的官方接口如何申请?

电商电子商务企业往往都会需要再很多平台上面铺货&#xff0c;上传商品。 高科技的今天&#xff0c;我们已经不需要手动一个个品去上传了。那通过官方接口&#xff0c;如何实现快速铺货呢&#xff1f; 1688官方开放平台的API接口类型众多&#xff0c;并不是所有的企业都能申请…

springboot3.x集成nacos踩坑,并实现多环境配置

一、nacos安装部署 springboot3.x集成Nacos首先需要将Nacos从1.x升级到2.x&#xff0c;建议直接安装2.x版本&#xff0c;手动将1.x的配置信息迁移到2.x中&#xff0c;先并行一段时间&#xff0c;待全部迁移完成稳定运行之后再停掉1.x&#xff0c;升级和安装、操作请查看官方文…

HBuilder X删除之前登录的账号

打开目录 C:\Users\Administrator\AppData\Roaming\HBuilder X 用 HBuilder X 打开文件 prefs 将账号删除 保存文件 重启HBuilder X即可

开发手札:unity2022+vscode1.87联合开发

不得不说&#xff0c;时间的力量是很强大的&#xff0c;同时熵增理论适用于任何地方。 在现在的公司干了五年多了&#xff0c;五年前配置的内网开发机&#xff0c;i7 870016g1t hddgtx1080已经卡爆了&#xff0c;特别是硬盘掉速严重&#xff0c;开机开软件没有一两分钟都…

班主任管理班级的策略与措施

在教育的世界里&#xff0c;班主任不仅是知识的传递者&#xff0c;更是学生心灵的引路人。那么&#xff0c;如何在这个充满挑战和机遇的岗位上&#xff0c;舞动管理的翅膀&#xff0c;让班级飞得更高更远呢&#xff1f; 深入了解学生。这不仅仅是了解学生的姓名、成绩&#xff…

网络编程:TCP机械臂,UDP文件传输

1.TCP机械臂测试 程序代码&#xff1a; 1 #include<myhead.h>2 #define SER_IP "192.168.126.112" //服务器IP3 #define SER_PORT 8888 //服务器端口号4 5 #define CLI_IP "192.168.126.121" //客户端IP6 #define CLI_PORT 9999 //…

阿里云服务器2核2G性能测评99元一年和61元一年

阿里云2核2G服务器多少钱&#xff1f;99元一年&#xff0c;轻量云服务器是61元一年。2核2G服务器性能如何&#xff1f;性能很不错&#xff0c;不限制CPU性能&#xff0c;99元2核2G服务器是ECS经济型e实例&#xff0c;61元2核2G服务器是轻量应用服务器&#xff0c;都是3M公网带宽…

【智慧互联,有序充电,多场景充电】企业微电网能效及充电管理解决方案

企业需求&#xff08;目的地充电&#xff09; 站在企业的角度&#xff0c;除了要主动承担碳达峰、碳中和的社会责任&#xff0c;也需要考虑自身的经营和利润&#xff0c;需要结合企业的现状进行改造 01用能可靠、清洁 02用能安全怎么重视都不为过 03专业全面的能源管理需求…

windows无界鼠标,多机共享一套键鼠

原因 当前使用一台笔记本和一个台式机。用起来很麻烦。想要找到共享键鼠的方案。找到了无界鼠标这个软件。 安装 在两台电脑上都安装powertoy应用。 https://github.com/microsoft/PowerToys csdn下载 安装完成后找到无界鼠标打开 配置 多台电脑配置相同的key,刷新识别设…