Mecanismo de Registro de Serviços do Nacos

Introdução

Fluxograma do Processo de Registro e Descoberta de Serviços

Para compreender de forma mais intuitiva o fluxo completo do registro e descoberta de serviços do Nacos, apresentamos abaixo um fluxograma em Mermaid que demonstra o fluxo de dados completo e a interação entre componentes desde a inicialização de uma aplicação Spring Boot até o armazenamento no servidorr:

Explicação dos Principais Nós do Fluxograma:

  1. Fase de Gatilho de Eventos (Área Azul)
  • Durante a inicialização do Spring Boot, quando a inicialização do servidor web é concluída, o evento ServletWebServerInitializedEvent é publicado
  • O NacosAutoServiceRegistration escuta esse evento e dispara o processo de registro
  1. Fase de Processamento do Cliente (Área Roxa)
  • Converte o objeto Registration do Spring Cloud para o objeto Instance do Nacos
  • Constrói requisições HTTP através do NamingProxy e as envia para o servidor Nacos
  1. Fase de Processamento do Servidor (Área Verde)
  • O InstanceController recebe a requisição de registro e realiza validação de parâmetros
  • O ServiceManager gerencia a criação de serviços e a lógica de adição de instâncias
  • As informações do serviço são armazenadas na estrutura de dados em memória serviceMap
  • Suporte à sincronização de dados do cluster com listen de consistência de registro
  1. Fase de Conclusão (Área Amarela)
  • Registro de serviço concluído, informações da instância persistidas no servidor Nacos
  • O cliente começa a enviar heartbeats para manter a conexão

Principais Caminhos do Fluxo de Dados:

  • Fluxo de Eventos: Inicialização Spring Boot → Inicialização WebServer → Publicação de Evento → Escuta de Evento → Gatilho de Registro
  • Fluxo de Dados: Registration → Instance → Requisição HTTP → Análise do Servidor → Armazenamento em Memória
  • Fluxo de Controle: Registro ativo do cliente → Processamento de validação do servidor → Armazenamento e sincronização → Registro concluído

Este fluxograma demonstra claramente a cadeia completa do registro de serviços do Nacos, ajudando os leitores a compreender a colaboração entre componentes e o processo de manipulação de dados.

O Nacos, como plataforma de descoberta de serviços e gerenciamento de configuração de código aberto desenvolvida pela Alibaba, desempenha um papel crucial no módulo de registro central em uma arquitetura de microsserviços. Este artigo analisará, a partir da integração com o Spring Cloud, o fluxo completo do registro de serviços do Nacos, cobrindo o mecanismo orientado por eventos do cliente, a estrutura de armazenamento de dados do servidor e os principais conceitos de design, ajudando os leitores a construir uma compreensão sistemática de um registro central de serviços distribuídos.

1. Integração Spring Cloud e Mecanismo de Auto-configuração

1.1 Ponto de Entrada da Auto-configuração

No ecossistema Spring Boot, o Nacos implementa auto-configuração através do arquivo spring.factories. Quando o projeto inclui a dependência spring-cloud-starter-alibaba-nacos-discovery, o Spring Boot carrega automaticamente a classe de configuração NacosServiceRegistryAutoConfiguration durante a inicialização.

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class,
        NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

    // Bean central do registro de serviços
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public NacosAutoServiceRegistration nacosAutoServiceRegistration(
            NacosServiceRegistry registro,
            AutoServiceRegistrationProperties propriedadesRegistroAutomatico,
            NacosRegistration registroNacos) {
        return new NacosAutoServiceRegistration(registro,
                propriedadesRegistroAutomatico, registroNacos);
    }
}


Análise das Anotações Principais:

  • @ConditionalOnNacosDiscoveryEnabled: Garante que a funcionalidade de descoberta de serviços do Nacos está ativada
  • @ConditionalOnProperty: Verifica a configuração spring.cloud.service-registry.auto-registration.enabled, com valor padrão true
  • @AutoConfigureAfter: Garante a execução após as classes de auto-configuração relacionadas, assegurando que os Beans dependentes estejam prontos

