深度解析nacos源码之注册中心(服务续约与故障下线)

原创不易,转载请注明出处
本文基于nacos1.4.0


前言

其实这个服务续约词是来自eureka里面,也就是renew,就是告诉服务注册中心我这服务还活着,你别把我删了,然后这个服务故障下线就是,服务注册中心在一定范围内没有收到某个服务的心跳信息,就认为你这个服务发生故障了,就会从注册表中将这个服务剔除掉,在nacos中心跳就是服务续约,同样也有服务故障下线功能。本文将先解析下nacos客户端心跳实现源码,与服务端接收心跳请求处理源码,接着就是解析下服务故障下线源码实现,将客户端心跳与服务故障下线结合起来。

1.心跳

1.1 客户端发送心跳

在NacosNamingService 实例化(也就是执行 NamingService naming = NamingFactory.createNamingService(properties);这行代码创建NamingService)的时候,有个init方法进行初始化,在初始化方法中有这么一行

this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));

创建了BeatReactor 组件,这个组件其实就是发送心跳的,第一个参数是serverProxy,你可以把它简单看成一个发送http的组件,不用太多care它,第二个参数是调用initClientBeatThreadCount 方法来算出一个线程数,这个线程数是根据你cpu 的核心数来计算的,如果你cpu核心数是大于1的,它的数量就是 cpu核心数/2,如果核心数就一个,那它就是1。
看下这个beatReactor的构造方法
在这里插入图片描述
创建了一个任务线程池,线程数就是我们上面说的那个。好了,到这beatReactor 初始化我们就了解了,我们再看下服务注册的时候有这么几行代码
在这里插入图片描述
如果是临时节点的话,创建一个beatInfo实体,然后调用beatReactor的addBeatInfo方法,这里这个beatInfo实体其实没啥好看的,里面就一个serviceName 与instance信息。我们看下这个beatReactor的addBeatInfo方法。
在这里插入图片描述
这个就非常重要了,先是生成一个key,然后将key与beatInfo实体塞到dom2beat这个map中(这个map其实就是防止任务重复的,这里就是多一嘴,可以自己慢慢体会),接着就会封装一个beatTask然后 扔到任务调度线程池中,延迟默认5s执行(这个玩意就是心跳间隔)。
好了,没啥好看的了,直接看beatTask 这个任务是怎么执行的吧。
在这里插入图片描述
这里有几个重要的点,1是调用 serverProxy的sendBeat方法向服务端发送心跳消息;2是从服务端返回的响应中获取这个发送心跳间隔与lightBeatEnabled的配置;3是如果返回服务没找到的话,也就是RESOURCE_NOT_FOUND 这个code,就会创建一个instance对象,进行注册服务;4是往executorService 任务线程池中添加这个任务,间隔可能是调整过的,可能是原来的5s,就是为了实现每几秒发送一下心跳。
这里重点关注调用 serverProxy的sendBeat方法向服务端发送心跳消息
在这里插入图片描述
这里就是封装请求参数,调用reqApi 方法进行发送心跳请求,这里lightBeatEnabled 这个参数明白了,就是请求的时候要不要将 beatInfo带过去,默认是带过去的,请求uri是/nacos/v1/ns/instance/beat ,然后请求方式是put,往下我们就不看了,其实就是找哪个server进行发送,与服务注册,服务下线是一样的,先随机找一个server,如果失败的话就下个server轮询发送,重试次数不超过server的个数。

1.2 服务端接收心跳

这个心跳请求是走了InstanceController 的beat 方法处理的,方法前半部分是一堆解析请求参数的,我们就不看了,直接看下半部分的代码
在这里插入图片描述
先是根据namespaceId, serviceName, clusterName, ip, port 这个参数调用 ServiceManager的getInstance 获取对应的instance,其实就是先根据namespace从serviceMap中获取对应的service,接着根据cluster从service的clusterMap中获取对应cluster的instance集合,然后再遍历比对ip与port。
如果没有找到对应的instance,而且beatInfo不是null,就会进行服务注册。
接着就是根据namespace与serviceName获取service,然后调用service的processClientBeat 方法处理心跳。这个processClientBeat 方法我们后面看,先看下后面这个有意思的,它往这个返回值中塞了clientBeatInterval 与lightBeatEnabled 参数值,这clientBeatInterval 就是心跳间隔,lightBeatEnabled 就是带不带beatInfo,这时候lightBeatEnabled 返回的就是true了,也就是下次不带了,看来这个心跳间隔是可以随时调整的,而且不用动服务,在控制台修改下某个实例的元数据就可以了。
接下来看下service是怎样处理请求的
在这里插入图片描述
封装一个ClientBeatProcessor ,然后交给了HealthCheckReactor 的scheduleNamingHealth 方法,其实就是给了一个健康检查的线程池处理了。看下ClientBeatProcessor 这个任务里面怎样执行的。
在这里插入图片描述
其实就是通过namespace/serviceName/cluster/ip/port找到对应的instance对象,重新设置一下LastBeat 的时间,也就是

