Nacos2服务注册流程


前言

Nacos 作为服务注册中心,整体的一个工作示意图如下所示。

Nacos 服务端启动完毕后,Nacos客户端就可以进行注册。

最简单的方式,可以通过HTTP请求的方式来注册,如下所示。

java 复制代码
127.0.0.1:8848/nacos/v1/ns/instance?serviceName=learn-nacos&ip=127.0.0.1&port=8080

也可以通过模板代码来调用Nacos 提供的API来注册,如下所示。

java 复制代码
public class NamingExample {

    public static void main(String[] args) throws NacosException {
        Properties properties = new Properties();
        properties.setProperty("serverAddr", System.getProperty("serverAddr"));
        properties.setProperty("namespace", System.getProperty("namespace"));

        NamingService naming = NamingFactory.createNamingService(properties);

        naming.registerInstance("learn-nacos", "127.0.0.1", 8080, "TEST1");
    }
    
}

如果是在SpringCloud 中,Nacos 提供了一套更加优雅服务注册机制

本篇文章将对SpringCloud 整合Nacos 作为服务注册中心时,应用启动后的服务注册流程进行学习。

SpringCloud 版本:2021.0.4
Nacos 版本:2.0.4
SpringCloud Alibaba 版本:2021.0.4.0

正文

一. 示例工程搭建

首先简单搭建一个示例工程。

创建Maven 工程,POM文件如下。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
    </parent>

    <groupId>com.lee.learn.nacos</groupId>
    <artifactId>learn-nacos</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2021.0.4.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

</project>

添加一个Springboot启动类,如下所示。

java 复制代码
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

最后就是提供一份application.yml文件,如下所示。

yaml 复制代码
server:
  port: 8080

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
  application:
    name: learn-nacos

二. 客户端的服务注册流程分析

SpringCloud 整合Nacos 是基于自动装配类AutoServiceRegistrationAutoConfiguration位于 spring-cloud-commons 包中 ),该类依赖注入了AutoServiceRegistration ,这里实际类型是NacosAutoServiceRegistration位于 spring-cloud-starter-alibaba-nacos-discovery 包中 ),NacosAutoServiceRegistration的类图如下所示。

特别需要注意的就是, 上述类图中的 AbstractAutoServiceRegistration 位于 spring-cloud-commons 包中,但 NacosAutoServiceRegistration 位于 spring-cloud-starter-alibaba-nacos-discovery 包中

AbstractAutoServiceRegistration 同时也是一个事件监听器 ,监听的逻辑在其onApplicationEvent() 方法中,如下所示。

java 复制代码
public void onApplicationEvent(WebServerInitializedEvent event) {
    bind(event);
}

因此监听的是WebServerInitializedEvent 事件,该事件会在WEB 容器初始化完毕后发布,上述监听到WebServerInitializedEvent事件后的行为时序图如下所示。

那么这里其实就想说明一点,SpringCloud 定义了服务注册 这个功能的相关接口(AutoServiceRegistration )或抽象(AbstractAutoServiceRegistration ),然后Nacos 作为服务注册这个功能的提供方,就需要提供对应的实现(NacosAutoServiceRegistration ),那么在初始化服务注册这个功能的时候,肯定会走到NacosAutoServiceRegistration 的逻辑中,而通过NacosAutoServiceRegistration ,最终可以执行到NacosServiceRegistryregister() 方法,从这里开始,服务注册就完全交给到Nacos来实现了,如下所示。

java 复制代码
@Override
public void register(Registration registration) {

    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
        return;
    }

    // 获取NamingService
    NamingService namingService = namingService();
    // 获取服务Id,作为注册到Nacos上的服务名
    String serviceId = registration.getServiceId();
    // 获取配置的组,如果没有配置就使用默认的DEFAULT_GROUP
    String group = nacosDiscoveryProperties.getGroup();

    // 生成当前实例,包含当前实例的ip和port等信息
    Instance instance = getNacosInstanceFromRegistration(registration);

    try {
        // 使用NamingService进行服务注册
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                instance.getIp(), instance.getPort());
    }
    catch (Exception e) {
        if (nacosDiscoveryProperties.isFailFast()) {
            log.error("nacos registry, {} register failed...{},", serviceId,
                    registration.toString(), e);
            rethrowRuntimeException(e);
        }
        else {
            log.warn("Failfast is false. {} register failed...{},", serviceId,
                    registration.toString(), e);
        }
    }
}

