NacosSync

本文深入探讨了微服务环境下,Eureka、Zookeeper与Nacos之间的服务同步逻辑。详细介绍了Eureka与Zookeeper如何通过随机选择客户端、校验HTTP状态码、注册实例等步骤将服务信息同步至Nacos。同时,解析了PathChildrenCache与NamingService在zk同步过程中的作用,以及Guava EventBus在同步流程中的应用。

目录

一、Eureka同步逻辑

1. EurekaHttpClient从Client列表中随机选择

2. NamingService从Client列表中随机选择

3. EurekaHttpResponse中InstanceInfo同步

4. 校验HTTP状态码

5. registerInstance()同步服务注册

6. 同步任务订阅到SpecialSyncEventBus

二、zk同步逻辑

1. 根据serviceName的path和随机Client得到PathChildrenCache

2. NamingService从Client列表中随机选择

3. 获取当前数据List

4. 校验queryParam

5. registerInstance()同步服务注册

6. 添加监听器到listener


根据Guava的EventBus模式进行不同注册中心的服务同步(迁移)。

通过EventListener触发同步

public class EventListener {    
    @Subscribe
    public void listenerSyncTaskEvent(SyncTaskEvent syncTaskEvent) {

        try {
            long start = System.currentTimeMillis();
            syncManagerService.sync(syncTaskEvent.getTaskDO());
            skyWalkerCacheServices.addFinishedTask(syncTaskEvent.getTaskDO());
            metricsManager.record(MetricsStatisticsType.SYNC_TASK_RT, System.currentTimeMillis() - start);
        } catch (Exception e) {
            log.warn("listenerSyncTaskEvent process error", e);
        }

    }
}

public class SyncManagerService implements InitializingBean, ApplicationContextAware {
    public boolean sync(TaskDO taskDO) {

        return getSyncService(taskDO.getSourceClusterId(), taskDO.getDestClusterId()).sync(taskDO);

    }

    public SyncService getSyncService(String sourceClusterId, String destClusterId) {

        ClusterTypeEnum sourceClusterType = this.skyWalkerCacheServices.getClusterType(sourceClusterId);
        ClusterTypeEnum destClusterType = this.skyWalkerCacheServices.getClusterType(destClusterId);

        return syncServiceMap.get(generateSyncKey(sourceClusterType, destClusterType));
    }
}

SyncService的实现类sync()同步实现逻辑:

一、Eureka同步逻辑

  1. EurekaHttpClient
  2. NamingService
  3. EurekaHttpResponse
  4. HTTP状态码校验
  5. 同步
  6. 订阅到SpecialSyncEventBus
@NacosSyncService(sourceCluster = ClusterTypeEnum.EUREKA, destinationCluster = ClusterTypeEnum.NACOS)
public class EurekaSyncToNacosServiceImpl implements SyncService {
    @Override
    public boolean sync(TaskDO taskDO) {
        try {
            //random.nextInt(allClusterConnectKey.size())从client列表中随机选择一个client
            //1.EurekaHttpClient
            EurekaHttpClient eurekaHttpClient = eurekaServerHolder.get(taskDO.getSourceClusterId(), null);
            //2.NamingService
            NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId(), null);
            //3.EurekaHttpResponse
            EurekaHttpResponse<Application> eurekaHttpResponse =
                eurekaHttpClient.getApplication(taskDO.getServiceName());
            //Whether eurekaHttpResponse.getStatusCode() is in the HTTP series
            //4.HTTP状态码校验
            if (Objects.requireNonNull(HttpStatus.resolve(eurekaHttpResponse.getStatusCode())).is2xxSuccessful()) {
                Application application = eurekaHttpResponse.getEntity();
                for (InstanceInfo instanceInfo : application.getInstances()) {
                    //5.同步
                    if (needSync(instanceInfo.getMetadata())) {
                        if (InstanceInfo.InstanceStatus.UP.equals(instanceInfo.getStatus())) {
                            destNamingService.registerInstance(taskDO.getServiceName(),
                                buildSyncInstance(instanceInfo, taskDO));
                        } else {
                            destNamingService.deregisterInstance(instanceInfo.getAppName(), instanceInfo.getIPAddr(),
                                instanceInfo.getPort());
                        }
                    }

                }
            } else {
                metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
                throw new RuntimeException("trying to connect to the server failed");
            }
            //6.订阅到SpecialSyncEventBus
            specialSyncEventBus.subscribe(taskDO, this::sync);
        } catch (Exception e) {
            log.error("sync task from eureka to nacos was failed, taskId:{}", taskDO.getTaskId(), e);
            metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
            return false;
        }
        return true;
    }
}

