微服务:剖析一下源码,Nacos的健康检查竟如此简单

本文详细介绍了Nacos客户端如何通过元数据配置健康检查参数,如heart-beat-interval,并探讨了心跳任务的定时执行、心跳信息的上报与服务端的响应处理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

}catch (Exception e) {

// …

}

}

复制代码

在该方法中有两处需要注意,第一处是构建Instance的getNacosInstanceFromRegistration方法,该方法内会设置Instance的元数据(metadata),通过源元数据可以配置服务器端健康检查的参数。比如,在Spring Cloud中配置的如下参数,都可以通过元数据项在服务注册时传递给Nacos的服务端。

spring:

application:

name: user-service-provider

cloud:

nacos:

discovery:

server-addr: 127.0.0.1:8848

heart-beat-interval: 5000

heart-beat-timeout: 15000

ip-delete-timeout: 30000

复制代码

其中的heart-beat-interval、heart-beat-timeout、ip-delete-timeout这些健康检查的参数,都是基于元数据上报上去的。

register方法的第二处就是调用NamingService#registerInstance来进行实例的注册。NamingService是由Nacos的客户端提供,也就是说Nacos客户端的心跳本身是由Nacos生态提供的。

在registerInstance方法中最终会调用到下面的方法:

@Override

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {

NamingUtils.checkInstanceIsLegal(instance);

String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);

if (instance.isEphemeral()) {

BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);

beatReactor.addBeatInfo(groupedServiceName, beatInfo);

}

serverProxy.registerService(groupedServiceName, groupName, instance);

}

复制代码

其中BeatInfo#addBeatInfo便是进行心跳处理的入口。当然,前提条件是当前的实例需要是临时(瞬时)实例。

对应的方法实现如下:

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {

NAMING_LOGGER.info(“[BEAT] adding beat: {} to beat map.”, beatInfo);

String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());

BeatInfo existBeat = null;

//fix #1733

if ((existBeat = dom2Beat.remove(key)) != null) {

existBeat.setStopped(true);

}

dom2Beat.put(key, beatInfo);

executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);

MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());

}

复制代码

在倒数第二行可以看到,客户端是通过定时任务来处理心跳的,具体的心跳请求有BeatTask完成。定时任务的执行频次,封装在BeatInfo,回退往上看,会发现BeatInfo的Period来源于Instance#getInstanceHeartBeatInterval()。该方法具体实现如下:

public long getInstanceHeartBeatInterval() {

return this.getMetaDataByKeyWithDefault(“preserved.heart.beat.interval”, Constants.DEFAULT_HEART_BEAT_INTERVAL);

}

复制代码

可以看出定时任务的执行间隔就是配置的metadata中的数据preserved.heart.beat.interval,与上面提到配置heart-beat-interval本质是一回事,默认是5秒。

BeatTask类具体实现如下:

class BeatTask implements Runnable {

BeatInfo beatInfo;

public BeatTask(BeatInfo beatInfo) {

this.beatInfo = beatInfo;

}

@Override

public void run() {

if (beatInfo.isStopped()) {

return;

}

long nextTime = beatInfo.getPeriod();

try {

JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);

long interval = result.get(“clientBeatInterval”).asLong();

boolean lightBeatEnabled = false;

if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {

lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();

}

BeatReactor.this.lightBeatEnabled = lightBeatEnabled;

if (interval > 0) {

nextTime = interval;

}

int code = NamingResponseCode.OK;

if (result.has(CommonParams.CODE)) {

code = result.get(CommonParams.CODE).asInt();

}

if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {

Instance instance = new Instance();

instance.setPort(beatInfo.getPort());

instance.setIp(beatInfo.getIp());

instance.setWeight(beatInfo.getWeight());

instance.setMetadata(beatInfo.getMetadata());

instance.setClusterName(beatInfo.getCluster());

instance.setServiceName(beatInfo.getServiceName());

instance.setInstanceId(instance.getInstanceId());

instance.setEphemeral(true);

try {

serverProxy.registerService(beatInfo.getServiceName(),

NamingUtils.getGroupName(beatInfo.getServiceName()), instance);

} catch (Exception ignore) {

}

}

} catch (NacosException ex) {

NAMING_LOGGER.error(“[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}”,

JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());

}

executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);

}

}

复制代码

在run方法中通过NamingProxy#sendBeat完成了心跳请求的发送,而在run方法的最后,再次开启了一个定时任务,这样周期性的进行心跳请求。

NamingProxy#sendBeat方法实现如下:

public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {

if (NAMING_LOGGER.isDebugEnabled()) {

NAMING_LOGGER.debug(“[BEAT] {} sending beat to server: {}”, namespaceId, beatInfo.toString());

}

Map<String, String> params = new HashMap<String, String>(8);

Map<String, String> bodyMap = new HashMap<String, String>(2);

if (!lightBeatEnabled) {

bodyMap.put(“beat”, JacksonUtils.toJson(beatInfo));

}

params.put(CommonParams.NAMESPACE_ID, namespaceId);

params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());

