Distro一致性

1.前言

Nacos中有CP和AP两种模式,而这两种模式在实现数据一致性方案上面是完全不一样的,对于CP模式而言,使用的是raft这种强一致性协议,对于AP模式而言,则是使用阿里自创的Distro协议,是面向临时实例设计的⼀种分布式协议,那么这里我们就来看看这个Distro协议在Nacos的实现。

2.Nacos写操作

2.1 整体流程

对于⼀个已经启动完成的 Distro 集群,在⼀次客户端发起写操作(以服务注册为例)的流程中,当注册临时实例实例的写请求打到某台 Nacos 服务器时,Distro 集群处理的流程图如下。

Distro一致性_第1张图片

  • 客户端随机选择向其中一台Nacos发起注册请求。

  • 前置的 Filter拦截请求,并根据请求中包含的 IP 和 port 信息计算其所属的 Distro 责任节点, 并将该请求转发到所属的 Distro 责任节点上。

  • 责任节点上的 Controller 将写请求进行解析。

  • Distro 协议定期执行 Sync 任务,将本机所负责的所有的实例信息同步到其他节点上。

2.2 平等选择

虽然上述提到客户端是随机选择向其中一台Nacos发起注册请求,但其中也存在着固定的随机逻辑。代码如下:

RpcClient 会发起 request 请求,用的是和 Nacos 建立 currentConnection 连接来发起调用

// 发起调用
response = this.currentConnection.request(request, timeoutMills);

这个 currentConnection 是客户端和 Nacos 集群中的某个节点建立的连接,我们找下它在哪里赋值的。

// 拿到 Nacos 节点信息
serverInfo = recommendServer.get() == null ? nextRpcServer() : recommendServer.get();
// 连接 Nacos 节点
connectToServer = connectToServer(serverInfo);
// 赋值 currentConnection
this.currentConnection = connectToServer;

这个 nextRpcServer() 方法里面会拿到一个随机的 Nacos 地址:

// 一个 int 随机数,范围 [0 ~ Nacos 个数)
currentIndex.set(new Random().nextInt(serverList.size()));
// index 自增 1
int index = currentIndex.incrementAndGet() % getServerList().size();
// 返回 Nacos 地址
return getServerList().get(index);
2.3 请求转发

由于请求随机选择了一台Nacos,但实际上每个Nacos仅负责处理其对应的实例请求,在这里就需要将请求转发至对应的责任节点上,主要是通过DistroFilter过滤器来实现。代码如下:

//根据ip+port,生成distroTag
String distroTag = distroTagGenerator.getResponsibleTag(req);
//获取distroTag的hashCode值,将hashCode与Nacos节点个数取余
int index = distroHash(responsibleTag) % servers.size();
//获取最终节点信息
return servers.get(index);
2.4 处理请求

这里我们以服务注册为案例,来讲述一下Nacos,接收到一个新请求时,如何做到写操作。Nacos 目前有两个版本,v1 和 v2,如果是 v1,则是 instanceController 来处理注册请求,否则用 instanceControllerV2。在这里我以v2版本为例。

//在InstanceControllerV2接收到注册请求发起注册
instanceServiceV2.registerInstance(namespaceId, serviceName, instance);


//根据命名空间和服务名称判断是否有已经存在的服务,如果没有则创建一个服务
createEmptyService(namespaceId, serviceName, instance.isEphemeral());
//获取实例
Service service = getService(namespaceId, serviceName);
//判断实例是否为空,如果为空,则说明之前的代码出现问题,则抛出异常
checkServiceIsNull(service, namespaceId, serviceName);
//增加实例
addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);

/**
* Add instance to service.
*
* @param namespaceId namespace
* @param serviceName service name
* @param ephemeral   whether instance is ephemeral
* @param ips         instances
* @throws NacosException nacos exception
*/
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        
    Service service = getService(namespaceId, serviceName);
        
    synchronized (service) {
         List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            
         Instances instances = new Instances();
         instances.setInstanceList(instanceList);
            
         consistencyService.put(key, instances);
    }
}

