Naocs

383 阅读9分钟

一、 Nacos服务注册

1、客户端处理

(1)自动装配服务注册相关的配置类

  • 从 Spring-cloud 开始
  • 首先看下一个包:spring-cloud-commons

image.png

  • 这个ServiceRegistry接口是SpringCloud提供的服务注册的标准,集成到SpringCloud中实现服务注册的组件,都需要实现这个接口。

  • 在spring.factories中配置EnableAutoConfiguration的内容后,项目在启动的时候,会导入相应的自动配置类,那么也就允许对该类的相关属性进行一个自动装配。那么显然,在这里导入了AutoServiceRegistrationAutoConfiguration这个类,而这个类顾名思义是服务注册相关的配置类

  • 这里不具体分析了,主要是调用的reigset()方法,最后调用的是Nacos Client SDK中的NamingService下的registerInstance()方法完成服务的注册

image.png

  • 心跳检测
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
    BeatInfo existBeat = null;
    if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }

    this.dom2Beat.put(key, beatInfo);
    // 通过schedule()方法,定时的向服务端发送一个数据包,然后启动一个线程不断地检测服务端的回应。
    // 如果在指定的时间内没有收到服务端的回应,那么认为服务器出现了故障。
    // 参数1:可以说是这个实例的相关信息。
    // 参数2:一个long类型的时间,代表从现在开始推迟执行的时间,默认是5000
    // 参数3:时间的单位,默认是毫秒,结合5000即代表每5秒发送一次心跳数据包
    this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}

(2)小结

  1. spring-cloud 存在一个接口,ServiceRegistry 在spring-cloud-common包下面,用于实现注册
  2. NacosServiceRegistry 实现了该接口,并通过NacosDiscoveryAutoConfiguration 进行装入IOC 容器
  3. MIN-INFO 下的 spring-factorys 会加载 AutoServiceRegistrationAutoConfiguration 服务配置类
  4. 至此 IOC 容器中 存在了 服务发现实现类 和 服务发现配置类
  5. WebServer初始化完成后,利用事件发布, 去调用 NacosServiceRegistryregister() 方法,去调用namingService.registerInstance方法进行服务注册
  6. 注册的时候,首先会发起一个定时任务schedule,5秒钟一次和服务端进行一次心跳保活 ===》心跳注册
  7. 当和服务器通信正常的时候,最后通过 openAPI 进行真正的注册===》服务注册
  8. 注意。心跳和注册所用的API 是一个重载方法
  9. 如果是参数beat信息的话,说明是第一次发起心跳,则会带有服务实例信息,因为发起心跳成功则服务端会返回下次不要带beat信息的参数,这样客户端第二次就不会携带beat信息了。如果发现没有该服务,又没带beat信息,说明这个服务可能被移除过了,直接返回没找到。如果没有服务,但是发现有beat信息,那就从beat中获取服务实例信息,进行注册。

2、服务端处理

(1) 注册

  • 调用服务注册接口,初始化参数,调用实例注册

image.png

  • 先是解析出来instance,就是根据client发送的那堆参数解析出来的。

  • 接着就是调用serviceManager组件进行实例注册,这个serviceManager 组件在注册中心是个核心组件,服务注册,下线,获取服务列表啥的,都是找这个组件的。

  • 第一次注册的时候,实例肯定不存在

    • 会创建服务,并将其加入到本地缓存,通过健康检测定时线程,进行扫描,同时添加两个监听器,实现后续的异步操作
  • 需要注意的是 ,对于service的操作都是异步的,通过service 注册的两个监听器,去监听 进行异步回调,后续的 删除,更新,下线,都是通过这种方案实现。

(2) 下线

  • Nacos Server会开启一个定时任务来检查注册服务的健康情况

  • 对于超过15秒没收到客户端的心跳实例会将它的 healthy属性置为false,此时当客户端不会将该实例的信息发现,

  • 如果某个服务的实例超过30秒没收到心跳,则剔除该实例,如果剔除的实例恢复,发送心跳则会恢复。 当有实例注册的时候,我们会看到有个service.init()的方法,该方法的实现主要是将ClientBeatCheckTask加入到线程池当中,如下图:

(3)小结

Nacos服务端接受注册请求

  1. 调用服务注册接口,初始化参数,调用实例注册

  2. 从缓冲中获取对应的 IP 和端口额实例、

  3. 如果实例不存在,注册该实例,

  4. 告诉客户端 变成轻量级心跳,同时会去更新每个实例的上一次心跳信息

  5. 维护一个定时让你购物,

    1.naocs 服务端 循环所有临时状态的实例,如果实例离上次执行心跳间隔超出了设置的心跳间隔时间,将健康状态设置为false

    2.nacos 服务端 循环所有临时状态的实例,如果实例离上次执行心跳间隔超出了设置的心跳删除时间,将实例删除