1. EurekaHttpClient从Client列表中随机选择

EurekaHttpClient eurekaHttpClient = eurekaServerHolder.get(taskDO.getSourceClusterId(), null);
public abstract class AbstractServerHolder<T> implements Holder {
    @Override
    public T get(String clusterId, String namespace) {
        //namespace=null,则finalNamespace=" ";否则为namespace
        final String finalNamespace = Optional.ofNullable(namespace).orElse("");
        //key=clusterId_finalNamespace
        String key = Joiner.on("_").join(clusterId,finalNamespace);
        log.info("starting create cluster server,clusterId={}", clusterId);
        //if key 不存在于serviceMap,则调用传入的function计算key的value,放入缓存map中
        serviceMap.computeIfAbsent(key, clusterKey -> {
            try {
                //随机从random.nextInt(allClusterConnectKey.size())中选择Cluster
                return createServer(clusterId,
                        () -> skyWalkerCacheServices.getClusterConnectKey(clusterId),
                        finalNamespace);
            } catch (Exception e) {
                log.error(String.format("clusterId=%s,start server failed", clusterId), e);
                return null;
            }
        });

        return serviceMap.get(key);
    }
}

2. NamingService从Client列表中随机选择

 NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId(), null);

3. EurekaHttpResponse中InstanceInfo同步

InstanceInfo存储服务信息。

EurekaHttpResponse<Application> eurekaHttpResponse =
                eurekaHttpClient.getApplication(taskDO.getServiceName());

4. 校验HTTP状态码

 if (InstanceInfo.InstanceStatus.UP.equals(instanceInfo.getStatus())) 

5. registerInstance()同步服务注册

destNamingService.registerInstance(taskDO.getServiceName(),
                                buildSyncInstance(instanceInfo, taskDO));

6. 同步任务订阅到SpecialSyncEventBus

specialSyncEventBus.subscribe(taskDO, this::sync);

二、zk同步逻辑

  1. PathChildrenCache
  2. NamingService
  3. List<ChildData>
  4. queryParam校验
  5. 同步
  6. 添加监听器
@NacosSyncService(sourceCluster = ClusterTypeEnum.ZK, destinationCluster = ClusterTypeEnum.NACOS)
public class ZookeeperSyncToNacosServiceImpl implements SyncService {
    @Override
    public boolean sync(TaskDO taskDO) {
        try {
            if (pathChildrenCacheMap.containsKey(taskDO.getTaskId())) {
                return true;
            }

            //使用PathChildrenCache监听zk集群当前目录+子节点。
            //1.PathChildrenCache
            PathChildrenCache pathChildrenCache = getPathCache(taskDO);
            //2.NamingService
            NamingService destNamingService = nacosServerHolder
                    .get(taskDO.getDestClusterId(), null);
            //3.List<ChildData>
            //获取当前数据:childData存储路径String path、信息Stat stat
            List<ChildData> currentData = pathChildrenCache.getCurrentData();
            for (ChildData childData : currentData) {
                String path = childData.getPath();
                Map<String, String> queryParam = parseQueryString(childData.getPath());
                //4.queryParam校验
                if (isMatch(taskDO, queryParam) && needSync(queryParam)) {
                    Map<String, String> ipAndPortParam = parseIpAndPortString(path);
                    Instance instance = buildSyncInstance(queryParam, ipAndPortParam, taskDO);
                    //5.同步
                    destNamingService.registerInstance(
                            getServiceNameFromCache(taskDO.getTaskId(), queryParam),
                            instance);
                }
            }
            //6.添加监听器
            Objects.requireNonNull(pathChildrenCache).getListenable()
                    .addListener((client, event) -> {
                        try {

                            String path = event.getData().getPath();
                            Map<String, String> queryParam = parseQueryString(path);

                            if (isMatch(taskDO, queryParam) && needSync(queryParam)) {
                                Map<String, String> ipAndPortParam = parseIpAndPortString(path);
                                Instance instance = buildSyncInstance(queryParam, ipAndPortParam,
                                        taskDO);
                                switch (event.getType()) {
                                    case CHILD_ADDED:
                                        destNamingService.registerInstance(
                                                getServiceNameFromCache(taskDO.getTaskId(),
                                                        queryParam), instance);
                                        break;
                                    case CHILD_UPDATED:

                                        destNamingService.registerInstance(
                                                getServiceNameFromCache(taskDO.getTaskId(),
                                                        queryParam), instance);
                                        break;
                                    case CHILD_REMOVED:

                                        destNamingService.deregisterInstance(
                                                getServiceNameFromCache(taskDO.getTaskId(),
                                                        queryParam),
                                                ipAndPortParam.get(INSTANCE_IP_KEY),
                                                Integer.parseInt(
                                                        ipAndPortParam.get(INSTANCE_PORT_KEY)));

                                        break;
                                    default:
                                        break;
                                }
                            }
                        } catch (Exception e) {
                            log.error("event process from zookeeper to nacos was failed, taskId:{}",
                                    taskDO.getTaskId(), e);
                            metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
                        }

                    });
        } catch (Exception e) {
            log.error("sync task from zookeeper to nacos was failed, taskId:{}", taskDO.getTaskId(),
                    e);
            metricsManager.recordError(MetricsStatisticsType.SYNC_ERROR);
            return false;
        }
        return true;
    }
}