1.2 Modelo de Registro Orientado por Eventos

O Nacos utiliza o modelo de eventos do Spring para implementar o registro de serviços, onde a classe central NacosAutoServiceRegistration herda de AbstractAutoServiceRegistration:

public class NacosAutoServiceRegistration extends AbstractAutoServiceRegistration<NacosRegistration> {
    
    public NacosAutoServiceRegistration(ServiceRegistry<Registration> registroServico,
            AutoServiceRegistrationProperties propriedadesRegistroAutomatico,
            NacosRegistration registro) {
        super(registroServico, propriedadesRegistroAutomatico);
        this.registro = registro;
    }
}


2. Momento do Gatilho do Registro de Serviços e Propagação de Eventos

2.1 Eventos do Ciclo de Vida do Servidor Web

Durante a inicialização de uma aplicação Spring Boot, a inicialização do servidor web dispara eventos-chave:

// WebServerStartStopLifecycle.java
@Override
public void start() {
    this.webServer.start();
    this.running = true;
    // Publica o evento ServletWebServerInitializedEvent
    this.applicationContext
            .publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}


2.2 Escuta e Processamento de Eventos

O AbstractAutoServiceRegistration implementa a interface ApplicationListener<WebServerInitializedEvent>:

@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent evento) {
    // Ponto central de gatilho do registro de serviços
    bind(evento);
}

@Deprecated
public void bind(WebServerInitializedEvent evento) {
    ApplicationContext contexto = evento.getApplicationContext();
    // Exclui porta de administração
    if (contexto instanceof ConfigurableWebServerApplicationContext) {
        if ("management".equals(((ConfigurableWebServerApplicationContext) contexto)
                .getServerNamespace())) {
            return;
        }
    }
    // Define a porta do serviço
    this.port.compareAndSet(0, evento.getWebServer().getPort());
    // Inicia o fluxo de registro
    this.start();
}


2.3 Ponto de Entrada do Fluxo de Registro

O método start() finalmente chama register(), entrando na lógica de registro do cliente Nacos:

protected void register() {
    this.serviceRegistry.register(getRegistration());
}


3. Implementação do Registro no Cliente Nacos

3.1 Interface de Registro de Serviços

O NacosServiceRegistry implementa a interface ServiceRegistry do Spring Cloud:

@Override
public void register(Registration registro) {
    // Constrói informações da instância Nacos
    Instance instancia = getNacosInstanceFromRegistration(registro);
    
    try {
        // Chama o serviço de nomeação do Nacos para registrar
        namingService.registerInstance(
            registro.getServiceId(), 
            registro.getGroup(), 
            instancia
        );
    } catch (Exception e) {
        log.error("falha no registro no nacos...", e);
    }
}


3.2 Construção de Informações da Instância

O cliente precisa converter o objeto Registration do Spring Cloud para o objeto Instance do Nacos:

private Instance getNacosInstanceFromRegistration(Registration registro) {
    Instance instancia = new Instance();
    instancia.setIp(registro.getHost());
    instancia.setPort(registro.getPort());
    instancia.setWeight(registro.getMetadata().getOrDefault("weight", "1.0"));
    instancia.setClusterName(registro.getMetadata().getOrDefault("cluster", "DEFAULT"));
    instancia.setMetadata(registro.getMetadata());
    instancia.setEphemeral(Boolean.parseBoolean(
        registro.getMetadata().getOrDefault("ephemeral", "true")
    ));
    return instancia;
}


4. Camada de Comunicação HTTP do Cliente Nacos

4.1 Construção e Envio de Requisições

O NamingProxy é responsável por construir requisições HTTP e enviá-las para o servidor Nacos:

public void registerService(String nomeServico, String nomeGrupo, Instance instancia) throws NacosException {
    
    final Map<String, String> parametros = new HashMap<>(8);
    parametros.put("namespaceId", namespaceId);
    parametros.put("serviceName", nomeServico);
    parametros.put("groupName", nomeGrupo);
    parametros.put("ip", instancia.getIp());
    parametros.put("port", String.valueOf(instancia.getPort()));
    parametros.put("weight", String.valueOf(instancia.getWeight()));
    parametros.put("enable", String.valueOf(instancia.isEnabled()));
    parametros.put("healthy", String.valueOf(instancia.isHealthy()));
    parametros.put("ephemeral", String.valueOf(instancia.isEphemeral()));
    
    // Envia requisição POST
    reqApi(UtilAndComs.nacosUrlInstance, parametros, HttpMethod.POST);
}


4.2 Caminho da Requisição e Endpoints

A interface RESTful para registro de instâncias no servidor Nacos:

public class UtilAndComs {
    public static String webContext = "/nacos";
    public static String nacosUrlBase = webContext + "/v1/ns";
    public static String nacosUrlInstance = nacosUrlBase + "/instance";
}


Caminho Completo da Requisição: /nacos/v1/ns/instance

5. Fluxo de Processamento do Servidor Nacos

5.1 Processamento na Camada de Controle

O servidor recebe requisições de registro através do InstanceController:

@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/instance")
public class InstanceController {
    
    @CanDistro
    @PostMapping
    @Secured(action = ActionTypes.WRITE)
    public String register(HttpServletRequest requisicao) throws Exception {
        
        final String namespaceId = WebUtils
                .optional(requisicao, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        final String serviceName = WebUtils.required(requisicao, CommonParams.SERVICE_NAME);
        NamingUtils.checkServiceNameFormat(serviceName);
        
        // Constrói o objeto Instance
        final Instance instance = HttpRequestInstanceBuilder.newBuilder()
                .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral())
                .setRequest(requisicao)
                .build();
        
        // Chama a camada de serviço para registro
        getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
        
        // Publica evento de registro para monitoramento e rastreamento
        NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(
            System.currentTimeMillis(), 
            "", 
            false, 
            namespaceId, 
            NamingUtils.getGroupName(serviceName), 
            NamingUtils.getServiceName(serviceName),
            instance.getIp(), 
            instance.getPort()
        ));
        
        return "ok";
    }
}


5.2 Processamento na Camada de Serviço

O InstanceOperatorServiceImpl, como camada de serviço, é responsável pelo processamento da lógica de negócios:

@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    // Converte para o objeto Instance interno do núcleo
    com.alibaba.nacos.naming.core.Instance coreInstance = parseInstance(instance);
    
    // Delega para o ServiceManager o processamento
    serviceManager.registerInstance(namespaceId, serviceName, coreInstance);
}


5.3 Lógica Central de Registro

O ServiceManager é o gerencaidor central do registro de serviços:

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
    
    // 1. Validação de legality da instância
    NamingUtils.checkInstanceIsLegal(instance);
    
    // 2. Criação de serviço vazio (se não existir)
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());
    
    // 3. Obtenção do objeto de serviço
    Service service = getService(namespaceId, serviceName);
    
    // 4. Verificação de nullidade do serviço
    checkServiceIsNull(service, namespaceId, serviceName);
    
    // 5. Adição da instância ao serviço
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}


6. Armazenamento de Serviços e Estrutura de Dados

6.1 Fluxo de Criação de Serviços

public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
        throws NacosException {
    
    // Verifica se o serviço já existe
    Service service = getService(namespaceId, serviceName);
    if (service != null) {
        return;
    }

    Loggers.SRV_LOG.info("criando serviço vazio {}:{}", namespaceId, serviceName);
    
    // Cria novo objeto Service
    service = new Service();
    service.setName(serviceName);
    service.setNamespaceId(namespaceId);
    service.setGroupName(NamingUtils.getGroupName(serviceName));
    service.setLastModifiedMillis(System.currentTimeMillis());
    service.recalculateChecksum();
    
    // Define informações de cluster
    if (cluster != null) {
        cluster.setService(service);
        service.getClusterMap().put(cluster.getName(), cluster);
    }
    
    // Validação do serviço
    service.validate();
    
    // Armazena serviço e inicializa
    putServiceAndInit(service);
    
    // Sincronização de cluster em modo não-local
    if (!local) {
        addOrReplaceService(service);
    }
}


