集群间数据同步(Nacos Sync)源码详解

Nacos 作为一个分布式服务发现与配置管理平台,支持 多数据中心(Multi-DC)部署。在跨地域、跨集群的场景下,如何实现 集群间的数据同步 是关键问题。

为此,Nacos 提供了官方的数据同步工具 —— Nacos Sync(也称 Nacos-Syncnacos-sync),它是一个独立的开源项目(github.com/nacos-group/nacos-sync),用于实现 多个 Nacos 集群之间的服务与配置数据双向/单向同步


本文将从 源码角度 深入解析 Nacos Sync 的核心架构与实现机制,涵盖:

  • 整体架构设计
  • 数据同步流程
  • 服务发现同步源码分析
  • 配置数据同步源码分析
  • 调度与心跳机制
  • 失败重试与一致性保障

📌 注:Nacos Sync 是一个独立于 Nacos 主体的中间件,不内置在 Nacos Server 中。


一、Nacos Sync 简介

1. 核心功能

  • 支持 Nacos 到 Nacos 的服务与配置同步
  • 支持 Eureka、ZooKeeper、Consul 到 Nacos 的桥接(但本文聚焦 Nacos ↔ Nacos)
  • 支持单向/双向同步
  • 提供 Web 控制台管理同步任务
  • 支持增量同步 + 全量同步

2. 典型使用场景

北京集群 ←→ 上海集群(双活)
杭州集群 → 北京集群(灾备)

二、整体架构图

+------------------+     +------------------+
|   Nacos Cluster A |<---->  Nacos Sync App  <---->|   Nacos Cluster B |
+------------------+     +------------------+     +------------------+
     ↑    ↑                       ↑                        ↑    ↑
  服务注册  配置发布           同步任务调度              服务发现  配置获取
  • Nacos Sync App 是一个独立的 Spring Boot 应用。
  • 它作为“客户端”同时连接两个 Nacos 集群,监听变更并转发。

三、核心模块与源码结构(GitHub 项目)

项目地址:https://github.com/nacos-group/nacos-sync

关键模块:

nacos-sync/
├── nacos-sync-core/             // 核心同步逻辑
├── nacos-sync-dao/              // 数据库存储(MySQL)
├── nacos-sync-web/              // Web 控制台
├── nacos-sync-worker/           // 同步执行器
└── nacos-sync-api/              // 对接 Nacos SDK

四、数据同步流程总览

1. 用户在 Web 控制台创建同步任务(源集群 → 目标集群)
2. 任务持久化到 MySQL
3. 调度器加载任务并启动 SyncWorker
4. SyncWorker 使用 Nacos SDK 订阅源集群的服务/配置变更
5. 变更事件触发后,调用目标集群的 API 写入数据
6. 记录同步状态(成功/失败)
7. 失败则加入重试队列

五、服务同步源码详解(Service Sync)

1. 创建同步任务:SyncServiceController

// com.nacossync.controller.SyncServiceController
@PostMapping("/add")
public ResponseDto addSyncTask(@RequestBody SyncTaskDTO taskDTO) {
    syncTaskService.add(taskDTO);
    return ResponseDto.success();
}
  • SyncTaskDTO 包含:
    • sourceClusterId, destClusterId
    • dataId, group, namespace(配置)
    • serviceName, groupName(服务)
    • syncMode(单向/双向)

2. 任务调度:ScheduleTask

// com.nacossync.schedule.ScheduleTask
@Component
public class ScheduleTask {

    @Scheduled(fixedDelay = 10_000)
    public void loadAndScheduleTasks() {
        List<SyncTaskDO> tasks = syncTaskDAO.selectEnableTasks();
        for (SyncTaskDO task : tasks) {
            if (!syncWorkerManager.isRunning(task.getTaskId())) {
                syncWorkerManager.start(task);
            }
        }
    }
}
  • 每 10 秒扫描一次数据库中的启用任务。
  • 通过 syncWorkerManager.start(task) 启动同步工作线程。

3. 启动服务同步:ServiceSyncWorker

// com.nacossync.worker.impl.ServiceSyncWorker
public class ServiceSyncWorker implements SyncWorker {

    private NamingService sourceNamingService; // 源集群 Nacos Client
    private NamingService destNamingService;   // 目标集群 Nacos Client

    @Override
    public void start(SyncTaskDO task) {
        // 初始化两个集群的 Nacos Client
        sourceNamingService = NacosFactory.createNamingService(task.getSourceClusterUrl());
        destNamingService = NacosFactory.createNamingService(task.getDestClusterUrl());

        // 订阅源集群服务变更
        sourceNamingService.subscribe(task.getServiceName(), task.getGroupName(), event -> {
            if (event instanceof InstanceChangeEvent) {
                handleInstanceChange((InstanceChangeEvent) event, task);
            }
        });
    }
}
  • 使用 Nacos SDKsubscribe 监听服务实例变化。
  • 支持 ADDED, REMOVED, HEALTHY, UNHEALTHY 事件。

4. 处理变更并同步:handleInstanceChange