1. 根据serviceName的path和随机Client得到PathChildrenCache

PathChildrenCache监听zk集群中节点信息。

PathChildrenCache pathChildrenCache = getPathCache(taskDO);
   /**
     * fetch the Path cache when the task sync
     */
    protected PathChildrenCache getPathCache(TaskDO taskDO) {
        return pathChildrenCacheMap.computeIfAbsent(taskDO.getTaskId(), (key) -> {
            try {
                //集群中每个节点启动时,都注册到ZKServer上,然后每个节点都接收节点改变的监听,以便每个节点都实时更新集群信息(其他应用要使用)
                //监听使用PathChildrenCache,能监听当前目录+子节点。
                PathChildrenCache pathChildrenCache =
                        new PathChildrenCache(
                                //从client列表中随机得到一个client(CuratorFramework的client)
                                zookeeperServerHolder.get(taskDO.getSourceClusterId(), ""),
                                //路径:/dubbo/serviceName/providers
                                convertDubboProvidersPath(taskDO.getServiceName()), false);
                //BUILD_INITIAL_CACHE模式启动cache
                //  调用rebuild方法(此方法会阻塞执行)
                //      重新查询所有需要的数据
                //      不会触发任何事件
                //      安全创建path
                //      清空currentData缓存
                //      重新加载path下子节点,逐个结点重构缓存
                //          逐个读取节点数据和状态
                //          构建ChildData放入currentData
                //      通过rebuildTestExchanger发送要给信号对象
                pathChildrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
                return pathChildrenCache;
            } catch (Exception e) {
                log.error("zookeeper path children cache start failed, taskId:{}",
                        taskDO.getTaskId(), e);
                return null;
            }
        });

    }

2. NamingService从Client列表中随机选择

 NamingService destNamingService = nacosServerHolder.get(taskDO.getDestClusterId(), null);

3. 获取当前数据List<ChildData>

 List<ChildData> currentData = pathChildrenCache.getCurrentData();

 ChildData存储path、服务信息Stat。

public class ChildData implements Comparable<ChildData>
{
    private final String path;
    private final Stat stat;
    private final byte[] data;
}

4. 校验queryParam

Map<String, String> queryParam = parseQueryString(childData.getPath());
if (isMatch(taskDO, queryParam) && needSync(queryParam))

5. registerInstance()同步服务注册

destNamingService.registerInstance(
                            getServiceNameFromCache(taskDO.getTaskId(), queryParam),
                            instance);

6. 添加监听器到listener

Objects.requireNonNull(pathChildrenCache).getListenable()
                    .addListener((client, event))

 

参考:https://my.oschina.net/roccn/blog/918209

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值