//将实例数据放置到内存缓存 concurrentHashMap
onPut(key, value);
        // If upgrade to 2.0.X, do not sync for v1.
        if (ApplicationUtils.getBean(UpgradeJudgement.class).isUseGrpcFeatures()) {
            return;
        }
//将任务放置到队列中,开启1s的延时任务,同步数据
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
DistroConfig.getInstance().getSyncDelayMillis());

3.Nacos读操作

Distro一致性_第2张图片

每个 Nacos 节点虽然只负责属于自己的客户端,但是每个节点都是包含有所有的客户端信息的,所以当客户端想要查询注册信息时,可以直接从请求的 Nacos 的节点拿到全量数据。

4.Distro简介

4.1 为什么需要Distro

我将上述读写流程总结为以下三点:

  • 每个节点平均处理写请求,有效避免消息堆积,再将自己负责的数据同步至其他节点。

  • 每个节点只负责部分数据,定时发送自己负责数据的校验值到其他节点来保持数据⼀致性。

  • 每个节点独立处理读请求,及时从本地发出响应。

通过上述讲解nacos读写数据的流程,因而Nacos各个节点之间,需要做到数据同步。Distro协议是为了注册中心临时节点之间数据同步而创造出的一致性协议。

4.2 Distro为何仅支持AP

首先在这里简单讲述下CAP理论

  • Consistency 一致性,对于客户端的每次读操作,要么读到的是最新的数据,要么读取失败。换句话说,一致性是站在分布式系统的角度,对访问本系统的客户端的一种承诺:要么我给您返回一个错误,要么我给你返回绝对一致的最新数据,不难看出,其强调的是数据正确。
  • Availability 可用性,任何客户端的请求都能得到响应数据,不会出现响应错误。换句话说,可用性是站在分布式系统的角度,对访问本系统的客户的另一种承诺:我一定会给您返回数据,不会给你返回错误,但不保证数据最新,强调的是不出错。
  • Partition tolerance 分区容错性,由于分布式系统通过网络进行通信,网络是不可靠的。当任意数量的消息丢失或延迟到达时,系统仍会继续提供服务,不会挂掉。换句话说,分区容忍性是站在分布式系统的角度,对访问本系统的客户端的再一种承诺:我会一直运行,不管我的内部出现何种数据同步问题,强调的是不挂掉。

系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择,分布式系统必须具有分区容错性,一致性与可用性只能选择其一,如果选择一致性,必须在某节点写入时,数据同步到其他节点完成后才可以响应下一个请求,这段时间内服务是不可用的;反之选择可用性,则一致性无法保证,这就是CAP理论。

在前言中我们提到,Distro是一个AP模式,为何注册中心更注重AP而非CP,我们做以下分析:

数据一致性需求分析

注册中心最本质的功能可以看成是一个 Query 函数 ,以 service-name 为查询参数,service-name 对应的服务的可用的 endpoints (ip:port)列表为返回值,先来看看关键数据 endpoints (ip:port) 不一致性带来的影响,即 CAP 中的 C 不满足带来的后果 :

Distro一致性_第3张图片

如上图所示,如果一个 svcB 部署了 10 个节点 (副本 /Replica),如果对于同一个服务名 svcB, 调用者 svcA 的 2 个节点的 2 次查询返回了不一致的数据,例如: S1 = { ip1,ip2,ip3…,ip9 }, S2 = { ip2,ip3,…ip10 }, 那么这次不一致带来的影响是什么?相信你一定已经看出来了,svcB 的各个节点流量会有一点不均衡。