上面方法的逻辑很清晰,就是拿到当前实例的元数据 后,调用NamingServiceregisterInstance() 方法来进行服务注册。

现在对SpringCloud 整合Nacos的客户端服务注册流程进行一个小结。

  1. SpringCloud 提供事件监听器监听 WebServerInitializedEvent 事件该事件会在 WEB 容器初始化完成后发布
  2. 监听到 WebServerInitializedEvent 事件后会执行服务注册逻辑
  3. 服务注册逻辑由 SpringCloud 提供抽象定义各服务注册组件方提供具体实现

上面,主要是分析SpringCloud 整合Nacos 后,为什么可以在应用启动完毕后,自动将当前实例注册到Nacos ,并且最终发现实际的注册还是需要调用到nacos-client 包提供的NacosNamingServiceregisterInstance() 方法。

下面,就将从NacosNamingServiceregisterInstance() 方法开始,对具体的注册细节进行学习。

NacosNamingServiceregisterInstance(String, String, Instance) 方法如下所示。

java 复制代码
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) 
        throws NacosException {
    // 对心跳时间进行校验
    NamingUtils.checkInstanceIsLegal(instance);
    // 使用客户端代理来注册服务
    clientProxy.registerService(serviceName, groupName, instance);
}

上面方法中的clientProxy 是一个代理对象,实际类型为NamingClientProxy ,继续跟进NamingClientProxyregisterService() 方法,如下所示。

java 复制代码
@Override
public void registerService(String serviceName, String groupName, Instance instance) 
        throws NacosException {
    // 先判断实例类型,然后根据不同的实例类型用不同的客户端向Nacos服务端发送注册请求
    getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}

private NamingClientProxy getExecuteClientProxy(Instance instance) {
    // 临时实例使用gRPC客户端,永久实例使用HTTP客户端
    return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}

上述方法很重要的一个逻辑就是会判断当前需要注册的实例是临时 的还是永久的。

  1. 临时实例 。实例信息暂存于注册中心的内存中,健康检测机制需要各实例向Nacos服务端主动发送心跳;
  2. 永久实例 。实例信息会存储到注册中心的内存和磁盘上,健康检测机制需要Nacos服务端主动去探活各实例。

默认 情况下,都是临时实例 ,所以下面主要分析grpcClientProxy 的逻辑,grpcClientProxy 的实际类型为NamingGrpcClientProxy ,其registerService() 方法如下所示。

java 复制代码
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
            instance);
    redoService.cacheInstanceForRedo(serviceName, groupName, instance);
    doRegisterService(serviceName, groupName, instance);
}

public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
    // 创建一个服务注册的gRPC请求
    InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
            NamingRemoteConstants.REGISTER_INSTANCE, instance);
    // 发送请求
    requestToServer(request, Response.class);
    redoService.instanceRegistered(serviceName, groupName);
}

private <T extends Response> T requestToServer(AbstractNamingRequest request, Class<T> responseClass)
        throws NacosException {
    try {
        request.putAllHeader(getSecurityHeaders());
        request.putAllHeader(getSpasHeaders(
                NamingUtils.getGroupedNameOptional(request.getServiceName(), request.getGroupName())));
        // 在这里使用rpcClient发送InstanceRequest
        Response response =
                requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
        if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) {
            throw new NacosException(response.getErrorCode(), response.getMessage());
        }
        if (responseClass.isAssignableFrom(response.getClass())) {
            return (T) response;
        }
        NAMING_LOGGER.error("Server return unexpected response '{}', expected response should be '{}'",
                response.getClass().getName(), responseClass.getName());
    } catch (Exception e) {
        throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e);
    }
    throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response");
}

