Nacos学习整理

配置中心:

新建配置:

Data-id:

配置中心用来区分和精准定位配置文件的属性

在Nacos-Server中新建配置,其中Data ID它的定义规则是:${prefix}-${spring.profiles.active}.${file-extension}

  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。
  • spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。

注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 {prefix}.{file-extension}

这里我创建Data Id 为pxsemic-demo.properties的配置文件,其中Group为指定应用pxsemic-demo,配置文件的格式也相应的选择properties,如图所示

Nacos的默认Group为DEFAULT_GROUP,可以在新建时根据需要进行切换

Nacos学习整理_第1张图片

个人开发使用nacos,建议直接变更group为自定义参数进行指定加载即可

环境切换方案:

一、Data ID方案

命名规则为:${prefix}-${spring.profiles.active}.${file-extension}

通过其中的spring.profile.active属性即可进行多环境下配置文件的读取

新建配置

1、启动Nacos-Server后,创建配置文件Data ID为:pxsemic-demo.properties, 其配置如下:

server.port: 10001

这里是dev环境下默认加载

2、继续创建配置文件Data ID为:pxsemic-demo-test.properties, 其配置如下:

server.port: 8899

这里是test环境

多环境测试

通过Idea启动demo项目,并指定spring.profiles.active,通过不同的环境进行启动

二、Group方案

可以通过指定group进行配置文件的指定切换

Nacos学习整理_第2张图片

只通过Group来进行多环境的区分的方式不推荐使用,因为涉及到了多环境自然就会改变spring.profile.active,而profile一旦生效,配置文件就会依据DataID的规则进行查找。所以Group的方式仅作参考。

Group的合理用法应该是配合namespace进行服务列表和配置列表的隔离和管理,如开发环境多开发人员各自持有一份配置文件。

三、namespace方案

Namespace命名空间进行环境隔离也是官方推荐的一种方式。Namespace的常用场景之一是不同环境的配置的区分隔离,例如:开发测试环境和生产环境的资源(如配置、服务)隔离等。

创建命名空间

创建命名空间dev和prod,不同的命名空间会生成相应的UUID,如下图

Nacos学习整理_第3张图片

可以通过指定spring.cloud.nacos.config.namespace来进行namespace的切换

Nacos学习整理_第4张图片

共享配置(shared-configs)和扩展配置(extension-config)

日常开发中,多个模块可能会有很多共用的配置,比如数据库连接信息,Redis 连接信息,MQ 连接信息,监控配置等等。那么此时,我们就希望可以加载多个配置,多个项目共享同一个配置之类等功能,Nacos Config 也确实支持。

  • Nacos在配置路径spring.cloud.nacos.config.extension-config下,允许我们指定⼀个或多个额外配置。
  • Nacos在配置路径spring.cloud.nacos.config.shared-configs下,允许我们指定⼀个或多个共享配置。

上述两类配置都⽀持三个属性:data-id、group(默认为字符串DEFAULT_GROUP)、refresh(默认为true)。

1、配置实例

spring.cloud.nacos.config.file-extension=properties


spring.cloud.nacos.config.extension-configs[0].data-id=api-conf.properties
spring.cloud.nacos.config.extension-configs[0].group=pxsemic-demo
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=comm-plugins.properties
spring.cloud.nacos.config.extension-configs[1].group=pxsemic-demo
spring.cloud.nacos.config.extension-configs[1].refresh=true
spring.profiles.active=test

#spring cloud中可以复用的配置  e.g.数据库连接
#spring.cloud.nacos.config.shared-configs[0].data-id=
##共享配置一般不指定group也可以根据应用区分
##spring.cloud.nacos.config.shared-configs[0].group=
#spring.cloud.nacos.config.shared-configs[0].file-extension=properties

参数解析:

  • data-id : Data Id
  • group:自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。
  • refresh: 控制该 Data Id 在配置变更时,是否支持应用中可动态刷新, 感知到最新的配置值。默认是不支持的。

注意:这里的Data ID后面是加.properties后缀的,且不需要指定file-extension。

三、共享配置和扩展配置的区