6.2 Estrutura de Armazenamento em Memória

O Nacos utiliza coleções concorrentes aninhadas em múltiplas camadas para armazenar informações de serviços:

/**
 * Estrutura de armazenamento de serviços: Map<namespace, Map<group::serviceName, Service>>
 * 
 * Primeira camada: Dimensão do namespace
 * Segunda camada: Dimensão do nome do serviço (formato: group::serviceName)
 * Terceira camada: Objeto Service, contendo lista de instâncias, informações de cluster, etc.
 */
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

private void putService(Service service) {
    // Garante que a dimensão do namespace exista
    if (!serviceMap.containsKey(service.getNamespaceId())) {
        serviceMap.putIfAbsent(
            service.getNamespaceId(), 
            new ConcurrentSkipListMap<>()
        );
    }
    
    // Armazena objeto de serviço
    serviceMap.get(service.getNamespaceId())
             .putIfAbsent(service.getName(), service);
}


6.3 Inicialização de Serviços e Escuta

private void putServiceAndInit(Service service) throws NacosException {
    // 1. Armazena serviço
    putService(service);
    
    // 2. Reobtém (garante segurança de thread)
    service = getService(service.getNamespaceId(), service.getName());
    
    // 3. Inicialização do serviço
    service.init();
    
    // 4. Registro de escuta de consistência
    consistencyService.listen(
        KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), 
        service
    );
    consistencyService.listen(
        KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), 
        service
    );
    
    Loggers.SRV_LOG.info("[NOVO-SERVIÇO] {}", service.toJson());
}


7. Protocolo de Consistência e Sincronização de Dados

7.1 Abstração do Serviço de Consistência

O Nacos suporta múltiplos protocolos de consistência através da camada de abstração ConsistencyService:

public interface ConsistencyService {
    
    // Escuta mudanças de dados
    void listen(String chave, RecordListener ouvinte) throws NacosException;
    
    // Grava dados
    void put(String chave, Record valor) throws NacosException;
    
    // Remove dados
    void remove(String chave) throws NacosException;
    
    // Obtém dados
    Datum get(String chave) throws NacosException;
}


7.2 Regras de Construção de Chaves

A construção de chaves para listas de instâncias segue regras específicas:

public class KeyBuilder {
    
    // Constrói chave da lista de instâncias
    public static String buildInstanceListKey(String namespaceId, String serviceName, boolean ephemeral) {
        return namespaceId + "##" + serviceName + (ephemeral ? "@@true" : "@@false");
    }
}


8. Padrões de Design e Conceitos Arquiteturais

8.1 Padrão Template Method

O AbstractAutoServiceRegistration utiliza o padrão Template Method:

public abstract class AbstractAutoServiceRegistration<R extends Registration> 
        implements AutoServiceRegistration, ApplicationListener<WebServerInitializedEvent> {
    
    // Template method: define o esqueleto do fluxo de registro
    protected void start() {
        if (!isEnabled()) {
            return;
        }
        // Hook method, subclasses podem sobrescrever
        if (!this.registration.getProperties().isRegisterEnabled()) {
            return;
        }
        // Lógica central de registro
        register();
    }
    
    // Método abstrato, implementado por subclasses
    protected abstract void register();
}


8.2 Padrão Observer

O mecanismo de eventos do Spring é uma aplicação típica do padrão Observer:

// Publicador de eventos
applicationContext.publishEvent(new ServletWebServerInitializedEvent(...));

// Ouvinte de eventos
@Component
public class NacosAutoServiceRegistration implements ApplicationListener<WebServerInitializedEvent> {
    