ip1 和 ip10 相对其它 8 个节点{ip2…ip9},请求流量小了一点,但很明显,在分布式系统中,即使是对等部署的服务,因为请求到达的时间,硬件的状态,操作系统的调度,虚拟机的 GC 等,任何一个时间点,这些对等部署的节点状态也不可能完全一致,而流量不一致的情况下,只要注册中心在 SLA 承诺的时间内(例如 1s 内)将数据收敛到一致状态(即满足最终一致),流量将很快趋于统计学意义上的一致,所以注册中心以最终一致的模型设计在生产实践中完全可以接受。

  • 分区容忍及可用性需求分析

接下来我们看一下网络分区(Network Partition)情况下注册中心不可用对服务调用产生的影响,即 CAP 中的 A 不满足时带来的影响。

考虑一个典型的三机房容灾 5 节点部署结构 (即 2-2-1 结构),如下图:

Distro一致性_第4张图片

当机房 3 出现网络分区 (Network Partitioned) 的时候,即机房 3 在网络上成了孤岛,我们知道虽然整体服务是可用的,如果支持强一致性的情况下,这时候节点 ZK5 是不可写的,因为联系不上其他节点,无法做到强一致性的数据复制。也就是说,这时候机房 3 的应用服务 svcB 是不可以新部署,重新启动,扩容或者缩容的,但是站在网络和服务调用的角度看,机房 3 的 svcA 虽然无法调用机房 1 和机房 2 的 svcB, 但是与机房 3 的 svcB 之间的网络明明是 OK 的啊,为什么不让我调用本机房的服务?

现在因为注册中心自身为了保脑裂 § 下的数据一致性(C)而放弃了可用性,导致了同机房的服务之间出现了无法调用,这在大部分情况下是无法接受的。同时我们再考虑一下这种情况下的数据不一致性,如果机房 1,2,3 之间都成了孤岛,那么如果每个机房的 svcA 都只拿到本机房的 svcB 的 ip 列表,也即在各机房 svcB 的 ip 列表数据完全不一致,影响是什么?其实没啥大影响。通过以上我们的阐述可以看到,在 CAP 的权衡中,注册中心的可用性比数据强一致性更宝贵,所以整体设计更应该偏向 AP,而非 CP,数据不一致在可接受范围,而 P 下舍弃 A 却完全违反了注册中心不能因为自身的任何原因破坏服务本身的可连通性的原则。

但为什么Nacos即支持AP又支持CP呢,在前言中提到,Distro是为临时节点的一致性协议,因为在Nacos中节点区分临时节点和永久节点,临时节点的数据无需存储在磁盘和数据库中,而是存储在缓存中;同时 Nacos 的服务注册发现设计,临时节点采取了客户端主动向注册中心上报心跳,心跳可自动完成服务数据补偿的机制。如果数据丢失的话,是可以通过该机制快速弥补数据丢失,做到最终一致性。而对于 Nacos 服务发现注册中的永久实例,因为所有的数据都是直接使用调用 Nacos 服务端直接创建,因此需要由 Nacos 保障数据在各个节点之间的强⼀致性,故而针对此类型的服务 数据,选择了强⼀致性共识算法来保障数据的⼀致性。

4.3 Distro协议源码解析

DistroProtocol作为Distro协议的入口,可以看到它使用了Spring的@Componet注解,意味着它将被Spring容器管理,执行到构造方法的时候将会启动Distro协议的工作。在构造方法中除了赋值以外,其中最关键则是startDistroTask()方法,该方法启动distro数据同步的后台线程,首先判断Nacos寻址方式,如果是单机模式不进行数据同步操作;集群模式下会调用两个方法 startVerifyTask()和startLoadTask()。startVerifyTask()方法启动定时任务用于集群节点之间数据校验,用于集群启动或节点加入新集群同步数据;startLoadTask()为数据同步任务,用于在运行时,各节点之间确保数据一致性的校验。

@Component
public class DistroProtocol {
    //nacos集群节点管理组件
    private final ServerMemberManager memberManager;
    //distro协议核心组件管理容器
    private final DistroComponentHolder distroComponentHolder;
    //distro协议任务执行引擎
    private final DistroTaskEngineHolder distroTaskEngineHolder;
    private volatile boolean isInitialized = false;
    
