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:
- 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
NacosAutoServiceRegistrationescuta esse evento e dispara o processo de registro
- Fase de Processamento do Cliente (Área Roxa)
- Converte o objeto
Registrationdo Spring Cloud para o objetoInstancedo Nacos - Constrói requisições HTTP através do
NamingProxye as envia para o servidor Nacos
- Fase de Processamento do Servidor (Área Verde)
- O
InstanceControllerrecebe a requisição de registro e realiza validação de parâmetros - O
ServiceManagergerencia 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
- 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çãospring.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:
- Arquitetura em Camadas: Cliente, camada de comunicação e servidor têm responsabilidades distintas e bem definidas
- Orientado por Eventos: Utiliza o mecanismo de eventos do Spring para implementar um gatilho de registro com baixo acoplamento