(二)微服务中间键工作原理——nacos服务端服务注册心跳包(/nacos/v1/ns/instance/beat)源码解读

本文详细介绍了Nacos服务端如何处理客户端注册服务时的心跳包,从InstanceController类的beat方法开始,包括心跳包数据的解析、服务实例注册、心跳检查等关键步骤。通过分析源码,揭示了服务实例生命周期管理和健康检查的内部机制。
摘要由CSDN通过智能技术生成

前言

上节内容我们通过分析nacos客户端源码,了解了nacos客户端是如何向服务端注册服务和发送心跳包的,本节内容话接上一节内容,我们通过分析nacos服务的源码,查看服务端是如何处理客户端注册时候的心跳包的。关于nacos服务端的源码,下载地址为:GitHub - alibaba/nacos: an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications.

正文

①找到nacos服务端处理心跳包的接口InstanceController类

接口地址:/nacos/v1/ns/instance/beat

 ②在InstanceController类中的beat方法实现了心跳包的处理逻辑

心跳包的整体处理流程说明:

@CanDistro
    @PutMapping("/beat")
    @Secured(action = ActionTypes.WRITE)
    public ObjectNode beat(HttpServletRequest request) throws Exception {

        ObjectNode result = JacksonUtils.createEmptyJsonNode();
        //1.设置默认心跳间隔时间
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());
        //2.获取心跳包数据
        String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);
        RsInfo clientBeat = null;
        if (StringUtils.isNotBlank(beat)) {
            //3.解析心跳包数据
            clientBeat = JacksonUtils.toObj(beat, RsInfo.class);
        }
        //4.获取集群名称、IP地址、和端口
        String clusterName = WebUtils
                .optional(request, CommonParams.CLUSTER_NAME, UtilsAndCommons.DEFAULT_CLUSTER_NAME);
        String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY);
        int port = Integer.parseInt(WebUtils.optional(request, "port", "0"));
        if (clientBeat != null) {
            if (StringUtils.isNotBlank(clientBeat.getCluster())) {
                clusterName = clientBeat.getCluster();
            } else {
                // fix #2533
                clientBeat.setCluster(clusterName);
            }
            ip = clientBeat.getIp();
            port = clientBeat.getPort();
        }
        //5.获取客户端服务命名空间和服务名称
        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        //6.检查服务名称规范,不符合要求抛出异常
        NamingUtils.checkServiceNameFormat(serviceName);
        Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}, namespaceId: {}", clientBeat,
                serviceName, namespaceId);
        BeatInfoInstanceBuilder builder = BeatInfoInstanceBuilder.newBuilder();
        builder.setRequest(request);
        //7.处理心跳包数据
        int resultCode = getInstanceOperator()
                .handleBeat(namespaceId, serviceName, ip, port, clusterName, clientBeat, builder);
        //8.返回处理结果
        result.put(CommonParams.CODE, resultCode);
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL,
                getInstanceOperator().getHeartBeatInterval(namespaceId, serviceName, ip, port, clusterName));
        result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
        return result;
    }

③在InstanceOperatorClientImpl类中handleBeat方法是对心跳包的具体处理流程

handleBeat方法执行流程的说明:

public int handleBeat(String namespaceId, String serviceName, String ip, int port, String cluster,
            RsInfo clientBeat, BeatInfoInstanceBuilder builder) throws NacosException {
        //1.根据服务命名空间和服务名称获取服务实例对象
        Service service = getService(namespaceId, serviceName, true);
        //2.根据ip和端口号获取客户端ID
        String clientId = IpPortBasedClient.getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port, true);
        //3.查询注册的客户端
        IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId);
        //4.如果客户端不存在或者客户端服务实例还未发布,注册客户端实例,否则跳过该操作
        if (null == client || !client.getAllPublishedService().contains(service)) {
            if (null == clientBeat) {
                //4.1心跳包不存在,直接返回不存在的提示码
                return NamingResponseCode.RESOURCE_NOT_FOUND;
            }
            //4.2根据心跳包和服务名称构建Instance实例对象
            Instance instance = builder.setBeatInfo(clientBeat).setServiceName(serviceName).build();
            //4.3注册Instance实例对象,该方式中存在关于client的实例对象的注册
            registerInstance(namespaceId, serviceName, instance);
            //4.4再次获取客户端实例
            client = (IpPortBasedClient) clientManager.getClient(clientId);
        }
        //5.验证服务实例对象是否存在,不存在则抛出服务不存在的异常
        if (!ServiceManager.getInstance().containSingleton(service)) {
            throw new NacosException(NacosException.SERVER_ERROR,
                    "service not found: " + serviceName + "@" + namespaceId);
        }
        //6.心跳包不存在,则根据传入参数封装客户端心跳包数据
        if (null == clientBeat) {
            clientBeat = new RsInfo();
            clientBeat.setIp(ip);
            clientBeat.setPort(port);
            clientBeat.setCluster(cluster);
            clientBeat.setServiceName(serviceName);
        }
        //7.服务健康检查,更新服务的心跳时间,如果服务的健康状态是false,则更新为true,表明服务实例是健康状态
        ClientBeatProcessorV2 beatProcessor = new ClientBeatProcessorV2(namespaceId, clientBeat, client);
        HealthCheckReactor.scheduleNow(beatProcessor);
        //8.更新客户端时间
        client.setLastUpdatedTime();
        return NamingResponseCode.OK;
    }

 registerInstance(namespaceId, serviceName, instance)方法实现了客户端服务和客户端实例的注册

 

下面的方法实现了服务的心跳时间更新和健康状态更新

ClientBeatProcessorV2 beatProcessor = new ClientBeatProcessorV2(namespaceId, clientBeat, client);
HealthCheckReactor.scheduleNow(beatProcessor);

④ InstanceOperatorClientImpl类中的registerInstance方法实现了IpPortBasedClient客户端和Instance服务实例的注册

IpPortBasedClient客户端和Instance服务实例的注册的源码说明:
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        //1.检查服务实例是否合法
        NamingUtils.checkInstanceIsLegal(instance);
        //2.获取IpPortBasedClient客户端id
        boolean ephemeral = instance.isEphemeral();
        String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
        //3.创建IpPortBasedClient客户端
        createIpPortClientIfAbsent(clientId);
        //4.获取服务实例
        Service service = getService(namespaceId, serviceName, ephemeral);
        //5.创建Instance实例
        clientOperationService.registerInstance(service, instance, clientId);
    }

 ⑤分析IpPortBasedClient客户端实例创建的方法createIpPortClientIfAbsent(clientId);

- IpPortBasedClient客户端存储在一个ConcurrentMap集合中

private final ConcurrentMap<String, IpPortBasedClient> clients = new ConcurrentHashMap<>();

- 判断IpPortBasedClient客户端是否存在,不存在则创建该客户端

private void createIpPortClientIfAbsent(String clientId) {
        //1.判断IpPortBasedClient客户端是否存在
        if (!clientManager.contains(clientId)) {
            ClientAttributes clientAttributes;
            if (ClientAttributesFilter.threadLocalClientAttributes.get() != null) {
                clientAttributes = ClientAttributesFilter.threadLocalClientAttributes.get();
            } else {
                clientAttributes = new ClientAttributes();
            }
            //2.客户端不存在,创建IpPortBasedClient客户端
            clientManager.clientConnected(clientId, clientAttributes);
        }
    }

-调用clientConnected方法,使用客户端工厂类EphemeralIpPortClientFactory创建一个客户端IpPortBasedClient

public boolean clientConnected(String clientId, ClientAttributes attributes) {
	//使用客户端工厂工具创建一个客户端
	return clientConnected(clientFactory.newClient(clientId, attributes));
}

-调用clientConnected同名方法,将IpPortBasedClient客户端存储于ConcurrentMap中,并执行init初始化方法

public boolean clientConnected(final Client client) {
	//将IpPortBasedClient客户端存储到clients的ConcurrentMap中
	clients.computeIfAbsent(client.getClientId(), s -> {
		Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
		IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
		//调用IpPortBasedClient的init方法,实现初始化方法
		ipPortBasedClient.init();
		return ipPortBasedClient;
	});
	return true;
}