	public DistroProtocol(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder,
            DistroTaskEngineHolder distroTaskEngineHolder) {
        this.memberManager = memberManager;
        this.distroComponentHolder = distroComponentHolder;
        this.distroTaskEngineHolder = distroTaskEngineHolder;
        //启动distro后台线程
        startDistroTask();
    }
    
    private void startDistroTask() {
        if (EnvUtil.getStandaloneMode()) {
            isInitialized = true;
            return;
        }
        //启动Verify校验任务线程
        startVerifyTask();
        //启动数据同步任务线程
        startLoadTask();
    }
    
    // ....
}
4.3.1 数据校验源码分析
//startVerifyTask方法每隔5s将自己本地的数据与其余Nacos节点数据进行一个校验    
private void startVerifyTask() {
        GlobalExecutor.schedulePartitionDataTimedSync(new DistroVerifyTimedTask(memberManager, distroComponentHolder,
                        distroTaskEngineHolder.getExecuteWorkersManager()),
                DistroConfig.getInstance().getVerifyIntervalMillis());
    }
/**
 * 定时验证延迟任务,此任务在启动时延迟5秒,间隔5秒执行。
 * 主要用于为每个节点创建一个数据验证的执行任务DistroVerifyExecuteTask。它的数据处理维度是Member。
 * @author xiweng.yy
 */
public class DistroVerifyTimedTask implements Runnable {
    
    private final ServerMemberManager serverMemberManager;
    
    private final DistroComponentHolder distroComponentHolder;
    
    private final DistroExecuteTaskExecuteEngine executeTaskExecuteEngine;
    
    public DistroVerifyTimedTask(ServerMemberManager serverMemberManager, DistroComponentHolder distroComponentHolder,
            DistroExecuteTaskExecuteEngine executeTaskExecuteEngine) {
        this.serverMemberManager = serverMemberManager;
        this.distroComponentHolder = distroComponentHolder;
        this.executeTaskExecuteEngine = executeTaskExecuteEngine;
    }
    
    @Override
    public void run() {
        try {
            //获取除去自己本身以外的所有节点
            List<Member> targetServer = serverMemberManager.allMembersWithoutSelf();
            if (Loggers.DISTRO.isDebugEnabled()) {
                Loggers.DISTRO.debug("server list is: {}", targetServer);
            }
            //遍历节点,获取到校验数据
            for (String each : distroComponentHolder.getDataStorageTypes()) {
                verifyForDataStorage(each, targetServer);
            }
        } catch (Exception e) {
            Loggers.DISTRO.error("[DISTRO-FAILED] verify task failed.", e);
        }
    }
    
    private void verifyForDataStorage(String type, List<Member> targetServer) {
        // 获取数据类型
        DistroDataStorage dataStorage = distroComponentHolder.findDataStorage(type);
        //如果未进行过全量同步则不进行数据校验,退出
        if (!dataStorage.isFinishInitial()) {
            Loggers.DISTRO.warn("data storage {} has not finished initial step, do not send verify data",
                    dataStorage.getClass().getSimpleName());
            return;
        }
        //获取本节点所需要验证的数据
        List<DistroData> verifyData = dataStorage.getVerifyData();
        if (null == verifyData || verifyData.isEmpty()) {
            return;
        }
        // 对每个节点开启一个异步的线程来执行
        for (Member member : targetServer) {
            DistroTransportAgent agent = distroComponentHolder.findTransportAgent(type);
            if (null == agent) {
                continue;
            }
            //往Distro数据验证任务执行器,添加一个校验任务
            executeTaskExecuteEngine.addTask(member.getAddress() + type,
                    new DistroVerifyExecuteTask(agent, verifyData, member.getAddress(), type));
        }
    }
}
/**
 * Distro数据验证任务执行器,用于向其他节点发送当前节点负责的Client状态报告,通知对方此Client正常服务。它的数据处理维度是DistroData。
 *
 * @author xiweng.yy
 */
