Nacos 注册流程

Nacos 注册流程

客户端

在客户端引入了 spring-cloud-starter-alibaba-nacos-discovery 依赖,发现该 Jar 包使用 SPI 机制引入了很多类,其中有个 NacosServiceRegistryAutoConfiguration,该类主要用途是:主要用于配置和初始化 Nacos 服务注册相关的组件,让应用在启动时自动注册到 Nacos,并维持与 Nacos 的心跳连接。

请添加图片描述

NacosServiceRegistryAutoConfiguration 引入了三个 Bean,分别是

  • NacosServiceRegistry : 提供服务注册和注销的具体实现。
  • NacosRegistration :封装了当前应用的注册信息。
  • NacosAutoServiceRegistration:实现自动服务注册逻辑。

主要看 NacosAutoServiceRegistration这个类,是实现自动注册的逻辑的。根据类图发现,继承了ApplicationEvent,该类是 Spring 事件机制的核心类,可以通过继承该类,在 Spring 的生命周期中,触发一些事件。

在这里插入图片描述

继承了 ApplicationEvent 的类,会在事件发布时,回调 onApplicationEvent 方法

// 回调方法
public void onApplicationEvent(WebServerInitializedEvent event) {
    bind(event);
}


public void bind(WebServerInitializedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (context instanceof ConfigurableWebServerApplicationContext) {
        if ("management".equals(((ConfigurableWebServerApplicationContext) context)
                .getServerNamespace())) {
            return;
        }
    }
    this.port.compareAndSet(0, event.getWebServer().getPort());
    this.start();
}

public void start() {
    if (!isEnabled()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Discovery Lifecycle disabled. Not starting");
        }
        return;
    }

    if (!this.running.get()) {
        this.context.publishEvent(
                new InstancePreRegisteredEvent(this, getRegistration()));
        // 这个地方会调用子类的 register方法
        register();
        if (shouldRegisterManagement()) {
            registerManagement();
        }
        this.context.publishEvent(
                new InstanceRegisteredEvent<>(this, getConfiguration()));
        this.running.compareAndSet(false, true);
    }
}

// 子类NacosAutoServiceRegistration的register方法
protected void register() {
    if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
        log.debug("Registration disabled.");
        return;
    }
    if (this.registration.getPort() < 0) {
        this.registration.setPort(getPort().get());
    }
    // 调用父类的register方法
    super.register();
}

// 父类的register方法
protected void register() {
    // 调用了serviceRegistry的register方法
    this.serviceRegistry.register(getRegistration());
}

serviceRegistry 的来源是 Spring 在 AbstractAutoServiceRegistration 初始化时,通过自动注入传入的;

// 在spring 初始化时,自动注入的
protected AbstractAutoServiceRegistration(ServiceRegistry<R> serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
}

看下 serviceRegistry的register 方法

public void register(Registration registration) {

    // 校验是否合法
    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
        return;
    }
	
    // 创建名称空间,若存在直接返回,若不存在,通过反射创建该类
    NamingService namingService = namingService();
    // 获取servicceId,其实就是服务名称
    String serviceId = registration.getServiceId();
    // 获取分组,默认为 DEFAULT_GROUP
    String group = nacosDiscoveryProperties.getGroup();
	// 获取自身实例
    Instance instance = getNacosInstanceFromRegistration(registration);

    try {
        // 注册方法
        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);
        }
    }
}

registerInstance 注册方法实现

    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        // 存活时间的相关校验
        // 心跳超时必须大于心跳间隔
		// ip删除超时必须大于心跳间隔
        NamingUtils.checkInstanceIsLegal(instance);
        // 使用代理去注册服务
        // clientProxy 是在构造方法中的init() 方法中,创建了NamingClientProxyDelegate对象
        clientProxy.registerService(serviceName, groupName, instance);
    }

查看 NamingClientProxyDelegateregisterInstance 方法

// NamingClientProxyDelegate 的 注册实例方法
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    // 在这调用的 getExecuteClientProxy,获取NamingClientProxy,然后去调用registerService
    getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}


