Nacos原理汇总

今天就应某位小伙伴的要求,来讲一讲Nacos作为服务注册中心底层的实现原理
不知你是否跟我一样,在使用Nacos时有以下几点疑问:

  • 临时实例和永久实例是什么?有什么区别?
  • 服务实例是如何注册到服务端的?
  • 服务实例和服务端之间是如何保活的?
  • 服务订阅是如何实现的?
  • 集群间数据是如何同步的?CP还是AP?
  • Nacos的数据模型是什么样的?

本文就通过探讨上述问题来探秘Nacos服务注册中心核心的底层实现原理。虽然Nacos最新版本已经到了2.x版本,但是为了照顾那些还在用1.x版本的同学,所以本文我会同时去讲1.x版本和2.x版本的实现
临时实例和永久实例
临时实例和永久实例在Nacos中是一个非常非常重要的概念,之所以说它重要,主要是因为我在读源码的时候发现,临时实例和永久实例在底层的许多实现机制是完全不同的
临时实例
临时实例在注册到注册中心之后仅仅只保存在服务端内部一个缓存中,不会持久化到磁盘,这个服务端内部的缓存在注册中心届一般被称为服务注册表
当服务实例出现异常或者下线之后,就会把这个服务实例从服务注册表中剔除
永久实例
永久服务实例不仅仅会存在服务注册表中,同时也会被持久化到磁盘文件中,当服务实例出现异常或者下线,Nacos只会将服务实例的健康状态设置为不健康,并不会对将其从服务注册表中剔除,所以这个服务实例的信息你还是可以从注册中心看到,只不过处于不健康状态,这是就是两者最最最基本的区别,当然除了上述最基本的区别之外,两者还有很多其它的区别,接下来本文还会提到,这里你可能会有一个疑问,为什么Nacos要将服务实例分为临时实例和永久实例?,主要还是因为应用场景不同,临时实例就比较适合于业务服务,服务下线之后可以不需要在注册中心中查看到,永久实例就比较适合需要运维的服务,这种服务几乎是永久存在的,比如说MySQL、Redis等等
MySQL、Redis等服务实例可以通过SDK手动注册
对于这些服务,我们需要一直看到服务实例的状态,即使出现异常,也需要能够查看时实的状态
所以从这可以看出Nacos跟你印象中的注册中心不太一样,他不仅仅可以注册平时业务中的实例,还可以注册像MySQL、Redis这个服务实例的信息到注册中心
在SpringCloud环境底下,一般其实都是业务服务,所以默认注册服务实例都是临时实例
当然如果你想改成永久实例,可以通过下面这个配置项来完成

spring
  cloud:
    nacos:
      discovery:
        #ephemeral单词是临时的意思,设置成false,就是永久实例了
        ephemeral: false

这里还有一个小细节,在1.x版本中,一个服务中可以既有临时实例也有永久实例,服务实例是永久还是临时是由服务实例本身决定的,但是2.x版本中,一个服务中的所有实例要么都是临时的要么都是永久的,是由服务决定的,而不是具体的服务实例,所以在2.x可以说是临时服务和永久服务。

为什么2.x把临时还是永久的属性由实例本身决定改成了由服务决定?
其实很简单,你想想,假设对一个MySQL服务来说,它的每个服务实例肯定都是永久的,不会出现一些是永久的,一些是临时的情况吧
所以临时还是永久的属性由服务本身决定其实就更加合理了

服务注册
作为一个服务注册中心,服务注册肯定是一个非常重要的功能,所谓的服务注册,就是通过注册中心提供的客户端SDK(或者是控制台)将服务本身的一些元信息,比如ip、端口等信息发送到注册中心服务端,服务端在接收到服务之后,会将服务的信息保存到前面提到的服务注册表中
1、1.x版本的实现
在Nacos在1.x版本的时候,服务注册是通过Http接口实现的

代码如下

