14、Nacos 源码分析-Distro协议(下)

本文详细分析了Distro协议在接收方的处理流程,从DistroDataRequest的多种数据操作类型入手,包括验证请求、快照请求、数据变动请求和查询数据请求的处理方式。核心涉及DistroDataRequestHandler、DistroProtocol和DistroClientDataProcessor的角色和交互,以及客户端验证、快照生成和数据同步的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在上一篇中,我们分析了和Distro协议相关的一些核心类,但是分析的更多是作为发起方的一个处理逻辑,本篇站在Dirstro协议的接收方的角度继续分析接收到之后请求后的处理逻辑。

回忆一下上篇中,我们分析到clusterRpcClientProxy.sendRequest(member, request)后,就未再继续往下继续跟踪了,本次接着这部分代码继续深入,首先我们确定的是这个requestDistroDataRequest类型,然后找到对应requesthandler就是他的处理逻辑。

DistroDataRequest的处理逻辑就是DistroDataRequestHandlerhandle方法中。

@Override
public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
    try {
        switch (request.getDataOperation()) {
            case VERIFY:
                // 处理验证请求
                return handleVerify(request.getDistroData(), meta);
            case SNAPSHOT:
                // 处理快照请求
                return handleSnapshot();
            case ADD:
            case CHANGE:
            case DELETE:
                // 处理数据变动的请求
                return handleSyncData(request.getDistroData());
            case QUERY:
                // 处理查询数据的请求
                return handleQueryData(request.getDistroData());
            default:
                // 默认的返回
                return new DistroDataResponse();
        }
    } catch (Exception e) {
        Loggers.DISTRO.error("[DISTRO-FAILED] distro handle with exception", e);
        DistroDataResponse result = new DistroDataResponse();
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("handle distro request with exception");
        return result;
    }
}

上面的代码可以看出,DistroDataRequest有这多种数据操作类型,根据不同的类型有着不同的处理方式。其处理方式分别有处理验证请求处理快照请求处理数据变动的请求处理查询数据的请求。下面我们一一分析下。

handleVerify

private DistroDataResponse handleVerify(DistroData distroData, RequestMeta meta) {
    DistroDataResponse result = new DistroDataResponse();
    // 使用distroProtocol进行验证
    if (!distroProtocol.onVerify(distroData, meta.getClientIp())) {
        result.setErrorInfo(ResponseCode.FAIL.getCode(), "[DISTRO-FAILED] distro data verify failed");
    }
    return result;
}

代码中直接使用了distroProtocol.onVerify方法。

public boolean onVerify(DistroData distroData, String sourceAddress) {
    if (Loggers.DISTRO.isDebugEnabled()) {
        Loggers.DISTRO.debug("[DISTRO] Receive verify data type: {}, key: {}", distroData.getType(),
                             distroData.getDistroKey());
    }
    String resourceType = distroData.getDistroKey().getResourceType();
    // 寻找对应的处理器执行,这里回想一下DistroClientComponentRegistry#doRegister()方法,在register中注册的处理器,实际就是DistroClientDataProcessor
    DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
    if (null == dataProcessor) {
        Loggers.DISTRO.warn("[DISTRO] Can't find verify data process for received data {}", resourceType);
        return false;
    }
    // 通过DistroClientDataProcessor执行验证
    return dataProcessor.processVerifyData(distroData, sourceAddress);
}

既然我们知道了dataProcessor就是DistroClientDataProcessor,那我们接着往下看

public boolean processVerifyData(DistroData distroData, String sourceAddress) {
    // 对数据进行反序列化为DistroClientVerifyInfo对象
    DistroClientVerifyInfo verifyData = ApplicationUtils.getBean(Serializer.class)
        .deserialize(distroData.getContent(), DistroClientVerifyInfo.class);
    // 通过clientManager进行验证
    if (clientManager.verifyClient(verifyData)) {
        return true;
    }
    Loggers.DISTRO.info("client {} is invalid, get new client from {}", verifyData.getClientId(), sourceAddress);
    return false;
}

clientManager是一个委托类,在Distro协议中,委托的就是EphemeralIpPortClientManager,因为ephemeral默认是true(在1、Nacos 服务注册客户端源码分析中分析过)

@Override
public boolean verifyClient(DistroClientVerifyInfo verifyData) {
    String clientId = verifyData.getClientId();
    IpPortBasedClient client = clients.get(clientId);
    if (null != client) {
        // 旧版本的远程节点将始终使用零修订进行验证
        if (0 == verifyData.getRevision() || client.getRevision() == verifyData.getRevision()) {
            NamingExecuteTaskDispatcher.getInstance()
                .dispatchAndExecuteTask(clientId, new ClientBeatUpdateTask(client));
            return true;
        } else {
            Loggers.DISTRO.info("[DISTRO-VERIFY-FAILED] IpPortBasedClient[{}] revision local={}, remote={}",
                                client.getClientId(), client.getRevision(), verifyData.getRevision());
        }
    }
    return false;
}