实际上,Nacos中并未对extension-configs和shared-configs的差别进⾏详细阐述。我们从他们的结构,看不出本质差别;除了优先级不同以外,也没有其他差别。那么,Nacos项⽬组为什么要引⼊两个类似的配置呢?我们可以从当初该功能的需求(issue)上找到其原始⽬的。

3.1 Nacos对配置的默认理念

  • namespace区分环境:开发环境、测试环境、预发布环境、⽣产环境。
  • group区分不同应⽤:同⼀个环境内,不同应⽤的配置,通过group来区分。

3.2 主配置是应⽤专有的配置

因此,主配置应当在dataId上要区分,同时最好还要有group的区分,因为group区分应⽤(虽然dataId上区分了,不⽤设置group也能按应⽤单独加载)。

3.3 要在各应⽤之间共享⼀个配置,请使⽤上⾯的 shared-configs

因此按该理念,shared-configs指定的配置,本来应该是不指定group的,也就是应当归⼊DEFAULT_GROUP这个公共分组。

3.4 如果要在特定范围内(⽐如某个应⽤上)覆盖某个共享dataId上的特定属性,请使⽤ extension-config

⽐如,其他应⽤的数据库url,都是⼀个固定的url,使⽤shared-configs.dataId = mysql的共享配置。但其中有⼀个应⽤demo是特例,需要为该应⽤配置扩展属性来覆盖。

3.5 关于优先级

1、上述两类配置都是数组,对同种配置,数组元素对应的下标越⼤,优先级越⾼。也就是排在后⾯的相同配置,将覆盖排在前⾯的同名配置。

  • 同为扩展配置,存在如下优先级关系:extension-configs[3] > extension-configs[2] > extension-configs[1] > extension-configs[0]。
  • 同为共享配置,存在如下优先级关系:shared-configs[3] > shared-configs[2] > shared-configs[1] > shared-configs[0]。

2、不同种类配置之间,优先级按顺序如下:主配置 > 扩展配置(extension-configs) > 共享配置(shared-configs)

注册中心:

服务注册中心,是一个给服务提供者注册服务、给服务消费者获取服务信息的地方,一般还提供服务列表查询、心跳检测等功能。注册中心为保证可用性一般集群部署。
注册中心组件我们可选的组件有Eureka、ZK、Nacos,ZK支持CP,Eureka支持AP,Nacos可支持AP也可支持CP。下面分别阐述下。

一、Eureka

Nacos学习整理_第5张图片

Eureka分服务端与客户端,服务端为注册中心,而客户端完成向服务端注册与服务发现。服务端的主要工作有:

  • 提供服务注册。提供一个统一存储服务的地方,客户端(服务提供者)将服务注册后服务端提供管理
  • 提供注册表。为客户端(服务消费者)提供服务列表的查询,也就是服务发现功能。客户端获取服务列表后一般会在本地进行缓存,以减少与注册中心交互
  • 服务剔除。如果客户端在一定时间内未上报心跳,服务端会剔除此服务实例
  • 自我保护机制。一段时间内如果客户端可用率低于一定比例则会进入自我保护阶段,防止Eureka客户端本身是可以正常访问的,但是由于网路通信故障等原因,造成Eureka服务端失去于客户端的连接,从而形成的不可用

客户端的功能这里只是简单罗列一下:服务注册、自动刷新缓存获取最新服务列表、服务续约上报心跳、远程调用、服务下线。

注册与发现的工作流程:

  1. server启动后,client启动后将服务注册到server
  2. client默认每30s向server发起心跳
  3. server若 90s没收到client的心跳请求,则统计15分钟内是否有超过85%的比例,如果有进入自我保护状态;如果没有则剔除该client
  4. client定时调用server接口获取服务列表更新本地缓存
  5. client远程调用时,先从本地缓存找,如果找到则直接发起调用,如果没有则先向server进行查询后再发起调用
  6. client关闭时会发http请求到server,server接受请求后将该实例剔除