整个逻辑比较简单,因为Nacos服务端本身就是用SpringBoot写的,但是在2.x版本的实现就比较复杂了
2、2.x版本的实现
2.1、通信协议的改变
2.x版本相比于1.x版本最主要的升级就是客户端和服务端通信协议的改变,由1.x版本的Http改成了2.x版本gRPC,gRPC是谷歌公司开发的一个高性能、开源和通用的RPC框架,Java版本的实现底层也是基于Netty来的,之所以改成了gRPC,主要是因为Http请求会频繁创建和销毁连接,白白浪费资源,所以在2.x版本之后,为了提升性能,就将通信协议改成了gRPC,根据官网显示,整体的效果还是很明显,相比于1.x版本,注册性能总体提升至少2倍,虽然通信方式改成了gRPC,但是2.x版本服务端依然保留了Http注册的接口,所以用1.x的Nacos SDK依然可以注册到2.x版本的服务端
2.2、具体的实现
Nacos客户端在启动的时候,会通过gRPC跟服务端建立长连接

 

这个连接会一直存在,之后客户端与服务端所有的通信都是基于这个长连接来的,当客户端发起注册的时候,就会通过这个长连接,将服务实例的信息发送给服务端,服务端拿到服务实例,跟1.x一样,也会存到服务注册表,除了注册之外,当注册的是临时实例时,2.x还会将服务实例信息存储到客户端中的一个缓存中,供Redo操作,所谓的Redo操作,其实就是一个补偿机制,本质是个定时任务,默认每3s执行一次,这个定时任务作用是,当客户端与服务端重新建立连接时(因为一些异常原因导致连接断开),那么之前注册的服务实例肯定还要继续注册服务端(断开连接服务实例就会被剔除服务注册表)
所以这个Redo操作一个很重要的作用就是重连之后的重新注册的作用,除了注册之外,比如服务订阅之类的操作也需要Redo操作,当连接重新建立,之前客户端的操作都需要Redo一下
小总结

  • 1.x版本是通过Http协议来进行服务注册的
  • 2.x由于客户端与服务端的通信改成了gRPC长连接,所以改成通过gRPC长连接来注册
  • 2.x比1.x多个Redo操作,当注册的服务实例是临时实例是,出现网络异常,连接重新建立之后,客户端需要将服务注册、服务订阅之类的操作进行重做

这里你可能会有个疑问,既然2.x有Redo机制保证客户端与服务端通信正常之后重新注册,那么1.x有类似的这种Redo机制么?,当然也会有,接下往下看。

 

 

心跳机制
心跳机制,也可以被称为保活机制,它的作用就是服务实例告诉注册中心我这个服务实例还活着

在正常情况下,服务关闭了,那么服务会主动向Nacos服务端发送一个服务下线的请求
Nacos服务端在接收到请求之后,会将这个服务实例从服务注册表中剔除
但是对于异常情况下,比如出现网络问题,可能导致这个注册的服务实例无法提供服务,处于不可用状态,也就是不健康
而此时在没有任何机制的情况下,服务端是无法知道这个服务处于不可用状态
所以为了避免这种情况,一些注册中心,就比如Nacos、Eureka,就会用心跳机制来判断这个服务实例是否能正常
在Nacos中,心跳机制仅仅是针对临时实例来说的,临时实例需要靠心跳机制来保活
心跳机制在1.x和2.x版本的实现也是不一样的
1.x心跳实现
在1.x中,心跳机制实现是通过客户端和服务端各存在的一个定时任务来完成的
在服务注册时,发现是临时实例,客户端会开启一个5s执行一次的定时任务

这个定时任务会构建一个Http请求,携带这个服务实例的信息,然后发送到服务端

在Nacos服务端也会开启一个定时任务,默认也是5s执行一次,去检查这些服务实例最后一次心跳的时间,也就是客户端最后一次发送Http请求的时间

  • 当最后一次心跳时间超过15s,但没有超过30s,会把这服务实例标记成不健康
  • 当最后一次心跳超过30s,直接把服务从服务注册表中剔除