上述方法会先创建出服务注册的gRPC 请求InstanceRequest ,然后使用rpcClient 发送InstanceRequestNacos服务端。

其中rpcClient 的实际类型是GrpcSdkClient ,是在NamingGrpcClientProxy 的构造方法中被创建并启动起来的,rpcClient 启动后会去基于服务端端口偏移1000 与服务端建立连接。具体的逻辑在RpcClientstart() 方法中,这里不再赘述。

下面再跟进一下RpcClientrequest(Request) 方法,如下所示。

java 复制代码
public Response request(Request request, long timeoutMills) throws NacosException {
    int retryTimes = 0;
    Response response;
    Exception exceptionThrow = null;
    long start = System.currentTimeMillis();
    while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
        boolean waitReconnect = false;
        try {
            if (this.currentConnection == null || !isRunning()) {
                waitReconnect = true;
                throw new NacosException(NacosException.CLIENT_DISCONNECT,
                        "Client not connected, current status:" + rpcClientStatus.get());
            }
            // 发送请求并且currentConnection类型是GrpcConnection
            response = this.currentConnection.request(request, timeoutMills);
            if (response == null) {
                throw new NacosException(SERVER_ERROR, "Unknown Exception.");
            }
            if (response instanceof ErrorResponse) {
                if (response.getErrorCode() == NacosException.UN_REGISTER) {
                    synchronized (this) {
                        waitReconnect = true;
                        if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
                            LoggerUtils.printIfErrorEnabled(LOGGER,
                                    "Connection is unregistered, switch server, connectionId = {}, request = {}",
                                    currentConnection.getConnectionId(), request.getClass().getSimpleName());
                            switchServerAsync();
                        }
                    }

                }
                throw new NacosException(response.getErrorCode(), response.getMessage());
            }
            lastActiveTimeStamp = System.currentTimeMillis();
            return response;

        } catch (Exception e) {
            if (waitReconnect) {
                try {
                    Thread.sleep(Math.min(100, timeoutMills / 3));
                } catch (Exception exception) {
                    
                }
            }

            LoggerUtils.printIfErrorEnabled(LOGGER, "Send request fail, request = {}, retryTimes = {}, errorMessage = {}",
                    request, retryTimes, e.getMessage());

            exceptionThrow = e;

        }
        retryTimes++;

    }

    if (rpcClientStatus.compareAndSet(RpcClientStatus.RUNNING, RpcClientStatus.UNHEALTHY)) {
        switchServerAsyncOnRequestFail();
    }

    if (exceptionThrow != null) {
        throw (exceptionThrow instanceof NacosException) ? (NacosException) exceptionThrow
                : new NacosException(SERVER_ERROR, exceptionThrow);
    } else {
        throw new NacosException(SERVER_ERROR, "Request fail, unknown Error");
    }
}

上述方法就是使用与Nacos 服务端已经建立好连接的GrpcConnection 来发送InstanceRequest

至此Nacos 作为服务注册中心时,Nacos客户端启动时如何向服务端发送服务注册请求的完整流程,就分析完毕。

三. 服务端的服务注册流程分析


在分析Nacos 服务端的服务注册流程 前,需要先说明一下Nacos 中的数据模型 和服务领域模型。数据模型如下所示。

也就是Namespace命名空间 )下划分Group ),Group 下是各个Service服务 )。在Nacos 服务端有一个com.alibaba.nacos.naming.core.v2.pojo.Service 对象,用于表示一个服务 ,并且就通过namespacegroupname服务名 )这三个字段唯一确定一个服务。

对于服务来说,也有其领域模型,如下图所示。

服务下其实就是一个又一个的实例,在Nacos 服务端使用com.alibaba.nacos.naming.core.v2.pojo.InstancePublishInfo 这个对象来表示已经发布的服务的实例。除此之外在Nacos2.x 服务端还需要关注一个对象,是ConnectionBasedClient ,用于表示基于TCP 会话的Nacos 客户端,里面会存储connectionId连接 Id ),存储这个Nacos 客户端发布的服务和实例信息,存储这个Nacos 客户端的订阅信息等,所以Nacos 服务端不是面向每个实例进行通信,而是面向每个Nacos客户端进行通信。