instance.setLastBeat(System.currentTimeMillis());

这行,接着就是判断,如果不健康的话,就更改健康状态是true,也就是改成健康。最后getPushService().serviceChanged(service);这行需要注意下,健康状态改变了,会引起它 将新的instance信息推送到那堆服务订阅者客户端上,这个服务订阅发布我们后面会介绍。
好了,到这我们服务端对心跳消息的处理就结束了,可以看到,处理心跳消息也是异步的,将处理封装成task投寄到线程池,然后就直接返回给客户端了,由线程池执行这个task。

2.故障下线

故障下线我们要从创建service对象说起,我们在服务注册的时候,上来就会根据namespace与serviceName去serviceMap中获取对应的service对象,如果没有的话,就要创建了
在这里插入图片描述
就是这么一个方法,创建完成一个service 之后,会设置一堆东西,比如说serviceName,namespace,group等等,最后会调用putServiceAndInit 方法将service存到serviceMap中并且初始化。
在这里插入图片描述
这里我们主要看下这个service的init方法,也就是初始化方法。
在这里插入图片描述
往健康检查组件中添加了一个调度任务。。。。我们看下这个HealthCheckReactor 的scheduleCheck 方法。
在这里插入图片描述
GlobalExecutor.scheduleNamingHealth 就是向健康检查线程池中提交了一个任务,返回一个future,然后将这个future存到了futureMap中,这里我们主要关心的是向健康检查线程池中提交了一个任务 ,可以看到是每5s 调度一次。
我们来看下clientBeatCheckTask这个任务的run方法实现。由于方法比较长,我们一段一段来看
在这里插入图片描述
这部分主要是获取到这个service下面所有的instance,然后遍历这堆instance,判断如果instance的lastbeat时间距离当前时间已经超过了15s,也就是preserved.heart.beat.timeout 这个参数控制的,如果没有被标记,就会检查健康状态,如果是健康就更改健康状态,并通知,并且推送实例心跳超时时间
在这里插入图片描述
这部分就是判断是否过期instance,默认是true的,就是会过期实例(这里比较拗口,其实就是判断参数要不要过期服务,如果是false的话,就算是长时间没有心跳,也不会摘掉服务)
接着就是遍历所有的实例instance,如果已经被标记就跳过,如果没有的话,判断实例的lastBeat时间距离现在时间是否超过了30s,如果查过的话,就走deleteIp删除这个实例
在这里插入图片描述
可以看到这个deleteIp 方法其实就是组装了个http请求,然后向本机发送服务下线请求,可以看到组装请求参数,然后请求url是“http://127.0.0.1:端口/context/v1/ns/instance” 走的是delete 请求方式,往下我们就不看了,服务下线这块可以参考
深度解析nacos源码之注册中心(服务主动下线)》这篇文章。

总结

我们总结一下,首先是心跳机制,客户端服务注册的时候,会生成一个beatInfo,然后添加到beatReactor这个组件中,其实就是添加到任务调度线程池中,每5s向服务端发送一次心跳。接着就是服务端接收到这个心跳消息之后,就会生成一个ClientBeatProcessor ,然后扔到线程池里面执行,就是找到对应的instance,修改lastbeat 这个字段,如果是不健康状态的话,就会修改成健康状态,并通知。
最后就是服务故障扫描了,在服务注册的时候,如果没有对应的service对象,就会创建并初始化,在service初始化的过程中,会创建一个task交给这个健康检查组件,然后这个健康检查组件会扔到任务线程池中,然后是每5s执行一次这个task,这个task里面主要是遍历这个service 下面所有的instance ,然后判断lastbeat 距离当前时间是否超过了15s如果超过15的话,就会将健康状态的instance标记成不健康,然后告诉pushService进行通知,这个pushService 其实就是专门通知那些订阅这个instance 订阅者或者是观察者,再就是发送站内的instance超时事件。接着就是判断是否要需要实例过期,默认是需要的,遍历所有的instance,找出lastbeat距离当前时间超过30s的,然后调用deleteIp方法进行服务下线,其实就是组装http服务下线的请求,发送给本机,好了,到这我们服务续约与服务故障下线就ok了。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值