(二) Nacos服务发现和注册源码分析

Nacos的注册流程,客户端启动调用服务端的接口

1 客户端的流程

首先分析cloud-nacos包下的自动装配包和类:

NacosAutoServiceRegistration>AbstractAutoServiceRegistration>ApplicationListener<WebServerInitializedEvent>
  • NacosAutoServiceRegistration 是一个监听器,监听WebServer初始化完成之后会触发onApplicationEvent
  • AbstractAutoServiceRegistration#start 中进行当前客户端注册到Nacos中
  • NacosServiceRegistry#register Nacos实例注册
  • NacosNamingService#registerInstance NacosNamingService进行注册实例
  • BeatReactor#addBeatInfo 添加一个心跳检测周期(访问服务端的心跳接口)
  • BeatReactor.BeatTask#run
    • 在该线程中进行发送心跳,并且从服务端获取客户端心跳时间间隔,
    • 如果服务端设置的心跳时间间隔为0,则按照客户端设置的心跳时间进行心跳。
    • 发送完心跳之后在该线程中接着开启一个延时心跳线程
  • NamingProxy#registerService 调用Nacos服务注册实例的接口

详细源码:

监听器触发:AbstractAutoServiceRegistration#onApplicationEvent

public void onApplicationEvent(WebServerInitializedEvent event) {
    this.bind(event);
}
public void bind(WebServerInitializedEvent event) {
    ApplicationContext context = event.getApplicationContext();
    if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
        this.port.compareAndSet(0, event.getWebServer().getPort());
      // 触发注册入口
        this.start();
    }
}
public void start() {
    if (!this.isEnabled()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Discovery Lifecycle disabled. Not starting");
        }

    } else {
        if (!this.running.get()) {
            this.context.publishEvent(new InstancePreRegisteredEvent(this, this.getRegistration()));
          // 客户端注册
            this.register();
            if (this.shouldRegisterManagement()) {
                this.registerManagement();
            }

            this.context.publishEvent(new InstanceRegisteredEvent(this, this.getConfiguration()));
            this.running.compareAndSet(false, true);
        }

    }
}

NacosAutoServiceRegistration#register

protected void register() {
    if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
        log.debug("Registration disabled.");
    } else {
        if (this.registration.getPort() < 0) {
            this.registration.setPort(this.getPort().get());
        }

        super.register();
    }
}

注意该处的设计模式待分析

NacosServiceRegistry#register

public void register(Registration registration) {
    if (StringUtils.isEmpty(registration.getServiceId())) {
        log.warn("No service to register for nacos client...");
    } else {
        String serviceId = registration.getServiceId();
        Instance instance = this.getNacosInstanceFromRegistration(registration);

        try {
            this.namingService.registerInstance(serviceId, instance);
            log.info("nacos registry, {} {}:{} register finished", new Object[]{serviceId, instance.getIp(), instance.getPort()});
        } catch (Exception var5) {
            log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var5});
        }

    }
}

NacosNamingService#registerInstance

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
  // 1 添加心跳监控
    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);
        this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
    }
		// 2 调用服务端的注册实例的接口进行实例注册
    this.serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}

心跳监控 this.beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);

BeatReactor#addBeatInfo

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    this.dom2Beat.put(this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
  // 添加心跳线程 进行心跳监控,该处实现循环心跳主要是通过BeatTask线程中循环开启心跳线程实现的周期性线程监控
    this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), 0L, TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}

BeatReactor.BeatTask#run

public void run() {
    if (!this.beatInfo.isStopped()) {
        // 获取心跳间隔时间 并发送心跳 (调用服务端的心跳接口)
        long result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo);
        long nextTime = result > 0L ? result : this.beatInfo.getPeriod();
        // 开启下一次心跳线程
        BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);
    }
}

总结

  • 在监听器下进行客户端注册(分别是注册实例和添加心跳)
  • 添加循环递归的心跳线程上报心跳信息
  • 调用服务端注册实例的接口进行实例注册

2 服务端服务注册的流程分析

服务端服务注册流程氛围两步对应客户端:注册服务实例和心跳监控

通过两个接口进行分析

2.1 注册实例接口

  • com.alibaba.nacos.naming.controllers.InstanceController
    • 构建实例元信息 Instance
    • 注册实例(主要分析)
@CanDistro
@PostMapping
@Secured(action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);
    // 构建实例Instance
    final Instance instance = HttpRequestInstanceBuilder.newBuilder()
            .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
    // 注册实例
    getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
    return "ok";
}
  • com.alibaba.nacos.naming.core.InstanceOperatorClientImpl
    • 创建Client 实例的客户端存储到Map中
    • 添加服务服务实例 (主要通过监听器进行添加,将实例信息使用Distro协议同步到其他节点上)
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) {
    boolean ephemeral = instance.isEphemeral();
    // 1 构建客户端ID(ClientId)
    String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
    // 2 依据clientId 创建构建Client  存放在 EphemeralIpPortClientManager.Map中或者PersistentIpPortClientManager.map中

    /**
     * 1 创建客户端,并且进行初始化客户端Client的(ClientBeatCheckTaskV2)心跳任务或者通过HealthCheckReactor
     *    永久实例:注册中心主动进行探活
     *    临时实例:临时实例会与注 册中心保持心跳,注册中心会在一段时间没有收到来自客户端的心跳后会将实例设置为不健康
     */
    createIpPortClientIfAbsent(clientId);

    /**
     * 以上两步针对了单机nacos的处理   将nacos维护在了Map内存中
     */

    // 3 构造Service
    Service service = getService(namespaceId, serviceName, ephemeral);
    // 4 注册Instance 到Service中
    clientOperationService.registerInstance(service, instance, clientId);
}
/**
 * 创建客户端,将客户端维护在ClientManager 中Map内存中
 *  在创建
 * @param clientId
 */