二、服务发现

  • nacos支持两种服务发现方式

    • 一种是直接去nacos服务端拉取某个服务的实例列表,就像eureka那样定时去拉取注册表信息

    • 一种是服务订阅的方式,就是订阅某个服务,然后这个服务下面的实例列表一旦发生变化,nacos服务端就会使用udp的方式通知客户端,并将实例列表带过去

  • 服务注册成功之后,消费者就可以从nacos server中获取到服务提供者的地址,然后进行服务的调用。在服务消费中,有一个核心的类 NacosDiscoveryClient 来负责和nacos交互,去获得服务提供者的地址信息。

  • 客户端进行调用远程服务的时候,最后都会走到NacosDiscoveryClientgetInstances 方法中,而后将我们说需要的group 、serviceId 传过去,获取基于该serviceId的实例列表,然后把instance转化为ServiceInstance对象

1、客户端直接拉去

  • 简单来说就说每一次都进行一次请求,当服务端检测到变化的时候,不进行推送
  • 较为简单

2、订阅模式

20201217184321302.png

  • 简单来说就说,当获取了请求之后,会在服务端进行一次注册,服务端会通过pushService 组件主要就是用来进行推送的, 比如我们订阅了某个服务,然后这个服务下面的实例信息发生了变化,pushService组件就会通知所有的订阅客户端,将新的数据给客户端推过去。
  • 需要注意的是,这里也是异步的

3、小结

  • 不订阅的服务发现非常简单,就是客户端发起请求直接去服务端拉取订阅的服务实例列表就可以了,

  • 如果是订阅的话,客户端先去服务端拉取订阅服务列表,并且客户端带上自己本地的udp端口给服务端,服务端会根据udp与客户端版本判断能不能订阅,能的话服务端根据订阅信息生成一个PushClient放到一个clientMap中,到时候服务实例信息改变的时候,首先通过异步处理,去调用listen,进行onchange的修改,在listen 的最后一行,回触发一个getPushService().serviceChanged(this);方法,就会从这个clientMap 取到这个PushClient信息,然后进行推送。

  • 在客户端方面,最开始生成的时候,就会生成一个PushReceiver,和一个接受的线程城池。进行阻塞,等待UDP 数据的相应,和回复UDP 的ack 给服务端,在run方法中,不断循环监听服务端的push请求。然后调用 processServiceJSON 对服务端的数据进行解析。 EventDispatcher 组件中有个Notifier 的任务会不断从这个队列中获取serviceInfo ,

image.png

三、调用的大致流程

  • 简单说一下,通过OpenFeign 调用的大致流程
  1. 通过 @EnableFeignCleints 注解启动 Feign Starter 组件

  2. Feign Starter 在项目启动过程中注册全局配置,扫描包下所有的 @FeignClient 接口类,并进行注册 IOC 容器

  3. @FeignClient 接口类被注入时,通过 FactoryBean#getObject 返回动态代理类

  4. 接口被调用时被动态代理类逻辑拦截,将 @FeignClient 请求信息通过编码器生成 Request

  5. 同时通过 Naococlient 去获取到服务的列表数据

  6. 交由 Ribbon 进行负载均衡,挑选出一个健康的 Server 实例

  7. 继而通过 Client 携带 Request 调用远端服务返回请求响应

  8. 通过解码器生成 Response 返回客户端,将信息流解析成为接口返回数据

四、配置中心

  • 客户端通过创建 configService

  • 启动两个线程池,一个单线程的线程池执行定时任务,每个10毫秒去检查一次配置信息,并将多个任务打包发送给另一个缓冲线程池,让另缓冲线程池去具体执行分发的每个任务

  • 缓冲线程池里面具体干两件事,

    • 一个是本地检查,取出和 taskID 相关联的 CacheData 保存到本地(需要做MD5检查,查看是否需要通知listen)用于容灾处理,会保存到本地磁盘,
    • 另一个是去请求服务端,: 通过LongPollingRunnable 中的 checkUpdateDataIds()方法从服务端获取那些值发生了变化的 dataId 列表,通过服务端返回changedGroupKeys,后继续向服务端发起请求最新的数据信息,接着将最新的配置信息保存到 CacheData 中
  • 总结下===》httpagetn 是发起长轮询的,要么返回空值,要么返回changedGroupKeys,这里判断是阻塞类型的,会阻塞再checkUpdateDataIds 方法上

2、长轮询修改配置

ClientLongPolling 被提交给 scheduler 执行之后,实际执行的内容可以拆分成以下四个步骤:

  • 1.创建一个调度的任务,调度的延时时间为 29.5s
  • 2.将该 ClientLongPolling 自身的实例添加到一个 allSubs 中去
  • 3.延时时间到了之后,首先将该 ClientLongPolling 自身的实例从 allSubs 中移除
  • 4.获取服务端中保存的对应客户端请求的 groupKeys 是否发生变更,将结果写入 response 返回给客户端
  • 客户端的延迟时间是 30s,服务端会在29.5s如果没有发生变化,就会返回 image.png