Eureka是CAP里的AP
从CAP理论看,Eureka是一个AP系统,其优先保证可用性(A)和分区容错性,不保证强一致性,但能做到最终一致性。

  • 只要集群中任意一个实例不出现问题,Eureka服务就是可用的;即Eureka Client 在向某个 Eureka Server 注册时,如果发现连接失败,则会自动切换至其它节点;
  • Eureka集群中没有主从的概念,各个节点都是平等的,节点间采用Replicate异步的方式来同步数据;

由于Eureka并不强调一致性而侧重可用性,在设计上为提升性能采用了多级缓存的方案。这种设计和mysql的读写分离及JDK里的CopyOnWriteArrayList有点类似,目的是为了使操作不阻塞读操作。

Eureka数据存储机制

Eureka没有采用数据库这类存储介质,它的数据层分数据存储层缓存层。数据存储层记录注册到 Eureka Server 上的服务信息,缓存层是经过包装后的数据,可以直接在 Eureka Client 调用时返回。

存储层
我们先来看看数据存储层的数据结构,它底层是一个双层HashMap:

private final ConcurrentHashMap>> registry= new ConcurrentHashMap>>();
  • 第一层的 ConcurrentHashMap 的 key=spring.application.name 也就是客户端实例注册的应用名;value 为嵌套的 ConcurrentHashMap。
  • 第二层嵌套的 ConcurrentHashMap 的 key=instanceId 也就是服务的唯一实例 ID,value 为 Lease 对象,Lease 对象存储着这个实例的所有注册信息,包括 ip 、端口、属性等。

缓存层
接下来我们再来看看缓存层。

Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中。

  • 第一层缓存:readOnlyCacheMap,本质上是 ConcurrentHashMap,依赖定时从 readWriteCacheMap 同步数据,默认时间为 30 秒。

readOnlyCacheMap : 是一个 CurrentHashMap 只读缓存,这个主要是为了供客户端获取注册信息时使用,其缓存更新,依赖于定时器的更新,通过和 readWriteCacheMap 的值做对比,如果数据不一致,则以 readWriteCacheMap 的数据为准。

  • 第二层缓存:readWriteCacheMap,本质上是 Guava 缓存。

readWriteCacheMap:readWriteCacheMap 的数据主要同步于存储层。当获取缓存时判断缓存中是否没有数据,如果不存在此数据,则通过 CacheLoader 的 load 方法去加载,加载成功之后将数据放入缓存,同时返回数据。
readWriteCacheMap 缓存过期时间,默认为 180 秒,当服务下线、过期、注册、状态变更,都会来清除此缓存中的数据。

存储类型

数据结构

概述

readOnlyCacheMap 一级缓存

ConcurrentHashMap

周期更新,默认每30s从二级缓存readWriteCacheMap中同步数据更新;Eureka Client默认从这里获取服务注册信息,可配为直接从readWriteCacheMap获取

readWriteCacheMap 二级缓存

Guava Cache

服务有变动时实时更新,缓存时间180秒,如果不存在此数据,则通过 CacheLoader 的 load 方法去registry层加载

registry 存储层

双层HashMap

服务有变动时实时更新,又名注册表,UI界面从这里获取服务注册信息

客户端查询流程

客户端查询服务端数据的流程是怎样的呢,这里暂时不考虑客户端自身的缓存。

Eureka Client 获取全量或者增量的数据时,会先从一级缓存中获取;如果一级缓存中不存在,再从二级缓存中获取;如果二级缓存也不存在,这时候先将存储层的数据同步到缓存中,再从缓存中获取。

通过 Eureka Server 的二层缓存机制,可以非常有效地提升 Eureka Server 的响应时间,通过数据存储层和缓存层的数据切割,根据使用场景来提供不同的数据支持。

客户端缓存

客户端缓存只是简单提一下:
Eureka Client 也同样存在着缓存机制,Eureka Client 启动时会全量拉取服务列表,启动后每隔 30 秒从 Eureka Server 量获取服务列表信息,并保持在本地缓存中。

二、ZK

Nacos学习整理_第6张图片

ZK作为注册中心原理主发依赖于其自身的文件,ZK 的文件结构类似于 Linux 系统的树状结构,注册服务时,即在 ZK 中创建一个唯一的 znode 节点来保存服务的 IP、端口、服务名等信息;发现服务时,遍历树状结构文件找到具体的 znode 节点或者服务相关信息进行远程调用。