public class DistroVerifyExecuteTask extends AbstractExecuteTask {    
    // ....
       
	@Override
    public void run() {
        for (DistroData each : verifyData) {
            try {
                if (transportAgent.supportCallbackTransport()) {
                    //Nacos 2.0采用rpc调用,支持回调
                    doSyncVerifyDataWithCallback(each);
                } else {
                    //Nacos 1.0采用http请求,不支持回调
                    doSyncVerifyData(each);
                }
            } catch (Exception e) {
                Loggers.DISTRO
                        .error("[DISTRO-FAILED] verify data for type {} to {} failed.", resourceType, targetServer, e);
            }
        }
    }
    
    private void doSyncVerifyDataWithCallback(DistroData data) {
        //通过nacos通信代理,把校验数据发送到指定目标节点
        transportAgent.syncVerifyData(data, targetServer, new DistroVerifyCallback());
    }
    
    // ....
}    
//集群其他节点接收到校验数据
@Component
public class DistroDataRequestHandler extends RequestHandler<DistroDataRequest, DistroDataResponse> {
    
    private final DistroProtocol distroProtocol;
    
    public DistroDataRequestHandler(DistroProtocol distroProtocol) {
        this.distroProtocol = distroProtocol;
    }
    
    @Override
    public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
        try {
            switch (request.getDataOperation()) {
                case VERIFY:
                    //接收到请求发起校验
                    return handleVerify(request.getDistroData(), meta);
                //.....
            }
        } 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;
        }
    }
    
    private DistroDataResponse handleVerify(DistroData distroData, RequestMeta meta) {
        DistroDataResponse result = new DistroDataResponse();
        if (!distroProtocol.onVerify(distroData, meta.getClientIp())) {
            result.setErrorInfo(ResponseCode.FAIL.getCode(), "[DISTRO-FAILED] distro data verify failed");
        }
        return result;
    }
    //.....
}
@Component
public class DistroProtocol {
    //····
    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();
        DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
        if (null == dataProcessor) {
            Loggers.DISTRO.warn("[DISTRO] Can't find verify data process for received data {}", resourceType);
            return false;
        }
        //发起校验
        return dataProcessor.processVerifyData(distroData, sourceAddress);
    }
}
//如果比对成功,则会更新心跳时间,如果比对失败接收回调发起全量数据同步
public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {
   
    @Override
    public boolean processVerifyData(DistroData distroData, String sourceAddress) {
        DistroClientVerifyInfo verifyData = ApplicationUtils.getBean(Serializer.class)
                .deserialize(distroData.getContent(), DistroClientVerifyInfo.class);
        //更新心跳时间
        if (clientManager.verifyClient(verifyData.getClientId())) {
            return true;
        }
        Loggers.DISTRO.info("client {} is invalid, get new client from {}", verifyData.getClientId(), sourceAddress);
        return false;
    }
}
4.3.2 数据同步源码分析
private void startLoadTask() {
        DistroCallback loadCallback = new DistroCallback() {
            @Override
            public void onSuccess() {
                isInitialized = true;
            }
            
            @Override
            public void onFailed(Throwable throwable) {
                isInitialized = false;
            }
        };
    	//往数据同步组件中添加任务
        GlobalExecutor.submitLoadDataTask(
                new DistroLoadDataTask(memberManager, distroComponentHolder, DistroConfig.getInstance(), loadCallback));
}
public class DistroLoadDataTask implements Runnable {
    //···
    
    public DistroLoadDataTask(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder,
            DistroConfig distroConfig, DistroCallback loadCallback) {
        this.memberManager = memberManager;
        this.distroComponentHolder = distroComponentHolder;
        this.distroConfig = distroConfig;
        this.loadCallback = loadCallback;
        loadCompletedMap = new HashMap<>(1);
    }
    
