JaiRouter 多版本配置管理:一个轻量级多版本配置实现思路

JaiRouter 多版本配置管理:一个轻量级多版本配置实现思路


文章目录

一、需求缘起

最近在写开源项目JaiRouter,配置项目比较多,考虑到真实的业务场景下,多次配置后,测试后,无法回滚到原始版本,因此考虑到需要频繁回滚 JSON 配置。为了省掉数据库依赖,我把版本号直接放到文件名里,写完发现代码意外地短,回滚速度意外地快。整理出来,权当抛砖引玉。


二、核心思路

  1. 文件名即版本号:config@version.json
  2. 当前配置单独存放:config.json
  3. 读/写/回滚/清理 全部基于 java.nio.file.Files,不引入第三方存储

三、代码骨架

kotlin 复制代码
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 文件系统多版本配置存储:文件名即版本号
 */
public final class FileVersionStore {

    private static final ObjectMapper MAPPER = new ObjectMapper();
    private final Path dir;

    public FileVersionStore(Path root) throws IOException {
        this.dir = root.toAbsolutePath().normalize();
        Files.createDirectories(dir);
    }

    /* ---------- 基础 API ---------- */

    /** 保存指定版本(覆盖写) */
    public void save(int version, Map<String, Object> data) throws IOException {
        Path file = versionFile(version);
        Files.writeString(file, MAPPER.writeValueAsString(data),
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    /** 读取当前配置 */
    public Map<String, Object> current() throws IOException {
        Path cur = currentFile();
        if (Files.notExists(cur)) {
            throw new NoSuchFileException("当前配置文件不存在: " + cur);
        }
        return MAPPER.readValue(cur.toFile(), new TypeReference<>() {});
    }

    /** 回滚到指定版本(原子覆盖) */
    public void rollbackTo(int version) throws IOException {
        Path src = versionFile(version);
        if (Files.notExists(src)) {
            throw new NoSuchFileException("版本文件不存在: " + src);
        }
        // ATOMIC_MOVE 保证并发安全
        Files.copy(src, currentFile(), StandardCopyOption.REPLACE_EXISTING);
    }

    /** 列出所有可用版本,升序 */
    public List<Integer> listVersions() throws IOException {
        try (Stream<Path> stream = Files.list(dir)) {
            return stream.map(Path::getFileName)
                         .map(Path::toString)
                         .filter(n -> n.startsWith("config@") && n.endsWith(".json"))
                         .map(n -> n.substring("config@".length(), n.length() - ".json".length()))
                         .mapToInt(Integer::parseInt)
                         .sorted()
                         .boxed()
                         .collect(Collectors.toList());
        }
    }

    /** 删除指定版本(手工清理用) */
    public void delete(int version) throws IOException {
        Files.deleteIfExists(versionFile(version));
    }

    /* ---------- 内部工具 ---------- */

    private Path currentFile() {
        return dir.resolve("config.json");
    }

    private Path versionFile(int version) {
        return dir.resolve("config@" + version + ".json");
    }

    /* ---------- 简单演示 ---------- */
    public static void main(String[] args) throws IOException {
        FileVersionStore store = new FileVersionStore(Paths.get("repo"));

        // 写两个版本
        store.save(1, Map.of("threshold", 10));
        store.save(2, Map.of("threshold", 20));

        // 回滚到 v1
        store.rollbackTo(1);
        System.out.println("current=" + store.current()); // {threshold=10}

        // 查看所有版本
        System.out.println("versions=" + store.listVersions()); // [1, 2]
    }
}

依赖只有 Jackson。


四、什么时候够用

  • 单节点或小集群
  • 版本总量 < 10 k(目录扫描可接受)
  • 无需字段级合并,整体覆盖即可

五、什么时候换方案

  • 需要分布式一致性 → 上 etcd/consul
  • 需要 JSON Patch → 上 Git 或数据库
  • 文件数 > 10 k → 加一层前缀索引或转对象存储

六、小结

把版本号写进文件名,回滚操作退化成一次文件复制,代码量少、依赖少、调试方便。对于个人项目或内部小工具,算一个"够用且易丢进 Docker"的折中方案。

完整实验代码已放到 JAiRouter,欢迎试用、提 issue。

相关推荐
发量惊人的中年网工4 分钟前
做 AI 应用怎么选大模型 API?选项应该看哪几点?
人工智能·ai·大模型
java_nnnn8 分钟前
spring web mvc图书管理小案例
前端·spring·mvc
随遇而安zx36 分钟前
Codex CLI 介绍和使用
ai·codex·智能体
又过二十9 小时前
用 Electron 做的 AI 视频切片工作台:VClip 的技术选型与踩坑实录
ai·electron·aigc·funasr·视频切片
步行cgn12 小时前
StringHttpMessageConverter 详解:Spring MVC 中字符串读写的默认转换器
java·spring·mvc
xcLeigh14 小时前
FreeRTOS实战:用STM32+SEGGER打造工业级实时嵌入式系统
人工智能·stm32·单片机·嵌入式硬件·ai
步行cgn14 小时前
@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
java·spring·mvc
xbzb16 小时前
IP、掩码、网关、DNS 到底谁管什么?网络基础与七种设备一次讲清
网络·网关·mac·ip·dns·掩码
老唐77716 小时前
大模型面试高频题:Transformer、MoE、RLHF、DPO 核心原理拆解
人工智能·深度学习·ai·大模型·transformer·智能体
小七-七牛开发者16 小时前
Agent 支线知识:Personal Agent 如何理解个人意图?
ai·agent·ai智能体·长期记忆·personalagent