-在init方法中,实现了注册服务心跳包检查和健康检查

public void init() {
	if (ephemeral) {
		//心跳包检查
		beatCheckTask = new ClientBeatCheckTaskV2(this);
		HealthCheckReactor.scheduleCheck(beatCheckTask);
	} else {
		//健康检查
		healthCheckTaskV2 = new HealthCheckTaskV2(this);
		HealthCheckReactor.scheduleCheck(healthCheckTaskV2);
	}
}

- 心跳包检查,在类ClientBeatCheckTaskV2中的doHealthCheck()方法实现心跳包的健康检查

 -在doInterceptor方法中的passIntercept方法实现了客户端检查、健康检查、服务实例检查

-这里我们以服务实例检查为例,ExpiredInstanceChecker中的doCheck方法实现服务实例的过期检查,如果过期则删除实例,默认超过30秒删除实例

public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) {
	boolean expireInstance = ApplicationUtils.getBean(GlobalConfig.class).isExpireInstance();
	//如果过期则删除服务注册实例
	if (expireInstance && isExpireInstance(service, instance)) {
		deleteIp(client, service, instance);
	}
}

 

- 在UnhealthyInstanceChecker中的doCheck方法实现服务实例的健康检查,默认时间超过15秒则视实例为不健康

public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) {
	//判断实例是否健康
	if (instance.isHealthy() && isUnhealthy(service, instance)) {
		//如果实例不健康则将实例状态置为false
		changeHealthyStatus(client, service, instance);
	}
}

⑥服务实例Instance注册 registerInstance

public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
	//1.检查服务实例是否合法
	NamingUtils.checkInstanceIsLegal(instance);
	//2.获取并创建单实例的服务
	Service singleton = ServiceManager.getInstance().getSingleton(service);
	if (!singleton.isEphemeral()) {
		throw new NacosRuntimeException(NacosException.INVALID_PARAM,
				String.format("Current service %s is persistent service, can't register ephemeral instance.",
						singleton.getGroupedServiceName()));
	}
	//3.获取客户端,如果不合法则直接返回
	Client client = clientManager.getClient(clientId);
	if (!clientIsLegal(client, clientId)) {
		return;
	}
	//4.获取实例发布信息
	InstancePublishInfo instanceInfo = getPublishInfo(instance);
	//5.更新客户端信息
	client.addServiceInstance(singleton, instanceInfo);
	client.setLastUpdatedTime();
	client.recalculateRevision();
	//6.发布客户端服务注册事件
	NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
	NotifyCenter
			.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}

结语

至此,关于nacos服务端服务注册心跳包(/nacos/v1/ns/instance/beat)源码解读到这里就结束了,下期见。。。。。。

根据引用中的代码片段,/nacos/u1/ns/instance/beat 是一个用于发送心跳信息的接口。当Spring容器启动时,Nacos客户端会向Nacos服务端发送心跳信息并注册当前服务。具体的实现是通过向线程池中提交一个发送心跳的任务,实现异步发送。 引用中提到,Nacos是一个服务注册和发现的平台,它可以用来管理和维护微服务架构中的各个服务实例。 引用中提到,当使用Nacos集群时,对于服务列表非常多的情况下,可以通过计算每个服务的哈希值,并对集群列表长度取模的方式来分担健康检查的压力。这样的设计类似于分片的思想,可以使多台Nacos服务器共同承担健康检查的任务。 引用中指出,Nacos的临时节点同步模式是AP模式。这是因为Nacos集群中没有主节点,第一次收到数据的节点会向其他节点发起同步请求,即使其他节点没有同步到服务,整个集群也能正常对外提供服务。这种设计满足了可用性要求,因此是AP模式。 综上所述,/nacos/u1/ns/instance/beat是用于发送心跳信息的接口,Nacos是一个服务注册和发现的平台,可以管理和维护微服务架构中的各个服务实例。在使用Nacos集群时,可以通过哈希值计算和分片的方式来分担健康检查的压力。Nacos的临时节点同步模式是AP模式,保证了集群的可用性。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

厉害哥哥吖

您的支持是我创作下去的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值