Nacos Service Registration & Discovery: Source Code Deep Dive with Spring Cloud Integration
This article analyzes Nacos service registration and discovery internals, covering architecture, Spring Cloud Commons integration, heartbeat-based health checks, OpenAPI registration flow, and the HostReactor's three-map caching mechanism with 10-second periodic updates and failover support.
1. Nacos Introduction
Nacos solves unified configuration, service registration, and discovery in microservices. It integrates registry and config center with features: service discovery via DNS/RPC, real-time health checks (Ping/TCP, HTTP, MySQL), dynamic configuration management, weighted DNS routing, and service/metadata lifecycle management.
2. Nacos Registry Implementation Principles
2.1 Architecture
Key modules:
Provider APP : service provider
Consumer APP : service consumer
Name Server : routes to Nacos HA cluster via Virtual IP or DNS
Nacos Server : provides OpenAPI entry, Config Service, Naming Service, and Consistency Protocol (Raft for cluster data sync)
Nacos Console : management UI
Providers access Nacos Server via VIP, register/query via OpenAPI; cluster nodes sync via Raft.
2.2 Registry Principles
Registration: instances register on startup, deregister on shutdown. Discovery: consumers query registry for available instances. Health checks: registry calls instance health APIs (transport/app layer).
3. Nacos Source Code Analysis
3.1 Service Registration
Spring Cloud defines ServiceRegistry<R extends Registration> interface with register, deregister, close, setStatus, getStatus. Nacos implements via NacosServiceRegistry in spring-cloud-starter-alibaba-nacos-discovery which depends on spring-cloud-commons.
Auto-configuration: spring.factories declares AutoServiceRegistrationAutoConfiguration (conditional on spring.cloud.service-registry.auto-registration.enabled, default true). It injects AutoServiceRegistration and AutoServiceRegistrationProperties. AbstractAutoServiceRegistration<R> implements ApplicationListener<WebServerInitializedEvent>. On WebServerInitializedEvent, onApplicationEvent calls bind(event) which eventually invokes NacosServiceRegistry.register(). NacosServiceRegistry.register() delegates to NamingService.registerInstance(serviceId, group, instance):
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { if (instance.isEphemeral()) { BeatInfo beatInfo = new BeatInfo(); beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName)); beatInfo.setIp(instance.getIp()); beatInfo.setPort(instance.getPort()); beatInfo.setCluster(instance.getClusterName()); beatInfo.setWeight(instance.getWeight()); beatInfo.setMetadata(instance.getMetadata()); beatInfo.setScheduled(false); long instanceInterval = instance.getInstanceHeartBeatInterval(); beatInfo.setPeriod(instanceInterval == 0L ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval); // 1. Heartbeat for health monitoring this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo); } // 2. Actual registration this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);}BeatReactor.addBeatInfo()stores beatInfo in dom2Beat map and schedules BeatTask every period (default 5000ms) via executorService.schedule(). Each beat sends a packet; missing response indicates failure. serverProxy.registerService() builds a parameter Map (namespaceId, serviceName, groupName, clusterName, ip, port, weight, enable, healthy, ephemeral, metadata) and POSTs to NACOS_URL_INSTANCE via OpenAPI.
Debug Walkthrough: Registration Flow
Project init triggers AbstractAutoServiceRegistration.onApplicationEvent() on WebServerInitializedEvent. NacosAutoServiceRegistration calls super.register(). NacosServiceRegistry.register() executes with serviceId, group, instance. registerInstance() does health check via beatReactor.addBeatInfo() then registration via serverProxy.registerService().
Registration Summary (Q&A)
Q1: Why spring-cloud-commons ? It provides ServiceRegistry interface; all Spring Cloud registry implementations must implement it. Nacos provides NacosServiceRegistry.
Q2: Service not registering? Must be a web project ( spring-boot-starter-web) because registration triggers on WebServerInitializedEvent.
Q3: Other roles of spring-cloud-commons ? Its spring.factories enables auto-configuration of AutoServiceRegistrationAutoConfiguration which injects AutoServiceRegistration (implemented by NacosAutoServiceRegistration) and wires the event listener.
Overall flow: startup → spring.factories auto-config → AutoServiceRegistrationAutoConfiguration → injects AutoServiceRegistration → listener on WebServerInitializedEvent → NacosServiceRegistry.register() → NamingService.registerInstance() → heartbeat + OpenAPI registration.
3.2 Service Discovery
Discovery occurs during remote calls (e.g., OpenFeign). @FeignClient("test-application") uses service name for discovery. NacosServerList.getServers() calls
discoveryProperties.namingServiceInstance().selectInstances(serviceId, group, true)and converts List<Instance> to List<NacosServer>. NacosNamingService.selectInstances() delegates to overloaded method with clusters, healthy, subscribe flags. If subscribe=true, uses local cache via hostReactor.getServiceInfo(); else fetches directly from server via getServiceInfoDirectlyFromServer(). HostReactor maintains three maps:
private final Map<String, ScheduledFuture<?>> futureMap; // async update callbacksprivate Map<String, ServiceInfo> serviceInfoMap; // local service list, key=service nameprivate Map<String, Object> updatingMap; // pending updatesprivate ScheduledExecutorService executor; // periodic updatesgetServiceInfo(serviceName, clusters):
If failover switch on, return from failover reactor.
Get ServiceInfo via getServiceInfo0(). If null, create new ServiceInfo, put into serviceInfoMap and updatingMap, then call updateServiceNow() to fetch from server immediately, remove from updatingMap.
If updatingMap contains key, wait up to 5000ms on serviceObj for update to complete.
Schedule periodic updates via scheduleUpdateIfAbsent().
Return ServiceInfo from serviceInfoMap. scheduleUpdateIfAbsent() creates an UpdateTask scheduled every 10 seconds via executor, stores ScheduledFuture in futureMap. This ensures async background refresh regardless of call frequency.
Debug Walkthrough: Discovery Flow
Remote call (Feign) triggers NacosServerList.getServers() with serviceId matching @FeignClient value.
Calls NacosNamingService.selectInstances() with subscribe=true → reads from local serviceInfoMap. HostReactor.getServiceInfo() retrieves from serviceInfoMap; uses updatingMap and 5s wait for consistency.
Always calls scheduleUpdateIfAbsent() to ensure periodic refresh.
Background UpdateTask every 10s updates local registry; changes reflected in serviceInfoMap.
Discovery Summary
Discovery typically uses subscription model → reads from local cache ( serviceInfoMap).
Non-subscribed calls fetch from server directly (requires healthy server).
Three maps coordinate: serviceInfoMap (data), updatingMap (in-flight), futureMap (async callbacks). getServiceInfo() combines immediate fetch ( updateServiceNow) and scheduled refresh ( scheduleUpdateIfAbsent).
Async updates via thread pool store futures in futureMap and refresh local registry on completion.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