已知Nacos 客户端会通过gRPC 的方法向Nacos 服务端发送服务注册请求InstanceRequest ,那么相应的,在Nacos 服务端会有一个叫做InstanceRequestHandler 的处理器来处理InstanceRequest 请求。下面看一下InstanceRequestHandlerhandle() 方法的实现。

java 复制代码
@Override
@Secured(action = ActionTypes.WRITE, parser = NamingResourceParser.class)
public InstanceResponse handle(InstanceRequest request, RequestMeta meta) throws NacosException {
    // 基于命名空间,组名,服务名和临时节点标识来创建当前实例所属的服务Service
    Service service = Service
            .newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), true);
    // 判断是服务注册还是注销
    switch (request.getType()) {
        case NamingRemoteConstants.REGISTER_INSTANCE:
            // 服务注册逻辑
            return registerInstance(service, request, meta);
        case NamingRemoteConstants.DE_REGISTER_INSTANCE:
            // 服务注销逻辑
            return deregisterInstance(service, request, meta);
        default:
            throw new NacosException(NacosException.INVALID_PARAM,
                    String.format("Unsupported request type %s", request.getType()));
    }
}

当收到服务注册或者注销的请求后,会先根据请求实例的命名空间组名服务名临时节点标识 来创建当前实例所属的服务Service,然后再根据当前请求是注册还是注销,分别执行不同的逻辑。

这里仅分析服务注册 的逻辑,对应InstanceRequestHandlerregisterInstance() 方法,如下所示。

java 复制代码
private InstanceResponse registerInstance(Service service, InstanceRequest request, RequestMeta meta) {
    clientOperationService.registerInstance(service, request.getInstance(), meta.getConnectionId());
    return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE);
}

在上述方法中,会先获取表示当前注册的实例的Instance ,然后再获取这个实例的gRPC 连接Id ,最后调用clientOperationServiceregisterInstance() 方法进行服务注册。Instance 中会存储当前注册的实例的各种数据,例如实例的ipport等,其类图如下所示。

同时由于gRPC 是长连接,连接是一直保持着的,所以Nacos 服务端需要保存每个gRPC 的连接Id ,用于后续推送数据到每一个Nacos客户端。

处理服务注册的clientOperationService 的实际类型是EphemeralClientOperationServiceImpl ,用于处理临时实例的服务注册。下面跟进其registerInstance() 方法。

java 复制代码
@Override
public void registerInstance(Service service, Instance instance, String clientId) {
    // 从缓存中拿到当前注册实例所属服务的Service对象
    // 如果当前注册实例所属服务是第一次注册,那么就缓存传入的Service对象
    Service singleton = ServiceManager.getInstance().getSingleton(service);
    if (!singleton.isEphemeral()) {
        throw new NacosRuntimeException(NacosException.INVALID_PARAM,
                String.format("Current service %s is persistent service, can't register ephemeral instance.",
                        singleton.getGroupedServiceName()));
    }
    // 通过clientId拿到对应的Nacos客户端Client
    Client client = clientManager.getClient(clientId);
    // 校验一下Client,防止注册超时等情况
    if (!clientIsLegal(client, clientId)) {
        return;
    }
    // 基于客户端传递过来的Instance构建InstancePublishInfo
    // 服务端使用InstancePublishInfo来表示某一个实例
    InstancePublishInfo instanceInfo = getPublishInfo(instance);
    // 将Service和InstancePublishInfo添加到Client
    // 然后发布一个ClientChangedEvent事件,用于将当前注册的实例同步到集群
    client.addServiceInstance(singleton, instanceInfo);
    // 刷新Client最后一次更新的时间
    client.setLastUpdatedTime();
    // 发布ClientRegisterServiceEvent事件
    NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
    // 发布InstanceMetadataEvent事件
    NotifyCenter
            .publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}