注册与发现的工作流程

  1. ZK 已经启动,服务提供者启动时把服务注册到 ZK 注册中心;
  2. ZK 注册中心和服务提供者之间建立一个 Socket 长连接,ZK 注册中心定时向每个服务提供者发数据包,如果服务提供者没响应,则剔除该服务提供者实例,把更新后的服务列表发送给所有服务消费者(即通知);
  3. 服务消费者启动时到 ZK 注册中心获取一份服务列表缓存到本地供以后使用;
  4. 服务消费者远程调用服务时,先从本地缓存找,如果找到则直接发起服务调用,如果没有则到 ZK 注册中心获取服务列表缓存到本地后再发起服务调用;
  5. 当其中一个服务提供者宕机或正常关闭时,ZK 注册中心会把该节点剔除,并通知所有服务消费者更新本地缓存
  6. 当这个服务提供者正常启动后,ZK 注册中心也能感知到,并通知所有服务消费者更新本地缓存。

ZK与Eureka的区别

  1. 根据 CAP 定律,ZooKeeper 支持 CP,Eureka 支持 AP。因为 ZK 集群中如果有节点宕机则需要选举 leader,选举过程需要 30 至 120 秒,选举过程时集群不可用,牺牲时间来保证数据一致性,因此支持 CP;而 Eureka 每个节点的数据都一致,没有主从节点之分,不需选举,如果其中一个节点宕机则马上切换到另外一个健康的节点上,保证可用性,因此支持 AP。
  2. 微服务架构当中,可用性比一致性更重要些,Eureka 比 ZooKeeper 更合适,而 ZooKeeper 更适合做分布式协调服务,比如:hadoop 集群。

这里简单提一下京东自研的RPC框架JSF,其注册中心为NameServer,与Eureka各节点一样,它们之间也是平等的、各节点存着全量数据,数据的同步采用的是消息总线的方式,显而易见,NameServer属于AP原则,通过消息总线来保证最终一致性。

三、Nacos

Nacos学习整理_第7张图片

Nacos既能作为注册中心也可以作为配置中心,下面是作为注册中心的流程:

  1. Nacos启动后,服务提供者启动时将服务注册到Nacos
  2. 服务提供者定时发送http请求,上报心跳
  3. Nacos长时间没收到服务提供者的心跳,则剔除该实例
  4. 服务消费者发现服务支持两种方式,一种是主动请求注册中心获取服务列表(图中左下角不推荐),一种是订阅注册中心的服务并提交一个 Listener,如果注册中心的服务有变更,由 Listener 来通知服务消费者更新本地服务列表(图中右下角)

Nacos学习整理_第8张图片

Nacos里的CP与AP

先看一下Nacos的架构图:

Nacos学习整理_第9张图片

我们可以看到Nacos是集成了Raft与Distro这两种一致性协议的,我们先从Distro入手,看下Nacos的设计机制:

  • 平等机制:nacos每个节点是平等的,都可以处理写请求,同时会将数据同步到其它节点。
  • 路由转发机制:客户端发送的写请求,如果属于自己则处理,否则路由转发给其它节点
  • 本地计机制:每个节点独立处理读请求,及时从本地发出响应,因为每个节点都存有全量数据
  • 异步复制机制:节点间通过1s的延迟任务,将数据同步给其它节点
  • 健康检查机制:每个节点只存部分数据,定期检查客户端状态保持数据一致性
  • 新节点同步机制:服务实例注册到nacos后,通过UDP的方式推送到所有服务实例,让其它服务实例感知到服务列表的变化

Nacos哪些地方用到了AP与CP呢?

  • 对于临时服务实例,采用AP来保证注册中心的可用性,Distro协议
  • 对于持久化服务实例,采用CP保证各个节点的强一致性,JRaft协议(Nacos对Raft的改造)
  • 对于配置中心,无Database 作为存储的情况下,Nacos 节点之间的内存数据为了保持一致,采用 CP
  • 对于配置中心,有 Database 作为存储的情况下,Nacos 通过持久化后通知其他节点到数据库拉取数据来保证数据一致性,另外采用读写分离架构来保证高可用,这里应该是AP