    @Override
    public void run() {
        try {
            //加载
            load();
            if (!checkCompleted()) {
                GlobalExecutor.submitLoadDataTask(this, distroConfig.getLoadDataRetryDelayMillis());
            } else {
                loadCallback.onSuccess();
                Loggers.DISTRO.info("[DISTRO-INIT] load snapshot data success");
            }
        } catch (Exception e) {
            loadCallback.onFailed(e);
            Loggers.DISTRO.error("[DISTRO-INIT] load snapshot data failed. ", e);
        }
    }
    
    private void load() throws Exception {
        //如果其余节点为空,不处理,休眠1秒
        while (memberManager.allMembersWithoutSelf().isEmpty()) {
            Loggers.DISTRO.info("[DISTRO-INIT] waiting server list init...");
            TimeUnit.SECONDS.sleep(1);
        }
        //如果数据类型为空,说明distroComponentHolder的组件注册器还未初始化完毕,不处理,休眠1秒
        while (distroComponentHolder.getDataStorageTypes().isEmpty()) {
            Loggers.DISTRO.info("[DISTRO-INIT] waiting distro data storage register...");
            TimeUnit.SECONDS.sleep(1);
        }
        //循环数据类型获取快照
        for (String each : distroComponentHolder.getDataStorageTypes()) {
            if (!loadCompletedMap.containsKey(each) || !loadCompletedMap.get(each)) {
                loadCompletedMap.put(each, loadAllDataSnapshotFromRemote(each));
            }
        }
    }
    
    private boolean loadAllDataSnapshotFromRemote(String resourceType) {
        DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType);
        DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
        if (null == transportAgent || null == dataProcessor) {
            Loggers.DISTRO.warn("[DISTRO-INIT] Can't find component for type {}, transportAgent: {}, dataProcessor: {}",
                    resourceType, transportAgent, dataProcessor);
            return false;
        }
        //循环所有除自己所有节点
        for (Member each : memberManager.allMembersWithoutSelf()) {
            long startTime = System.currentTimeMillis();
            try {
                Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {}", resourceType, each.getAddress());
                //根据地址获取对应节点快照
                DistroData distroData = transportAgent.getDatumSnapshot(each.getAddress());
                Loggers.DISTRO.info("[DISTRO-INIT] it took {} ms to load snapshot {} from {} and snapshot size is {}.",
                        System.currentTimeMillis() - startTime, resourceType, each.getAddress(),
                        getDistroDataLength(distroData));
                //处理快照
                boolean result = dataProcessor.processSnapshot(distroData);
                Loggers.DISTRO
                        .info("[DISTRO-INIT] load snapshot {} from {} result: {}", resourceType, each.getAddress(),
                                result);
                if (result) {
                    distroComponentHolder.findDataStorage(resourceType).finishInitial();
                    return true;
                }
            } catch (Exception e) {
                Loggers.DISTRO.error("[DISTRO-INIT] load snapshot {} from {} failed.", resourceType, each.getAddress(), e);
            }
        }
        return false;
    }
    
	//...
}
public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {
	//...
    private void handlerClientSyncData(ClientSyncData clientSyncData) {
        Loggers.DISTRO.info("[Client-Add] Received distro client sync data {}", clientSyncData.getClientId());
        clientManager.syncClientConnected(clientSyncData.getClientId(), clientSyncData.getAttributes());
        Client client = clientManager.getClient(clientSyncData.getClientId());
        //真正处理数据更新方法
        upgradeClient(client, clientSyncData);
    }
    
    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 = 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()));
            }
        }
    }
}
4.3.3 服务注册增量同步

数据校验和数据同步是在Nacos节点启动时默认就会启用的任务,但注册中心会不断有新服务注册进来,前面在讲解Nacos写操作的时候,我们以服务注册为案例,提到在完成服务注册之后,发送了延时任务,而这个延时任务最终就是为了实现新注册进来的服务,Nacos各个节点之间需要做增量同步,那么我们接下来就来分析一下:

public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {    
	@Override
    public List<Class<? extends Event>> subscribeTypes() {
        //监听变更事件
        List<Class<? extends Event>> result = new LinkedList<>();
        result.add(ClientEvent.ClientChangedEvent.class);
        result.add(ClientEvent.ClientDisconnectEvent.class);
        result.add(ClientEvent.ClientVerifyFailedEvent.class);
        return result;
    }
    
    @Override
    public void onEvent(Event event) {
        if (EnvUtil.getStandaloneMode()) {
            return;
        }
        if (!upgradeJudgement.isUseGrpcFeatures()) {
            return;
        }
        if (event instanceof ClientEvent.ClientVerifyFailedEvent) {
            syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);
        } else {
            //除本节点外的所有节点进行数据同步
            syncToAllServer((ClientEvent) event);
        }
    }
}    
public boolean syncData(DistroData data, String targetServer) {
    if (isNoExistTarget(targetServer)) {
        return true;
    }
    DistroDataRequest request = new DistroDataRequest(data, data.getType());
    Member member = memberManager.find(targetServer);
    if (checkTargetServerStatusUnhealthy(member)) {
        Loggers.DISTRO.warn("[DISTRO] Cancel distro sync caused by target server {} unhealthy", targetServer);
        return false;
    }
    try {
        // 使用Rpc代理对象发送同步rpc请求
        Response response = clusterRpcClientProxy.sendRequest(member, request);
        return checkResponse(response);
    } catch (NacosException e) {
        Loggers.DISTRO.error("[DISTRO-FAILED] Sync distro data failed! ", e);
    }
    return false;
}
@Component
public class DistroDataRequestHandler extends RequestHandler<DistroDataRequest, DistroDataResponse> {
    
    //...
    
	@Override
    public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
        try {
            switch (request.getDataOperation()) {
                case ADD:
                case CHANGE:
                case DELETE:
                    //发起数据同步
                    return handleSyncData(request.getDistroData());
            }
        } 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;
        }
    }
    
    private DistroDataResponse handleSyncData(DistroData distroData) {
        DistroDataResponse result = new DistroDataResponse();
        if (!distroProtocol.onReceive(distroData)) {
            result.setErrorCode(ResponseCode.FAIL.getCode());
            result.setMessage("[DISTRO-FAILED] distro data handle failed");
        }
        return result;
    }
    
    //...
}
    /**
     * 在DistroProtocol类中的onReceive方法中最终还是调用dataProcessor.processData(distroData);
     * 
     */
    public boolean onReceive(DistroData distroData) {
        Loggers.DISTRO.info("[DISTRO] Receive distro data type: {}, key: {}", distroData.getType(),
                distroData.getDistroKey());
        String resourceType = distroData.getDistroKey().getResourceType();
        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);
    }
    @Override
    public boolean processData(DistroData distroData) {
        switch (distroData.getType()) {
            case ADD:
            case CHANGE:
                //更新事件调用更新方法,之后所走的流程和全量同步一致
                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;
        }
    }

5.总结

Distro协议是Nacos社区自研的⼀种AP分布式协议,是面向临时实例设计的⼀种分布式协议,其保证了在某些Nacos节点宕机后,整个临时实例处理系统依旧可以正常工作。其数据存储在缓存中,并且会在启动时进行全量数据同步,并定期进行数据校验,当新服务加入也会启动数据同步,从而确保各个节点之间的数据一致性。从而实现读取数据时,节点从本地读取数据即可返回,大大提升效率。

参考资料

https://developer.aliyun.com/ebook/36?spm=a2c6h.14164896.0.0.7c2d435bVO3OU7

https://www.cnblogs.com/lukama/p/14984858.html

https://www.cnblogs.com/lukama/p/14918667.html

你可能感兴趣的:(微服务)