EphemeralClientOperationServiceImplregisterInstance() 方法会做如下事情。

  1. 缓存当前注册实例所属服务的 Service 对象 。如果已经缓存过,则将已经缓存的Service 对象获取出来使用。需要注意,同一个服务的不同实例进行注册时,传递过来的Service 对象在堆上是不同的对象,但是在判断对象是否相等时,可能会判断为相等,这是因为Service 类中针对equals()hashCode() 方法进行了重写,只要namespace命名空间 ),group )和name服务名 )这三个字段一样,那么这两个不同的Service对象会被判定为相等;
  2. 根据 clientId 获取到 Client 对象Client 表示Nacos 客户端,里面缓存着gRPC 的连接Id ,这个Nacos 客户端发布的服务和实例信息,这个Nacos客户端的订阅信息等;
  3. 将服务 Service 和实例 InstancePublishInfo 缓存到 Client publishers publishers 类型为ConcurrentHashMap<Service, InstancePublishInfo> ,用于存储当前Client发布的服务以及对应的实例;
  4. 发布 ClientChangedEvent 事件 。该事件用于将当前注册的实例的信息同步给Nacos集群的其它服务端;
  5. 发布 ClientRegisterServiceEvent 事件。该事件用于完成服务注册;
  6. 发布 InstanceMetadataEvent 事件。该事件用于更新实例的元数据信息。

所以可以知道,Nacos 服务端处理服务注册,使用到了事件监听机制,NotifyCenter 就是事件的广播器 。这里重点关注ClientRegisterServiceEvent 事件,对应的事件处理器是ClientServiceIndexesManager ,其处理事件的onEvent() 方法如下所示。

java 复制代码
@Override
public void onEvent(Event event) {
    if (event instanceof ClientEvent.ClientDisconnectEvent) {
        handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
    } else if (event instanceof ClientOperationEvent) {
        handleClientOperation((ClientOperationEvent) event);
    }
}

private void handleClientOperation(ClientOperationEvent event) {
    Service service = event.getService();
    String clientId = event.getClientId();
    if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
        // 服务注册处理逻辑
        addPublisherIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
        removePublisherIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
        addSubscriberIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
        removeSubscriberIndexes(service, clientId);
    }
}

private void addPublisherIndexes(Service service, String clientId) {
    // 建立服务与服务下的实例发布者的映射关系
    // 一个服务对应一个ConcurrentHashSet集合
    // 集合中存储着发布了该服务实例的所有客户端Id
    publisherIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());
    publisherIndexes.get(service).add(clientId);
    // 发布ServiceChangedEvent事件
    // 将当前服务的变更推送给当前服务的订阅者
    NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}

监听到ClientRegisterServiceEvent 事件后,会最终调用到ClientServiceIndexesManageraddPublisherIndexes() 方法,在这个方法中,会首先将服务与发布了该服务实例的客户端Id 缓存到publisherIndexes 中,然后发布ServiceChangedEvent 事件用于将当前服务的变更消息推送给当前服务的订阅者。其中publisherIndexes 类型是ConcurrentMap <Service , Set <String>>,也就是一个服务对应多个客户端。

那么至此,Nacos服务端处理服务注册的流程分析完毕。整个流程会发布四个事件,小结如下。

  1. ClientChangedEvent 事件 。用于将当前注册的实例的信息同步给Nacos集群的其它服务端;
  2. ClientRegisterServiceEvent 事件。用于完成服务注册;
  3. InstanceMetadataEvent 事件。用于更新实例的元数据信息;
  4. ServiceChangedEvent 事件。用于向注册实例所属服务的订阅者推送服务变更消息。

与服务注册强相关的ClientRegisterServiceEvent 事件,会被ClientServiceIndexesManager 监听和处理,最后当前注册实例的服务和发布这个实例的客户端会建立起一对多的关系并缓存在内存中(缓存在 ClientServiceIndexesManager publisherIndexes 字段中)。

