Nacos(公式サイト)は、サービス登録・ディスカバリ機能を提供する代表的なマイクロサービス基盤である。ここでは、Spring Boot アプリケーションが Nacos にサービスを登録する際の内部フローを、ソースコードレベルで追って解説する。
まず、Spring Cloud Alibaba Nacos Discovery の自動設定エントリポイントとして spring.factories ファイルを確認すると、NacosServiceRegistryAutoConfiguration というクラスが存在することがわかる。このクラスはサービス登録に関連する主要な Bean を定義している。
@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
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties properties) {
return new NacosServiceRegistry(properties);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosRegistration nacosRegistration(
NacosDiscoveryProperties properties,
ApplicationContext context) {
return new NacosRegistration(properties, context);
}
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
public NacosAutoServiceRegistration nacosAutoServiceRegistration(
NacosServiceRegistry registry,
AutoServiceRegistrationProperties autoProps,
NacosRegistration registration) {
return new NacosAutoServiceRegistration(registry, autoProps, registration);
}
}
NacosAutoServiceRegistration は AbstractAutoServiceRegistration を継承しており、この抽象クラスは ApplicationListener<WebServerInitializedEvent> を実装している。これにより、アプリケーション起動時に Web サーバーが初期化されると自動的に onApplicationEvent が呼び出され、最終的に register() メソッドが実行される。
public class NacosAutoServiceRegistration
extends AbstractAutoServiceRegistration<Registration> {
private final NacosRegistration registration;
public NacosAutoServiceRegistration(
ServiceRegistry<Registration> serviceRegistry,
AutoServiceRegistrationProperties autoProps,
NacosRegistration registration) {
super(serviceRegistry, autoProps);
this.registration = registration;
}
@Override
protected void register() {
if (!registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
return;
}
if (registration.getPort() < 0) {
registration.setPort(getPort().get());
}
super.register();
}
// その他オーバーライドメソッド省略...
}
親クラスの register() 内部では、実際の登録処理が NacosServiceRegistry.register() に委譲される。
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} registered",
group, serviceId, instance.getIp(), instance.getPort());
} catch (Exception e) {
log.error("nacos registry failed for {}", serviceId, e);
rethrowRuntimeException(e);
}
}
namingService.registerInstance() の内部では、インスタンスが一時的(ephemeral)かどうかを判定し、一時インスタンスであればハートビート送信タスクをスケジュールする。その後、実際にサービスを Nacos サーバーに登録する処理が実行される。
@Override
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.setPeriod(instance.getInstanceHeartBeatInterval());
beatReactor.addBeatInfo(
NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(
NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
ハートビート送信は BeatTask という Runnable 実装によって定期的に実行される。このタスクは /nacos/v1/ns/instance/beat エンドポイントに対して HTTP リクエストを送信し、サーバーから返されたハートビート間隔に基づいて次回の送信タイミングを調整する。もしサーバー側で該当インスタンスが見つからない場合(ステータスコード RESOURCE_NOT_FOUND)、再度 /nacos/v1/ns/instance に対して登録リクエストを送信するリトライ処理も含まれている。
public void run() {
if (beatInfo.isStopped()) return;
long nextTime = beatInfo.getPeriod();
try {
JSONObject result = serverProxy.sendBeat(beatInfo, lightBeatEnabled);
long interval = result.getIntValue("clientBeatInterval");
if (interval > 0) {
nextTime = interval;
}
int code = result.getIntValue("code");
if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
Instance instance = buildInstanceFromBeatInfo(beatInfo);
serverProxy.registerService(
beatInfo.getServiceName(),
NamingUtils.getGroupName(beatInfo.getServiceName()),
instance);
}
} catch (NacosException e) {
log.error("[CLIENT-BEAT] failed: {}", e.getMessage());
}
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
以上のように、クライアント側のサービス登録は「インスタンス情報の構築 → 初期登録 → ハートビートの定期送信(および必要に応じた再登録)」という流れで実現されている。