如果版本一致的话,就会执行ClientBeatUpdateTask,看下ClientBeatUpdateTaskrun方法

@Override
public void run() {
    long currentTime = System.currentTimeMillis();
    for (InstancePublishInfo each : client.getAllInstancePublishInfo()) {
        ((HealthCheckInstancePublishInfo) each).setLastHeartBeatTime(currentTime);
    }
    client.setLastUpdatedTime();
}

这个方法就是更新了client的最新的更新时间。

handleSnapshot

private DistroDataResponse handleSnapshot() {
    DistroDataResponse result = new DistroDataResponse();
    // 使用的是distroProtocol的onSnapshot方法
    DistroData distroData = distroProtocol.onSnapshot(DistroClientDataProcessor.TYPE);
    result.setDistroData(distroData);
    return result;
}

代码中直接使用了distroProtocol.onSnapshot方法。

public DistroData onSnapshot(String type) {
    // 寻找对应的DistroDataStorage执行,这里回想一下DistroClientComponentRegistry#doRegister()方法,在register中的registerDataStorage,实际就是DistroClientDataProcessor
    DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(type);
    if (null == distroDataStorage) {
        Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", type);
        return new DistroData(new DistroKey("snapshot", type), new byte[0]);
    }
    return distroDataStorage.getDatumSnapshot();
}

没想到吧,DistroDataStorage也是DistroClientDataProcessor,继续往下看

public DistroData getDatumSnapshot() {
    List<ClientSyncData> datum = new LinkedList<>();
    for (String each : clientManager.allClientId()) {
        Client client = clientManager.getClient(each);
        if (null == client || !client.isEphemeral()) {
            continue;
        }
        // 获取SyncData
        datum.add(client.generateSyncData());
    }
    ClientSyncDatumSnapshot snapshot = new ClientSyncDatumSnapshot();
    snapshot.setClientSyncDataList(datum);
    byte[] data = ApplicationUtils.getBean(Serializer.class).serialize(snapshot);
    // 返回了clientManager中所有client的ClientSyncData
    return new DistroData(new DistroKey(DataOperation.SNAPSHOT.name(), TYPE), data);
}

返回了所有实例的ClientSyncData。那generateSyncData()是生成了什么数据呢?

@Override
public ClientSyncData generateSyncData() {
    List<String> namespaces = new LinkedList<>();
    List<String> groupNames = new LinkedList<>();
    List<String> serviceNames = new LinkedList<>();

    List<String> batchNamespaces = new LinkedList<>();
    List<String> batchGroupNames = new LinkedList<>();
    List<String> batchServiceNames = new LinkedList<>();

    List<InstancePublishInfo> instances = new LinkedList<>();
    List<BatchInstancePublishInfo> batchInstancePublishInfos = new LinkedList<>();
    BatchInstanceData  batchInstanceData = new BatchInstanceData();
    for (Map.Entry<Service, InstancePublishInfo> entry : publishers.entrySet()) {
        InstancePublishInfo instancePublishInfo = entry.getValue();
        if (instancePublishInfo instanceof BatchInstancePublishInfo) {
            BatchInstancePublishInfo batchInstance = (BatchInstancePublishInfo) instancePublishInfo;
            batchInstancePublishInfos.add(batchInstance);
            buildBatchInstanceData(batchInstanceData, batchNamespaces, batchGroupNames, batchServiceNames, entry);
            batchInstanceData.setBatchInstancePublishInfos(batchInstancePublishInfos);
        } else {
            namespaces.add(entry.getKey().getNamespace());
            groupNames.add(entry.getKey().getGroup());
            serviceNames.add(entry.getKey().getName());
            instances.add(entry.getValue());
        }
    }
    ClientSyncData data = new ClientSyncData(getClientId(), namespaces, groupNames, serviceNames, instances, batchInstanceData);
    data.getAttributes().addClientAttribute(REVISION, getRevision());
    return data;
}

ClientSyncData就是一个实例的所有的属性信息,包括namespacegroupNameserviceName等信息。

handleSyncData

private DistroDataResponse handleSyncData(DistroData distroData) {
    DistroDataResponse result = new DistroDataResponse();
    // distroProtocol接受distroData
    if (!distroProtocol.onReceive(distroData)) {
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("[DISTRO-FAILED] distro data handle failed");
    }
    return result;
}

还是distroProtocol处理

