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。

相关推荐
lyx49497 小时前
Claude Code + GLM4.7 避坑指南:解决 Unable to connect to Anthropic services
ai·claude code
晓翔仔7 小时前
【深度实战】Agentic AI 安全攻防指南:基于 CSA 红队测试手册的 12 类风险完整解析
人工智能·安全·ai·ai安全
哥不是小萝莉8 小时前
揭秘 Codex Agent 的核心运行机制:从循环到智能决策
ai
你知道“铁甲小宝”吗丶8 小时前
VSCode使用Claude Code(MCP服务)
vscode·ai·ai编程
雨中飘荡的记忆12 小时前
Spring AI Gateway:从入门到实战,打造智能AI服务网关
人工智能·spring·gateway
JaguarJack12 小时前
Clawedbot 完整对接飞书教程 手把手搭建你的专属 AI 助手
ai·clawdbot
a程序小傲13 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
青春男大13 小时前
Redis和RedisTemplate快速上手
java·数据库·redis·后端·spring·缓存
大厂技术总监下海14 小时前
从“使用AI服务”到“拥有AI助手”:Clawdbot,你的个人AI基础设施
人工智能·ai·开源
带刺的坐椅15 小时前
论 AI Skills 分布式发展的必然性:从单体智能到“云端大脑”的跃迁
java·ai·llm·mcp·tool-call·skills