private NamingClientProxy getExecuteClientProxy(Instance instance) {
    // 根据是否是临时实例,去判断使用grpc方式注册还是http方式注册
    // 默认情况下都是临时实例
    return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}  

临时实例与持久实例的区别

特性临时实例持久实例
生命周期短,依赖心跳长,需手动注销
自动清除
数据持久化不持久化,仅存于内存中持久化到磁盘
使用场景弹性伸缩、边缘节点等短生命周期服务稳定运行的核心服务

由于使用的是临时实例,继续深入grpcClientProxy

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
            instance);
    // 将实例信息缓存一下,可以看到缓存到了ConcurrentMap<String, InstanceRedoData>
    redoService.cacheInstanceForRedo(serviceName, groupName, instance);
    // 注册方法
    doRegisterService(serviceName, groupName, instance);
}

public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
    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 {
    // 只放了核心操作,异常捕获没有写
    
    request.putAllHeader(
                    getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName()));
    // 判断requestTimeout,发起请求,通过debug发现,此处的requestTimeout为-1
    Response response =
            requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout);
}

然后继续调用 rpcClient.request(request)

public Response request(Request request, long timeoutMills) throws NacosException {
    ...
    int retryTimes = 0;
    // RETRY_TIMES = 3, 最多重试三次
    while (retryTimes < RETRY_TIMES && System.currentTimeMillis() < timeoutMills + start) {
      	...
        response = this.currentConnection.request(request, timeoutMills);
        ...
		retryTimes++;
    }
    ...
}