public boolean onReceive(DistroData distroData) {
    Loggers.DISTRO.info("[DISTRO] Receive distro data type: {}, key: {}", distroData.getType(),
                        distroData.getDistroKey());
    String resourceType = distroData.getDistroKey().getResourceType();
    // 获取到DistroClientDataProcessor
    DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
    if (null == dataProcessor) {
        Loggers.DISTRO.warn("[DISTRO] Can't find data process for received data {}", resourceType);
        return false;
    }
    return dataProcessor.processData(distroData);
}

依然是调用DistroClientDataProcessorprocessData方法

@Override
public boolean processData(DistroData distroData) {
    switch (distroData.getType()) {
        case ADD:
        case CHANGE:
            // 反序列化ClientSyncData
            ClientSyncData clientSyncData = ApplicationUtils.getBean(Serializer.class)
                .deserialize(distroData.getContent(), ClientSyncData.class);
            // 处理新增和更新的方法
            handlerClientSyncData(clientSyncData);
            return true;
        case DELETE:
            String deleteClientId = distroData.getDistroKey().getResourceKey();
            Loggers.DISTRO.info("[Client-Delete] Received distro client sync data {}", deleteClientId);
            // 删除则断开连接
            clientManager.clientDisconnected(deleteClientId);
            return true;
        default:
            return false;
    }
}

在新增的和修改的时候调用了handlerClientSyncData方法。

private void handlerClientSyncData(ClientSyncData clientSyncData) {
    Loggers.DISTRO
        .info("[Client-Add] Received distro client sync data {}, revision={}", clientSyncData.getClientId(),
              clientSyncData.getAttributes().getClientAttribute(ClientConstants.REVISION, 0L));
    // 同步客户端的连接
    clientManager.syncClientConnected(clientSyncData.getClientId(), clientSyncData.getAttributes());
    Client client = clientManager.getClient(clientSyncData.getClientId());
    // 对client更新
    upgradeClient(client, clientSyncData);
}

syncClientConnected

这个syncClientConnected使用的是EphemeralIpPortClientManagersyncClientConnected

@Override
public boolean clientConnected(String clientId, ClientAttributes attributes) {
    // 创建一个client,并进行连接
    return clientConnected(clientFactory.newClient(clientId, attributes));
}

@Override
public IpPortBasedClient newClient(String clientId, ClientAttributes attributes) {
    long revision = attributes.getClientAttribute(REVISION, 0);
    // 创建了一个基于ip和端口的客户端
    IpPortBasedClient ipPortBasedClient = new IpPortBasedClient(clientId, true, revision);
    ipPortBasedClient.setAttributes(attributes);
    return ipPortBasedClient;
}

@Override
public boolean clientConnected(final Client client) {
    clients.computeIfAbsent(client.getClientId(), s -> {
        Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
        IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
        // 对客户端进行初始化
        ipPortBasedClient.init();
        return ipPortBasedClient;
    });
    return true;
}

public void init() {
    if (ephemeral) {
        beatCheckTask = new ClientBeatCheckTaskV2(this);
        // 定期,每隔5s做一次健康检查
        HealthCheckReactor.scheduleCheck(beatCheckTask);
    } else {
        healthCheckTaskV2 = new HealthCheckTaskV2(this);
        HealthCheckReactor.scheduleCheck(healthCheckTaskV2);
    }
}

在同步客户端连接的时候,后台会执行一个每隔5s的定时任务,对连接的客户端进行健康检查。

upgradeClient

private void upgradeClient(Client client, ClientSyncData clientSyncData) {
    Set<Service> syncedService = new HashSet<>();
    // process batch instance sync logic
    processBatchInstanceDistroData(syncedService, client, clientSyncData);
    List<String> namespaces = clientSyncData.getNamespaces();
    List<String> groupNames = clientSyncData.getGroupNames();
    List<String> serviceNames = clientSyncData.getServiceNames();
    List<InstancePublishInfo> instances = clientSyncData.getInstancePublishInfos();

    for (int i = 0; i < namespaces.size(); i++) {
        Service service = Service.newService(namespaces.get(i), groupNames.get(i), serviceNames.get(i));
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        syncedService.add(singleton);
        InstancePublishInfo instancePublishInfo = instances.get(i);
        if (!instancePublishInfo.equals(client.getInstancePublishInfo(singleton))) {
            // 处理变动的新增数据
            client.addServiceInstance(singleton, instancePublishInfo);
            NotifyCenter.publishEvent(
                new ClientOperationEvent.ClientRegisterServiceEvent(singleton, client.getClientId()));
        }
    }
    for (Service each : client.getAllPublishedService()) {
        if (!syncedService.contains(each)) {
            // 处理变动的多余数据
            client.removeServiceInstance(each);
            NotifyCenter.publishEvent(
                new ClientOperationEvent.ClientDeregisterServiceEvent(each, client.getClientId()));
        }
    }
}