private void handleInstanceChange(InstanceChangeEvent event, SyncTaskDO task) {
    Instance instance = event.getInstance();
    try {
        switch (event.getEventType()) {
            case ADDED:
            case HEALTHY:
                destNamingService.registerInstance(task.getServiceName(), task.getGroupName(), instance);
                break;
            case REMOVED:
            case UNHEALTHY:
                destNamingService.deregisterInstance(task.getServiceName(), task.getGroupName(), instance.getIp(), instance.getPort());
                break;
        }
        // 更新同步状态
        syncTaskDAO.updateLastSyncTime(task.getTaskId(), System.currentTimeMillis());
    } catch (Exception e) {
        // 记录失败,加入重试
        retryManager.addRetry(new RetryTask(task, event));
        Loggers.SYNC.error("Sync instance failed", e);
    }
}
  • 使用 registerInstance / deregisterInstance 同步到目标集群。
  • 异常时调用 retryManager.addRetry()

六、配置同步源码详解(Config Sync)

1. 配置同步器:ConfigSyncWorker

// com.nacossync.worker.impl.ConfigSyncWorker
public class ConfigSyncWorker implements SyncWorker {

    private ConfigService sourceConfigService;
    private ConfigService destConfigService;

    @Override
    public void start(SyncTaskDO task) {
        sourceConfigService = NacosFactory.createConfigService(task.getSourceClusterUrl());
        destConfigService = NacosFactory.createConfigService(task.getDestClusterUrl());

        // 获取当前配置(全量同步)
        String content = sourceConfigService.getConfig(task.getDataId(), task.getGroup(), 5000);

        // 推送到目标集群
        destConfigService.publishConfig(task.getDataId(), task.getGroup(), content);

        // 监听后续变更(增量同步)
        sourceConfigService.addListener(task.getDataId(), task.getGroup(), new Listener() {
            @Override
            public void receiveConfigInfo(String configInfo) {
                try {
                    destConfigService.publishConfig(task.getDataId(), task.getGroup(), configInfo);
                    syncTaskDAO.updateLastSyncTime(task.getTaskId(), System.currentTimeMillis());
                } catch (NacosException e) {
                    retryManager.addRetry(new RetryTask(task, configInfo));
                }
            }
        });
    }
}
  • 首次启动时执行 全量同步getConfig + publishConfig)。
  • 之后通过 addListener 实现 增量同步

七、重试机制:RetryManager

// com.nacossync.retry.RetryManager
@Component
public class RetryManager {

    private final BlockingQueue<RetryTask> retryQueue = new LinkedBlockingQueue<>();

    @PostConstruct
    public void init() {
        // 启动重试线程
        new Thread(this::retryLoop).start();
    }

    private void retryLoop() {
        while (true) {
            RetryTask task = retryQueue.take();
            if (task.getRetryCount() < MAX_RETRY_TIMES) {
                Thread.sleep(Math.pow(2, task.getRetryCount()) * 1000); // 指数退避
                execute(task);
                task.incrementRetryCount();
            } else {
                Loggers.SYNC.error("Retry failed after {} times: {}", MAX_RETRY_TIMES, task);
            }
        }
    }
}
  • 支持最多 3~5 次重试。
  • 采用 指数退避 策略,避免雪崩。

八、数据库设计(MySQL)

CREATE TABLE sync_task (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    task_name VARCHAR(64),
    source_cluster_id VARCHAR(64),
    dest_cluster_id VARCHAR(64),
    data_type ENUM('SERVICE', 'CONFIG'),
    service_name VARCHAR(128),
    group_name VARCHAR(128),
    data_id VARCHAR(128),
    group VARCHAR(128),
    sync_mode ENUM('ONEWAY', 'BIDIRECTIONAL'),
    status TINYINT DEFAULT 1,
    last_sync_time BIGINT
);
  • 所有同步任务持久化存储。
  • 支持动态增删改查。

九、Web 控制台功能

  • 任务管理:增删改查同步任务
  • 同步日志:查看成功/失败记录
  • 集群管理:维护 Nacos 集群地址
  • 状态监控:实时显示同步延迟

十、源码调用链总结

服务同步链路:

SyncServiceController.add()
    → syncTaskDAO.insert()
        → ScheduleTask.loadAndScheduleTasks()
            → ServiceSyncWorker.start()
                → sourceNamingService.subscribe()
                    → InstanceChangeEvent
                        → destNamingService.registerInstance()

配置同步链路:

ConfigSyncWorker.start()
    → sourceConfigService.getConfig() → 全量同步
    → sourceConfigService.addListener() → 增量监听
        → receiveConfigInfo()
            → destConfigService.publishConfig()

十一、局限性与注意事项

问题说明
不支持自动冲突解决若双向同步,需避免同名服务/配置冲突
无最终一致性保证依赖重试,极端情况下可能丢失变更
性能瓶颈在单节点Sync App 是单点,高并发需集群化部署
不支持批量同步每个任务独立运行,资源消耗大

十二、总结

特性实现方式
同步模式基于 Nacos SDK 订阅 + 调用 API 写入
服务同步subscribe + registerInstance
配置同步getConfig + addListener + publishConfig
调度机制定时扫描数据库任务
可靠性重试 + 日志记录
存储MySQL 持久化任务

建议阅读源码路径

  1. ServiceSyncWorker.java —— 服务同步核心
  2. ConfigSyncWorker.java —— 配置同步核心
  3. ScheduleTask.java —— 任务调度
  4. RetryManager.java —— 重试机制
  5. sync_task 表结构 —— 任务持久化
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值