最后再提一下ClientServiceIndexesManager中的两个字段,如下所示。

java 复制代码
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();

private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();

publisherIndexes 表示一个服务有哪些发布者。subscriberIndexes表示一个服务有哪些订阅者。

关于服务发现服务订阅相关的逻辑,将会在后续的文章中进行学习。

总结

首先是SpringCloud 整合Nacos后,客户端启动时服务注册的一个时序图,如下所示。

SpringCloud 针对服务注册,专门提供了事件监听器来监听WebServerInitializedEvent 事件,监听到事件后,会调用到SpringCloud 定义的服务注册接口ServiceRegistry 的具体实现,这里的具体实现由Nacos 提供,后续就是Nacos的服务注册流程。

Nacos客户端的一个服务注册流程图如下所示。

如果是临时实例,则创建gRPC 的请求InstanceRequest ,然后通过gRPC客户端往服务端发送。

Nacos服务端,处理服务注册流程图如下所示。

Nacos 服务端在收到InstanceRequest 后,会创建出ServiceInstancePublishInfoClient ,分别代表本次注册的实例所属服务 ,本次注册的实例客户端 ,然后发布一系列事件。其中ClientRegisterServiceEven 事件是服务注册相关事件,该事件在Nacos服务端的处理流程图如下所示。

Nacos 服务端处理服务注册事件其实就是先缓存本次注册的服务Service 与本次发布服务实例的客户端的映射关系(一对多 ),然后发布一个ServiceChangedEvent事件,用于将当前服务的变更推送给当前服务的订阅者。

最后,就是Nacos服务端这边,有几个内存缓存,这里可以总结一下。

首先是ServiceManager,有两个缓存,分别如下。

java 复制代码
// 用于将每个服务Service缓存起来
// Map[服务, 服务]
private final ConcurrentHashMap<Service, Service> singletonRepository;

// 用于缓存命名空间namespace与服务Service的映射关系
// Map[命名空间, Set[服务]]
private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;

然后是客户端Client ,实际类型是ConnectionBasedClient ,缓存定义在其父类AbstractClient中,如下所示。

java 复制代码
// 用于缓存当前客户端发布的服务和实例的映射关系
// Map[服务, 实例]
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);

// 用于缓存当前客户端订阅的服务和订阅者的映射关系
// Map[服务, 订阅者]
protected final ConcurrentHashMap<Service, Subscriber> subscribers = new ConcurrentHashMap<>(16, 0.75f, 1);

最后是ClientServiceIndexesManager,有两个缓存,如下所示。

java 复制代码
// 用于缓存服务和发布这个服务实例的客户端的映射关系
// Map[服务, Set[客户端Id]]
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();

// 用于缓存服务和订阅这个服务的客户端的映射关系
// Map[服务, Set[客户端Id]]
private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();

知识分享不易,如果本文对你有帮助,烦请点赞收藏关注,感谢帅气漂亮的你。

相关推荐
五敷有你5 分钟前
Go:hello world
开发语言·后端·golang
拔剑纵狂歌24 分钟前
Golang异常处理机制
开发语言·后端·golang·go
缘友一世34 分钟前
Armbian 1panel面板工具箱中FTP服务无法正常启动的解决方法
linux·运维·后端·1panel
weixin_4193497943 分钟前
flask使用定时任务flask_apscheduler(APScheduler)
后端·python·flask
乐之者v1 小时前
Spring之 IoC、BeanFactory、ApplicationContext
java·后端·spring
一起学习计算机1 小时前
[附源码]基于Flask的演唱会购票系统
后端·python·flask
字节跳动数据平台1 小时前
深耕分析型数据库领域,火山引擎ByteHouse入围《2024爱分析数据库厂商全景报告
数据库·后端·云原生
优秀的颜1 小时前
RabbitMQ(集群相关部署)
开发语言·后端
clisk2 小时前
GO语言入门之准备
开发语言·后端·golang
techlead_krischang2 小时前
合合信息大模型“加速器”重磅上线
后端·go