Nacos、ZK、Eureka区别

Eureka不能支撑大量服务实例,因为它的每个节点之间会产生大量心跳检查导致并发性能降低;ZK如果出现频繁上下线通知也会导致性能下降;Nacos可以支持大量服务实例而又不丢失性能,服务数量可达到10万级别。

实战:

一、开始:

使用nacos注册中心需要添加依赖如下:


  	com.alibaba.cloud
  	spring-cloud-starter-alibaba-nacos-discovery
  	2.2.9.RELEASE



  	org.springframework.boot
  	spring-boot-starter-web

正常添加上方依赖,nacos控制中心前端才能刷新注册客户端数据

二、客户端:

Nacos学习整理_第10张图片

在SpringBoot启动类上开启@EnableDiscoveryClient注解启用注册到注册中心

配置文件:

spring.cloud.nacos.discovery.server-addr=192.168.25.136:8848
spring.cloud.nacos.discovery.namespace=83b829ce-6ebd-46d9-bcdc-6f9d95f364a3
#group不指定默认为 DEFAULT_GROUP
spring.cloud.nacos.discovery.group=pxsemic-demo
#根据需求设定实例是否为持久化实例,默认为true
spring.cloud.nacos.discovery.ephemeral=false

nacos设置是否持久化实例发生变更且变更应用曾注册至nacos,需要将 \data\protocol\raft目录下注册元数据清除后重启nacos来进行应用的正常启动

根据此配置可以决定nacos使用CP/AP模式

Nacos学习整理_第11张图片

服务注册源码:

Nacos学习整理_第12张图片

Nacos心跳检测(客户端):

Nacos学习整理_第13张图片

心跳检测(服务端):

 /**
     * Create a beat for instance.
     *
     * @param request http request
     * @return detail information of instance
     * @throws Exception any error during handle
     */
    @CanDistro
    @PutMapping("/beat")
    @Secured(action = ActionTypes.WRITE)
    public ObjectNode beat(HttpServletRequest request) throws Exception {
        
        ObjectNode result = JacksonUtils.createEmptyJsonNode();
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());
        
        String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);
        RsInfo clientBeat = null;
        if (StringUtils.isNotBlank(beat)) {
            clientBeat = JacksonUtils.toObj(beat, RsInfo.class);
        }
        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();
        }
        String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
        String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
        NamingUtils.checkServiceNameFormat(serviceName);
        Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}, namespaceId: {}", clientBeat,
                serviceName, namespaceId);
        BeatInfoInstanceBuilder builder = BeatInfoInstanceBuilder.newBuilder();
        builder.setRequest(request);
        int resultCode = getInstanceOperator()
                .handleBeat(namespaceId, serviceName, ip, port, clusterName, clientBeat, builder);
        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;