这就是1.x版本的心跳机制,本质就是两个定时任务
其实1.x的这个心跳还有一个作用,就是跟上一节说的gRPC时Redo操作的作用是一样的
服务在处理心跳的时候,发现心跳携带这个服务实例的信息在注册表中没有,此时就会添加到服务注册表
所以心跳也有Redo的类似效果
2.x心跳实现
在2.x版本之后,由于通信协议改成了gRPC,客户端与服务端保持长连接,所以2.x版本之后它是利用这个gRPC长连接本身的心跳来保活
一旦这个连接断开,服务端就会认为这个连接注册的服务实例不可用,之后就会将这个服务实例从服务注册表中提出剔除
除了连接本身的心跳之外,Nacos还有服务端的一个主动检测机制
Nacos服务端也会启动一个定时任务,默认每隔3s执行一次
这个任务会去检查超过20s没有发送请求数据的连接
一旦发现有连接已经超过20s没发送请求,那么就会向这个连接对应的客户端发送一个请求
如果请求不通或者响应失败,此时服务端也会认为与客户端的这个连接异常,从而将这个客户端注册的服务实例从服务注册表中剔除
所以对于2.x版本,主要是两种机制来进行保活:
连接本身的心跳机制,断开就直接剔除服务实例
Nacos主动检查机制,服务端会对20s没有发送数据的连接进行检查,出现异常时也会主动断开连接,剔除服务实例
小总结
心跳机制仅仅针对临时实例而言
1.x心跳机制是通过客户端和服务端两个定时任务来完成的,客户端定时上报心跳信息,服务端定时检查心跳时间,超过15s标记不健康,超过30s直接剔除
1.x心跳机制还有类似2.x的Redo作用,服务端发现心跳的服务信息不存在会,会将服务信息添加到注册表,相当于重新注册了
2.x是基于gRPC长连接本身的心跳机制和服务端的定时检查机制来的,出现异常直接剔除
健康检查
前面说了,心跳机制仅仅是临时实例用来保护的机制
而对于永久实例来说,一般来说无法主动上报心跳
就比如说MySQL实例,肯定是不会主动上报心跳到Nacos的,所以这就导致无法通过心跳机制来保活
所以针对永久实例的情况,Nacos通过一种叫健康检查的机制去判断服务实例是否活着
健康检查跟心跳机制刚好相反,心跳机制是服务实例向服务端发送请求
而所谓的健康检查就是服务端主动向服务实例发送请求,去探测服务实例是否活着

 

健康检查机制在1.x和2.x的实现机制是一样的
Nacos服务端在会去创建一个健康检查任务,这个任务每次执行时间间隔会在2000~7000毫秒之间
当任务触发的时候,会根据设置的健康检查的方式执行不同的逻辑,目前主要有以下三种方式:
TCP
HTTP
MySQL
TCP的方式就是根据服务实例的ip和端口去判断是否能连接成功,如果连接成功,就认为健康,反之就任务不健康
HTTP的方式就是向服务实例的ip和端口发送一个Http请求,请求路径是需要设置的,如果能正常请求,说明实例健康,反之就不健康
MySQL的方式是一种特殊的检查方式,他可以执行下面这条Sql来判断数据库是不是主库

默认情况下,都是通过TCP的方式来探测服务实例是否还活着
服务发现
所谓的服务发现就是指当有服务实例注册成功之后,其它服务可以发现这些服务实例
Nacos提供了两种发现方式:
主动查询
服务订阅
主动查询就是指客户端主动向服务端查询需要关注的服务实例,也就是拉(pull)的模式
服务订阅就是指客户端向服务端发送一个订阅服务的请求,当被订阅的服务有信息变动就会主动将服务实例的信息推送给订阅的客户端,本质就是推(push)模式

参考博客:https://www.cnblogs.com/zzyang/p/17943039

posted @ 2024-11-06 09:33  郭慕荣  阅读(69)  评论(0编辑  收藏  举报