upgradeClient则是对同步的数据clientSyncData进行遍历,找到变动的数据,然后进行处理。当然处理的逻辑都是发布事件,将事件和逻辑区分开。由事件驱动。这里就不再分析各个事件,如果有感兴趣的小伙伴可以自行分析。

handleQueryData

private DistroDataResponse handleQueryData(DistroData distroData) {
    DistroDataResponse result = new DistroDataResponse();
    DistroKey distroKey = distroData.getDistroKey();
    // distroProtocol执行onQuery
    DistroData queryData = distroProtocol.onQuery(distroKey);
    result.setDistroData(queryData);
    return result;
}

看下distroProtocolonQuery

public DistroData onQuery(DistroKey distroKey) {
    String resourceType = distroKey.getResourceType();
    // 这里拿到的DistroClientDataProcessor
    DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(resourceType);
    if (null == distroDataStorage) {
        Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", resourceType);
        return new DistroData(distroKey, new byte[0]);
    }
    // DistroClientDataProcessor的getDistroData
    return distroDataStorage.getDistroData(distroKey);
}

DistroClientDataProcessor#getDistroData的方法

public DistroData getDistroData(DistroKey distroKey) {
    Client client = clientManager.getClient(distroKey.getResourceKey());
    if (null == client) {
        return null;
    }
    // 拿到client.generateSyncData()
    byte[] data = ApplicationUtils.getBean(Serializer.class).serialize(client.generateSyncData());
    return new DistroData(distroKey, data);
}

查询的方法比较简单,就是拿到client.generateSyncData(),序列化后组成DistroData返回。

总结

接受到Distro的相关请求后,通过DistroDataRequestHandler处理,而DistroDataRequestHandler又由DistroProtocol中的处理逻辑进行处理的。从而我们分析了上篇没有分析的DistroProtocol中的几个相关方法。

DistroProtocol中又主要由DistroClientDataProcessor进行处理。其主要还是将各个节点的属性的数据存储在DistroData中,然后由各个服务节点之间的缓存进行保存的。所以我们一直称之为Distro是一个临时性的同步协议,它的数据仅仅保存再运行态,当服务关闭了就不存在了。启动的时候拉取最新的数据进行保存,然后通过一系列Rpc的请求和内部各个事件,比如连接注册事件,断开事件等进行事件的驱动,一环扣一环,保证数据的一致性。

### 配置和集成 Nacos 2.5.1 使用 nacos-postgresql-plugin 为了使 Nacos 2.5.1 支持 PostgreSQL 数据库并成功集成 `nacos-postgresql-plugin`,可以按照以下方法完成配置: #### 1. 下载并编译插件 首先需要获取适用于当前版本的 `nacos-postgresql-plugin` 插件。如果官方未提供预构建二进制文件,则需自行下载源码并编译。 - **从 Gitee 或 GitHub 获取最新版 Nacos 源码**[^4]。 - 修改项目中的 `nacos-config-plugin` 和 `nacos-persistence` 模块,在其对应的 `pom.xml` 文件中添加 PostgreSQL 的 Maven 依赖项: ```xml <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.x.x</version> <!-- 替换为兼容版本 --> </dependency> ``` 此操作确保了项目能够识别 PostgreSQL JDBC 驱动程序[^2]。 #### 2. 构建自定义插件 JAR 包 执行以下命令来打包插件: ```bash mvn clean package -DskipTests ``` 完成后会在目标目录下找到生成的 `.jar` 文件(例如:`nacos-postgresql-plugin-<version>.jar`)。将其复制至运行环境下的容器路径中: ```bash for i in $(seq 0 2); do kubectl -n nacos cp /path/to/nacos-postgresql-plugin-<version>.jar \ nacos-${i}:/home/nacos/plugins/peer-finder/ done ``` 上述脚本会将插件分发给集群内的所有节点实例[^1]。 #### 3. 更新应用属性设置 编辑位于 `$NACOS_HOME/conf/application.properties` 中的内容,加入针对 PostgreSQL 的数据源声明语句: ```properties spring.datasource.platform=postgresql db.num=1 db.url.0=jdbc:postgresql://${DB_HOST}:${DB_PORT}/${DB_NAME}?tcpKeepAlive=true&reWriteBatchedInserts=true&ApplicationName=nacos_java db.user=${POSTGRES_USER} db.password=${POSTGRES_PASSWORD} db.pool.config.driverClassName=org.postgresql.Driver ``` 其中 `${}` 占位符应替换实际值或者通过外部变量注入实现动态化管理[^3]。 #### 4. 启动服务验证连通性 重启整个微服务平台以加载新的驱动器组件;随后测试是否能正常访问远程数据库资源。注意观察日志输出确认初始化过程无误。 --- ### 注意事项 尽管单体式开发模式具备较低复杂度的优势[^5],但在大规模分布式场景下建议采用更灵活的服务拆分策略降低相互间的影响范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值