@Override
public int handleBeat(String namespaceId, String serviceName, String ip, int port, String cluster,
    RsInfo clientBeat, BeatInfoInstanceBuilder builder) throws NacosException {
    // 获取服务 Service{namespace='public', group='DEFAULT_GROUP',
    // name='service-consumer', ephemeral=true, revision=0}
    Service service = getService(namespaceId, serviceName, true);
    // 获取客户端ID信息 192.168.60.1:1020#true
    String clientId = IpPortBasedClient.getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port,
    true);
    // 根据客户端ID信息从 clientManager 中获取注册的实例
    IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId);
    // 如果 client 为空或者发布者 publishers 信息集合里面没有该客户端实例那么就从新注册一个
    if (null == client || !client.getAllPublishedService().contains(service)) {
    // 如果心跳实体信息为空返回 20404,请求未找到
    if (null == clientBeat) {
    	return NamingResponseCode.RESOURCE_NOT_FOUND;
    }
    // 构建实例信息
    Instance instance = new Instance();
    // 设置端口信息
    instance.setPort(clientBeat.getPort());
    // 设置IP信息
    instance.setIp(clientBeat.getIp());
    	//设置权重
    	instance.setWeight(clientBeat.getWeight());
    	// 设置元数据
    	instance.setMetadata(clientBeat.getMetadata());
        // 设置集群名称
        instance.setClusterName(clientBeat.getCluster());
        // 设置服务名称
        instance.setServiceName(serviceName);
        // 设置实例ID信息
        instance.setInstanceId(instance.getInstanceId());
        // 设置虚拟节点
        instance.setEphemeral(clientBeat.isEphemeral());
        // 发起注册
        registerInstance(namespaceId, serviceName, instance);
        // 注册完后再一次获取 client 从 clientManager 中,
        // 因为注册的时候就是把这个客户端放入到 clientManager 中去的
        client = (IpPortBasedClient) clientManager.getClient(clientId);
    }
    // 注册时候已经将当前的 service 放入到了 ServiceManager.getInstance()的
    // ConcurrentHashMap singletonRepository 里面
    // 所以在这里面获取了一次所以会有的
    if (!ServiceManager.getInstance().containSingleton(service)) {
         throw new NacosException(NacosException.SERVER_ERROR,
                  "service not found: " + serviceName + "@" + namespaceId);
    }
    // 如果心跳信息为空那么就构建一个心跳信息
    if (null == clientBeat) {
        clientBeat = new RsInfo();
        clientBeat.setIp(ip);
        clientBeat.setPort(port);
        clientBeat.setCluster(cluster);
        clientBeat.setServiceName(serviceName);
    }
    // 创建心跳处理器任务
    ClientBeatProcessorV2 beatProcessor = new ClientBeatProcessorV2(namespaceId, clientBeat, client);
    HealthCheckReactor.scheduleNow(beatProcessor);
    client.setLastUpdatedTime();
    return NamingResponseCode.OK;
}

Nacos学习整理_第14张图片

Nacos与eureka区别(事件处理):

 @Override
    public void run() {
        if (Loggers.EVT_LOG.isDebugEnabled()) {
            Loggers.EVT_LOG.debug("[CLIENT-BEAT] processing beat: {}", rsInfo.toString());
        }
        // 获取ID
        String ip = rsInfo.getIp();
        // 端口号
        int port = rsInfo.getPort();
        // 服务名称
        String serviceName = NamingUtils.getServiceName(rsInfo.getServiceName());
        // 组名称
        String groupName = NamingUtils.getGroupName(rsInfo.getServiceName());
        // 获取服务
        Service service = Service.newService(namespace, groupName,
                                             serviceName, rsInfo.isEphemeral());
        // 获取健康检测实例发布信息里面多了几个属性,
        // 比如 lastHeartBeatTime 和 healthCheckStatus
        HealthCheckInstancePublishInfo instance =
            (HealthCheckInstancePublishInfo) client.getInstancePublishInfo(service);
        System.out.println("健康检测 ====== start =====+" + ip + "_" + port +
                           "+  HealthCheckInstancePublishInfo instance" +
                         instance.toString() + "date: " + new Date() + "====end====");
        if (instance.getIp().equals(ip) && instance.getPort() == port) {
            if (Loggers.EVT_LOG.isDebugEnabled()) {
                Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}",
                                      rsInfo.toString());
            }
            // 更新一下实例的心跳时间
            instance.setLastHeartBeatTime(System.currentTimeMillis());
            System.out.println("健康检测 ====== start =====+" + ip + "_" + port +
                           "+  HealthCheckInstancePublishInfo instance" +
                          instance.toString() + "date: " + new Date() + "====end====");
            // 如果实例是健康的那么就直接执行完任务,
            // 如果不是健康的那么就设置成健康的,之后发布服务改变事件和客户端改变事件
			if (!instance.isHealthy()) {
               instance.setHealthy(true);
               Loggers.EVT_LOG.info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{},
                                     "region: {}, msg: client beat ok",
                          rsInfo.getServiceName(), ip, port, rsInfo.getCluster(),
                                     UtilsAndCommons.LOCALHOST_SITE);
               NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service));
               NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client));
            }
    }
    
        

你可能感兴趣的:(java,spring,cloud,微服务,学习)