    @Override
    public void onApplicationEvent(WebServerInitializedEvent evento) {
        // Processa evento
        bind(evento);
    }
}


8.3 Padrão Strategy

O Nacos suporta diferentes protocolos de consistência através do padrão Strategy:

public class ConsistencyDelegate implements ConsistencyService {
    
    private final ConsistencyService servicoConsistenciaPersistente;
    private final ConsistencyService servicoConsistenciaEphemeral;
    
    @Override
    public void put(String chave, Record valor) throws NacosException {
        // Seleciona estratégia diferente baseado no tipo de instância
        mapConsistencyService(chave).put(chave, valor);
    }
    
    private ConsistencyService mapConsistencyService(String chave) {
        return chave.contains(EPHEMERAL_KEY_PREFIX) ? 
               servicoConsistenciaEphemeral : servicoConsistenciaPersistente;
    }
}


9. Otimização de Desempenho e Melhores Práticas

9.1 Controle de Concorrência

O Nacos utiliza coleções concorrentes em vários pontos para garantir segurança de thread:

// Utiliza ConcurrentHashMap para garantir segurança em ambiente concorrente
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

// Utiliza CopyOnWriteArrayList para garantir desempenho em cenários de leitura intensiva
private List<Instance> todosIPs = new CopyOnWriteArrayList<>();


9.2 Lazy Loading e Cache

Mecanismo de lazy loading para objetos de serviço:

public Service getService(String namespaceId, String serviceName) {
    Map<String, Service> servicosNamespace = serviceMap.get(namespaceId);
    if (servicosNamespace == null) {
        return null;
    }
    return servicosNamespace.get(serviceName);
}


9.3 Otimização de Operações em Lote

Para cenários de registro em lote, o cliente Nacos suporta interface de lote:

public void batchRegisterInstance(String serviceName, String groupName, List<Instance> instances) 
        throws NacosException {
    
    // Construção de parâmetros em lote
    BatchInstanceRequest requisicao = new BatchInstanceRequest();
    requisicao.setServiceName(serviceName);
    requisicao.setGroupName(groupName);
    requisicao.setInstances(instances);
    
    // Envio de requisição em lote
    reqApi(UtilAndComs.nacosUrlInstance + "/batch", requisicao, HttpMethod.POST);
}


10. Tratamento de Falhas e Mecanismos de Tolerância

10.1 Repetição em Caso de Falha de Registro

Estratégia de retry quando o registro do cliente falha:

public void register(Registration registro) {
    int maxTentativas = 3;
    int contadorTentativas = 0;
    
    while (contadorTentativas < maxTentativas) {
        try {
            namingService.registerInstance(...);
            return; // Registro bem-sucedido, sai do loop
        } catch (Exception e) {
            contadorTentativas++;
            if (contadorTentativas >= maxTentativas) {
                log.error("Registro no Nacos falhou após {} tentativas", maxTentativas, e);
                throw new IllegalStateException("Registro falhou", e);
            }
            // Exponential backoff
            Thread.sleep(1000 * (long) Math.pow(2, contadorTentativas));
        }
    }
}


10.2 Mecanismo de Keep-alive via Heartbeat

Instâncias temporárias mantêm estado ativo através de heartbeats:

public class BeatReactor {
    
    // Envia heartbeats periodicamente
    public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(() -> {
            try {
                // Envia requisição de heartbeat
                sendBeat(beatInfo);
            } catch (Exception e) {
                log.error("Falha ao enviar heartbeat", e);
            }
        }, 0, beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    }
}


Considerações Finais

O mecanismo de registro de serviços do Nacos demonstra os principais conceitos de design de sistemas distribuídos modernos:

  1. Arquitetura em Camadas: Cliente, camada de comunicação e servidor têm responsabilidades distintas e bem definidas
  2. Orientado por Eventos: Utiliza o mecanismo de eventos do Spring para implementar um gatilho de registro com baixo acoplamento

Tags: spring-cloud Nacos microservices service-discovery Distributed-Systems

Publicado em 8-16 05:54