params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());

params.put(“ip”, beatInfo.getIp());

params.put(“port”, String.valueOf(beatInfo.getPort()));

String result = reqApi(UtilAndComs.nacosUrlBase + “/instance/beat”, params, bodyMap, HttpMethod.PUT);

return JacksonUtils.toObj(result);

}

复制代码

实际上,就是调用了Nacos服务端提供的"/nacos/v1/ns/instance/beat"服务。

在客户端的常量类Constants中定义了心跳相关的默认参数:

static {

DEFAULT_HEART_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15L);

DEFAULT_IP_DELETE_TIMEOUT = TimeUnit.SECONDS.toMillis(30L);

DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5L);

}

复制代码

这样就呼应了最开始说的Nacos健康检查机制的几个时间维度。

服务端接收心跳


分析客户端的过程中已经可以看出请求的是/nacos/v1/ns/instance/beat这个服务。Nacos服务端是在Naming项目中的InstanceController中实现的。

@CanDistro

@PutMapping(“/beat”)

@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)

public ObjectNode beat(HttpServletRequest request) throws Exception {

// …

Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port);

if (instance == null) {

// …

instance = new Instance();

instance.setPort(clientBeat.getPort());

instance.setIp(clientBeat.getIp());

instance.setWeight(clientBeat.getWeight());

instance.setMetadata(clientBeat.getMetadata());

instance.setClusterName(clusterName);

instance.setServiceName(serviceName);

instance.setInstanceId(instance.getInstanceId());

instance.setEphemeral(clientBeat.isEphemeral());

serviceManager.registerInstance(namespaceId, serviceName, instance);

}

Service service = serviceManager.getService(namespaceId, serviceName);

// …

service.processClientBeat(clientBeat);

// …

return result;

}

复制代码

服务端在接收到请求时,主要做了两件事:第一,如果发送心跳的实例不存在,则将其进行注册;第二,调用其Service的processClientBeat方法进行心跳处理。

processClientBeat方法实现如下:

public void processClientBeat(final RsInfo rsInfo) {

ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor();

clientBeatProcessor.setService(this);

clientBeatProcessor.setRsInfo(rsInfo);

HealthCheckReactor.scheduleNow(clientBeatProcessor);

}

复制代码

ClientBeatProcessor同样是一个实现了Runnable的Task,通过HealthCheckReactor定义的scheduleNow方法进行立即执行。

scheduleNow方法实现:

public static ScheduledFuture<?> scheduleNow(Runnable task) {

return GlobalExecutor.scheduleNamingHealth(task, 0, TimeUnit.MILLISECONDS);

}

复制代码

再来看看ClientBeatProcessor中对具体任务的实现:

@Override

public void run() {

Service service = this.service;

// logging

String ip = rsInfo.getIp();

String clusterName = rsInfo.getCluster();

int port = rsInfo.getPort();

Cluster cluster = service.getClusterMap().get(clusterName);

List instances = cluster.allIPs(true);

for (Instance instance : instances) {

if (instance.getIp().equals(ip) && instance.getPort() == port) {

// logging

instance.setLastBeat(System.currentTimeMillis());

if (!instance.isMarked()) {

if (!instance.isHealthy()) {

instance.setHealthy(true);

// logging

getPushService().serviceChanged(service);

}

}

}

}

}

复制代码

在run方法中先检查了发送心跳的实例和IP是否一致,如果一致则更新最后一次心跳时间。同时,如果该实例之前未被标记且处于不健康状态,则将其改为健康状态,并将变动通过PushService提供事件机制进行发布。事件是由Spring的ApplicationContext进行发布,事件为ServiceChangeEvent。

通过上述心跳操作,Nacos服务端的实例的健康状态和最后心跳时间已经被刷新。那么,如果没有收到心跳时,服务器端又是如何判断呢?

服务端心跳检查


客户端发起心跳,服务器端来检查客户端的心跳是否正常,或者说对应的实例中的心跳更新时间是否正常。

Spring全套教学资料

Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。

目录:

部分内容:

Spring源码

  • 第一部分 Spring 概述
  • 第二部分 核心思想
  • 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

Spring MVC

目录:

部分内容:

一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

[外链图片转存中…(img-IRmMnOgx-1714700979088)]

[外链图片转存中…(img-MisRkhz9-1714700979088)]

Spring MVC

目录:

[外链图片转存中…(img-IeWm1F8M-1714700979088)]

[外链图片转存中…(img-nk39AAMy-1714700979089)]

[外链图片转存中…(img-fxq66z7T-1714700979089)]

部分内容:

[外链图片转存中…(img-1es3O3qf-1714700979089)]

[外链图片转存中…(img-nBbhV4od-1714700979090)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值