Nacos サービス登録の内部処理フロー解析

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);
    }
}

NacosAutoServiceRegistrationAbstractAutoServiceRegistration を継承しており、この抽象クラスは 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);
}

以上のように、クライアント側のサービス登録は「インスタンス情報の構築 → 初期登録 → ハートビートの定期送信(および必要に応じた再登録)」という流れで実現されている。

タグ: Nacos Spring Cloud Alibaba サービスディスカバリ マイクロサービス Java

7月24日 02:21 投稿