private void createIpPortClientIfAbsent(String clientId) {
    if (!clientManager.contains(clientId)) {
        // 进行客户端链接信息的维护
        clientManager.clientConnected(clientId, new ClientAttributes());
    }
}

com.alibaba.nacos.naming.core.v2.client.manager.impl.EphemeralIpPortClientManager#clientConnected(com.alibaba.nacos.naming.core.v2.client.Client)

@Override
public boolean clientConnected(final Client client) {
    clients.computeIfAbsent(client.getClientId(), s -> {
        Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
        IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
      	// 进行实例的健康检查 将该Client添加到健康检查中
        ipPortBasedClient.init();
        return ipPortBasedClient;
    });
    return true;
}
/**
 * Init client.
 *
 * 1 临时节点:
 *      ClientBeatCheckTaskV2 心跳时间监控
 * 2 非临时节点:
 *
 */

public void init() {
    if (ephemeral) {
        beatCheckTask = new ClientBeatCheckTaskV2(this);
        // 定时周期调度进行心跳检测
        HealthCheckReactor.scheduleCheck(beatCheckTask);
    } else {
        healthCheckTaskV2 = new HealthCheckTaskV2(this);
        HealthCheckReactor.scheduleCheck(healthCheckTaskV2);
    }
}
/**
 * Schedule client beat check task with a delay.
 *
 * @param task client beat check task
 */
public static void scheduleCheck(BeatCheckTask task) {
    // 定时调度的线程封装
    Runnable wrapperTask =
            task instanceof NacosHealthCheckTask ? new HealthCheckTaskInterceptWrapper((NacosHealthCheckTask) task)
                    : task;
    //  将定时调度的线程维护到健康检测的Map内存中
    futureMap.computeIfAbsent(task.taskKey(),
            k -> GlobalExecutor.scheduleNamingHealth(wrapperTask, 5000, 5000, TimeUnit.MILLISECONDS));
}
/**
 * interceptorChain:
 *      ServiceEnableBeatCheckInterceptor:检测当前服务是否开启了心跳检测
 *      InstanceEnableBeatCheckInterceptor: 检查实例是否开启心跳检测
 *      InstanceBeatCheckResponsibleInterceptor: 责任检查拦截器,检查当前节点的心跳是否由当前节点负责。如果不是由当前节点负责不进行后续的处理
 */
@Override
public void doHealthCheck() {
    try {
        Collection<Service> services = client.getAllPublishedService();
        for (Service each : services) {
            HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) client
                    .getInstancePublishInfo(each);
            interceptorChain.doInterceptor(new InstanceBeatCheckTask(client, each, instance));
        }
    } catch (Exception e) {
        Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
    }
}

com.alibaba.nacos.naming.core.v2.client.manager.impl.EphemeralIpPortClientManager在实例化的时候会初始化一个线程池进行client的健康检查,对不健康节点的进行剔除

public EphemeralIpPortClientManager(DistroMapper distroMapper, SwitchDomain switchDomain) {
    this.distroMapper = distroMapper;
     // 定时调度检测过期健康节点 默认5秒的调度周期
    GlobalExecutor.scheduleExpiredClientCleaner(new ExpiredClientCleaner(this, switchDomain), 0,
            Constants.DEFAULT_HEART_BEAT_INTERVAL, TimeUnit.MILLISECONDS);
    clientFactory = ClientFactoryHolder.getInstance().findClientFactory(ClientConstants.EPHEMERAL_IP_PORT);
}

ExpiredClientCleaner中进行健康检测剔除节点的逻辑

// 进行过期调度剔除
@Override
public void run() {
    long currentTime = System.currentTimeMillis();
    for (String each : clientManager.allClientId()) {
        IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(each);
        if (null != client && isExpireClient(currentTime, client)) {
            clientManager.clientDisconnected(each);
        }
    }
}
@Override
public boolean clientDisconnected(String clientId) {
    Loggers.SRV_LOG.info("Client connection {} disconnect, remove instances and subscribers", clientId);
    IpPortBasedClient client = clients.remove(clientId);
    if (null == client) {
        return true;
    }
    NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client));
    client.release();
    return true;
}

总结

  • 调用服务端注册接口
  • 将实例的Client注册到ConcurrentHashMap中
  • 在定时调度中检测实例节点的检测过期
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值