// 继续查看 this.currentConnection.request(request, timeoutMills) 方法
public Response request(Request request, long timeouts) throws NacosException {
    Payload grpcRequest = GrpcUtils.convert(request);
    // 发起请求,这里可以发现grpcFutureServiceStub与正常的类不太一样,因为这个类是通过protoc文件编译生成的类,这里调用request,就是向服务端发起了注册请求
    // 通过protoc文件定义接口,然后通过编译,将其转换为特定语言的代码,以便开发者能够快速、可靠地进行序列化、反序列化和 RPC 调用,使数据的跨语言和跨平台通信变得更加便捷和高效。
    ListenableFuture<Payload> requestFuture = grpcFutureServiceStub.request(grpcRequest);
    Payload grpcResponse;
    try {
        grpcResponse = requestFuture.get(timeouts, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
        throw new NacosException(NacosException.SERVER_ERROR, e);
    }

    return (Response) GrpcUtils.parse(grpcResponse);
}

自此,nacos注册的客户端流程结束。

整体流程如下

在这里插入图片描述

服务端

服务端从启动 grpc 服务开始,在 com.alibaba.nacos.core.remote.BaseRpcServer 类中,存在 start() 方法,该方法是启动 gprc 服务。

@PostConstruct
public void start() throws Exception {
    ...
    startServer();
    ...
}

// 构建grpc服务器,设置参数
public void startServer() throws Exception {
    ...
    // 添加服务和拦截器
    addServices(handlerRegistry, getSeverInterceptors().toArray(new ServerInterceptor[0]));
	....
}

// 注册grpc服务的方法处理器,单向调用注册、双向流注册及相应的处理方法
private void addServices(MutableHandlerRegistry handlerRegistry, ServerInterceptor... serverInterceptor) {
    ...
    final ServerCallHandler<Payload, Payload> payloadHandler = ServerCalls.asyncUnaryCall(
                (request, responseObserver) -> {
                    // 收到单向流请求时,使用该方法进行处理
                    // 服务状态变动、心跳检测等,都会走这个方法
                    handleCommonRequest(request, responseObserver);
                });
    ...
}

// 处理来自客户端的grpc请求
protected void handleCommonRequest(Payload grpcRequest, StreamObserver<Payload> responseObserver) {
    // 校验请求源是否合法
    if (!invokeSourceAllowCheck(grpcRequest)) {
        Payload payloadResponse = GrpcUtils.convert(ErrorResponse.build(NacosException.BAD_GATEWAY,
                String.format(" invoke %s from %s is forbidden", grpcRequest.getMetadata().getType(),
                        this.getSource())));
        responseObserver.onNext(payloadResponse);

        responseObserver.onCompleted();
        MetricsMonitor.recordGrpcRequestEvent(grpcRequest.getMetadata().getType(), false,
                NacosException.BAD_GATEWAY, null, null, 0);
    } else {
        // 若请求源合法,进行处理
        grpcCommonRequestAcceptor.request(grpcRequest, responseObserver);
    }
}

然后看下请求处理的 request 方法

public void request(Payload grpcRequest, StreamObserver<Payload> responseObserver) {
    // 各种校验
    ...
    // 根据请求类型,获取请求处理器,若获取不到,则抛出异常
    RequestHandler requestHandler = requestHandlerRegistry.getByRequestType(type);
    ...
    // 请求处理
    Response response = requestHandler.handleRequest(request, requestMeta);
	...
}

先看下请求处理器获取逻辑
Map<String, RequestHandler> registryHandlers = new HashMap<>();
public RequestHandler getByRequestType(String requestType) {
    return registryHandlers.get(requestType);
}

可以看到请求处理器时从一个Map中获取,定位下Map的初始化逻辑
// 进行了代码简化
// spring初始化完成后,通过事件发布,回调onApplicationEvent
public void onApplicationEvent(ContextRefreshedEvent event) {
    // 获取容器中所有类型为RequestHandler的bean,也就是获取容器中所有的处理器
    Map<String, RequestHandler> beansOfType = event.getApplicationContext().getBeansOfType(lei'xin.class);
    Collection<RequestHandler> values = beansOfType.values();
    
    for (RequestHandler requestHandler : values) {
        // 循环所有的bean,并将其放到map中
        registryHandlers.putIfAbsent(tClass.getSimpleName(), requestHandler);
    }
}

                                                                                           
继续回去看请求处理的流程,也就是 handleRequest() 方法
public Response handleRequest(T request, RequestMeta meta) throws NacosException {
    // 遍历 requestFilters 中的所有过滤器,对每个过滤器调用 filter 方法
    // 过滤器的注册流程
    // 1. 在父类中创建一个List,使用@PostConstruct声明init()方法,并在方法中往List中add自身(this)
    // 2. 所有继承了该父类的子类,在spring的实例化过程中,都会执行父类的init()
    // 3. 最终所有继承了该父类的子类,都会将自身注册到父类创建的List中
    for (AbstractRequestFilter filter : requestFilters.filters) {
        try {
            Response filterResult = filter.filter(request, meta, this.getClass());
            if (filterResult != null && !filterResult.isSuccess()) {
                return filterResult;
            }
        } catch (Throwable throwable) {
            Loggers.REMOTE.error("filter error", throwable);
        }
    }
    // 请求处理
    return handle(request, meta);
}

继续看请求处理方法 handle,该类有很多实现类,通过debugger发现,服务注册调用的是 InstanceRequestHandler 的handle
在这里插入图片描述

InstanceRequestHandler 的处理方法中,判断是服务注册还是服务下线,看下服务注册流程

public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);

    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()));
    }
    Client client = clientManager.getClient(clientId);
    checkClientIsLegal(client, clientId);
    InstancePublishInfo instanceInfo = getPublishInfo(instance);
    // 添加实例,其实就是存到一个Map中
    client.addServiceInstance(singleton, instanceInfo);
    client.setLastUpdatedTime();
    client.recalculateRevision();
    // 发布了 ClientRegisterServiceEvent 事件
    NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
    NotifyCenter
            .publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}

在注册过程中,发布了一个 ClientRegisterServiceEvent 事件,找一下该事件监听的地方

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) {
    // 将实例信息进行存储到另一个map中,用于
    publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>()).add(clientId);
    // 进行事件发布
    NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}

上述两个map的区别

  • publishers 主要存储的是服务实例的详细信息(如服务提供者的 IP 地址、端口、元数据等),它是实际的服务数据。

  • publisherIndexes 则是一个索引数据结构,用于提升查找 publishers 中服务实例的效率。

至此 Nacos 的注册流程梳理完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值