【Nacos源码之配置管理 六】集群模式下服务器之间是如何互相感知的

简介: 我们用Nacos当配置中心的时候,上一篇文章中【Nacos源码之配置管理 五】为什么把配置文件Dump到磁盘中 知道了,所有的配置文件都会Dump到服务器的本地磁盘中, 那么集群模式下:• [x] 服务器之间如何彼此感知发现的?• [x] 当某一台机器宕机挂掉之后怎么处理的?• [x] 如何获取本地Ip• [x] 获取服务器列表• [x] 服务器列表健康检查阅读完本文,会带你对上面的问题有个很清晰的认知;

Part1前言


我们用Nacos当配置中心的时候,上一篇文章中【Nacos源码之配置管理 五】为什么把配置文件Dump到磁盘中 知道了,所有的配置文件都会Dump到服务器的本地磁盘中, 那么集群模式下:

  • [x] 服务器之间如何彼此感知发现的?
  • [x] 当某一台机器宕机挂掉之后怎么处理的?
  • [x]  如何获取本地Ip
  • [x] 获取服务器列表
  • [x] 服务器列表健康检查

阅读完本文,会带你对上面的问题有个很清晰的认知;

Part2集群模式


我们先集群模式启动,开启调试

1集群模式启动

  • 配置集群机器列表;文件distribution/conf/cluster.conf 中配置所有的机器列表;IP:PORT的形式;例如
  • 执行打包命令


mvn -Prelease-nacos clean install -U  -Dmaven.test.skip=true

  • 打包完毕,执行启动脚本


sh distribution/target/nacos-server-{version}/nacos/bin/startup.sh

启动之后就可以进行远程调试了;怎么调试可以参考【Nacos源码之配置管理 一】阅读源码第一步,本地启动Nacos

2ServerListService 服务器列表


在看源码之前先说明一下Nacos读取服务器列表的两种方式

方式一:本地读取cluster.conf

每台服务器本地维护一份集群配置文件 cluster.conf

方式二:读取统一配置中心配置文件

在这里插入图片描述

ApplicationListener 监听器

ServerListService实现了SpringBoot的扩展类ApplicationListener;并且事件源是WebServerInitializedEvent:WebServer初始化的事件;通过WebServerInitializedEvent可以拿到WeServer的实例;通过WeServer.getPort()拿到启动的端口; 关于Spring的事件监听可以看 【Nacos源码之配置管理 二】Nacos中的事件发布与订阅--观察者模式

在ServerListService中就是通过这个获取Server的端口号

这个ServerListService是服务器列表,这里面保存着所有的服务器信息; 那么是如何获取所有服务器信息的呢?;接下来分析源码

初始化方法init

这个初始化的init方法里面做了什么事情呢?

Spring启动时,执行@PostConstruct 注解的初始化方法;

@Service
public class ServerListService implements ApplicationListener<WebServerInitializedEvent> {
    @Autowired
    private Environment env;
    @Autowired
    private ServletContext servletContext;
    private int port;
    @PostConstruct
    public void init() {
        serverPort = System.getProperty("nacos.server.port", "8848");
        String envDomainName = System.getenv("address_server_domain");
        if (StringUtils.isBlank(envDomainName)) {
            domainName = System.getProperty("address.server.domain", "jmenv.tbsite.net");
        } else {
            domainName = envDomainName;
        }
        String envAddressPort = System.getenv("address_server_port");
        if (StringUtils.isBlank(envAddressPort)) {
            addressPort = System.getProperty("address.server.port", "8080");
        } else {
            addressPort = envAddressPort;
        }
        addressUrl = System.getProperty("address.server.url",
            servletContext.getContextPath() + "/" + RunningConfigUtils.getClusterName());
        addressServerUrl = "http://" + domainName + ":" + addressPort + addressUrl;
        envIdUrl = "http://" + domainName + ":" + addressPort + "/env";
        defaultLog.info("ServerListService address-server port:" + serverPort);
        defaultLog.info("ADDRESS_SERVER_URL:" + addressServerUrl);
        isHealthCheck = PropertyUtil.isHealthCheck();
        maxFailCount = PropertyUtil.getMaxHealthCheckFailCount();
        try {
            String val = null;
            val = env.getProperty("useAddressServer");
            if (val != null && FALSE_STR.equals(val)) {
                isUseAddressServer = false;
            }
            fatalLog.warn("useAddressServer:{}", isUseAddressServer);
        } catch (Exception e) {
            fatalLog.error("read application.properties wrong", e);
        }
        GetServerListTask task = new GetServerListTask();
        task.run();
        if (null == serverList || serverList.isEmpty()) {
            fatalLog.error("########## cannot get serverlist, so exit.");
            throw new RuntimeException("cannot get serverlist, so exit.");
        } else {
            TimerTaskService.scheduleWithFixedDelay(task, 0L, 5L, TimeUnit.SECONDS);
        }
        httpclient.start();
        CheckServerHealthTask checkServerHealthTask = new CheckServerHealthTask();
        TimerTaskService.scheduleWithFixedDelay(checkServerHealthTask, 0L, 5L, TimeUnit.SECONDS);
    }
}
  1. 获取服务端口 serverPort; 可以通过设置Jvm属性nacos.server.port设置这个端口,例如启动脚本加上-Dnacos.server.port=8848;默认不填写情况端口是8848;
  2. 获取方式二中的域名地址domainName ,读取环境变量System.getenv("address_server_domain") ;如果环境变量没有获取到也可以通过Jvm属性 System.getProperty("address.server.domain", "jmenv.tbsite.net") 配置这个属性;如果都没有默认是jmenv.tbsite.net, 如果集群的机器列表是配置在本地(上面的方式一)其实这个domainName就没有什么作用,如果是方式二; 才会使用到这个;这个就是服务器列表配置中心的域名
  3. 获取方式二中的服务器列表配置中心的端口addressPort  ,先从环境变量中获取 System.getenv("address_server_port"),如果没有则从Jvm属性里面获取System.getProperty("address.server.port", "8080"); ;如果都没有配置默认就是8080;
  4. 获取方式二中的请求地址addressUrl ;默认/nacos/serverlist ;可以通过Jvm设置属性-Daddress.server.url=地址
  5. 最终的请求地址是  "http://" + domainName + ":" + addressPort + addressUrl;
  6. 根据配置文件中的属性useAddressServer=true/false 判断是否使用方式二这种服务器列表配置中心的形式;useAddressServer默认就是true
  7. 获取服务器列表 GetServerListTask ;在执行一次之后,开始每隔5秒执行一次
  8. 每隔5秒做一次服务器列表健康检查 CheckServerHealthTask

如果本地配置了cluster.conf,也配置了useAddressServer=true 会读取哪个?

就算 6 中的useAddressServer=true 也不一定会去请求读取远程的服务器列表;如果本地也配置了 cluster.conf的话,那么会优先读取本地的配置的; 如果本地的读取不到列表,才会去读取远程的服务器列表

本地cluster.conf的路径是到底在哪里?

private static String getClusterConfFilePath() {
        return NACOS_HOME + File.separator + "conf" + File.separator + "cluster.conf";
    }

{NACOS_HOME}/conf/cluster.conf

那NACOS_HOME是什么路径? 我在之前的文章 【Nacos源码之配置管理 四】DumpService如何将配置文件全部Dump到磁盘中   有讲过NACOS_HOME 的地址和配置;打开文章全文搜索一下NACOS_HOME 就可以看到;

getApacheServerList() 获取服务器列表的方法

这个方法就是获取服务器列表的方法的具体细节,代码我就不放出来,我直接说流程;

  1. 优先从本地文件读取服务列表,如果读取到了直接返回;
  2. 如果1中没有读取到,则判断useAddressServer=true;如果=true,则读取远程服务器中的服务器列表,如果读取到了直接返回;
  3. 如果2中执行了maxFailCount=12次还是没有获取到,则标识 isAddressServerHealth = false;;说明远程服务器挂掉了;
  4. 如果本地没有数据,并且useAddressServer=false;那么就会把自己的Ip加入到服务器列表;也就是说只有一台机器;
  5. 这个方法只是获取运维配置的集群服务器列表;并没有去检验每个集群列表的机器是否健康! 如果使用方式二;远程配置中心服务器不可访问那么返回的是一个空列表;

如何获取自己的Ip

上面的4中说到,把自己的Ip放入到服务器列表,这个自己的Ip是多少?

  1. 先看看Jvm属性配置了nacos.server.ip=IP地址没有;如果有就是它;
  2. 如果1中没有,则看看配置文件application.properties中有没有属性nacos.inetutils.ip-address=IP地址;如果有就是它
  3. 如果还没有,那判断是否优先使用hostname;preferHostnameOverIp 的判断逻辑是;先判断JVM属性有没有配置nacos.preferHostnameOverIp=true/false;如果false,再去判断配置文件application.properties中有没有属性 nacos.inetutils.prefer-hostname-over-ip=true/false;如果有的话 就优先获取hostname;   inetAddress.getHostName();
  4. 否则的话 就获取所有网卡中第一个非回环地址

selfIp = findFirstNonLoopbackAddress().getHostAddress();

就是不会找到 127.0.0.1这样的回环地址; 具体代码在类 InetUtils中;

GetServerListTask 每五秒重新获取一次

每五秒执行一次这个任务updateIfChanged方法见名思意就是如果服务器列表有更改(例如新上线,下线,宕机)的时候就要及时的把服务器列表更新一下;

 

class GetServerListTask implements Runnable {
        @Override
        public void run() {
            try {
                updateIfChanged(getApacheServerList());
            } catch (Exception e) {
                defaultLog.error("[serverlist] failed to get serverlist, " + e.toString(), e);
            }
        }
    }
  1. getApacheServerList()获取最新的服务器列表配置newList ; (这个时候并不知道这些服务器是否健康)
  2. ServerListService类中有 List全局属性   serverListUnhealth; 存放的是当前配置中(当前配置意思是,如果配置中移除了某个机器,那么这个对应的不健康服务器列表也要移除)不健康的服务器列表; (这个属性由谁维护,就是CheckServerHealthTask的做的事情)
  3. 如果最新的服务器列表newList中的Ip不存在在serverListUnhealth中了,就从serverListUnhealth中把这个Ip移除掉 ((可能的情况就是,运维知道某台服务挂掉了,就从服务器配置文件中把这个不健康的Ip手动移除;、)
  4. 发送服务器变更事件EventDispatcher.fireEvent(new ServerlistChangeEvent()); ;但是系统中还暂时没有监听这个事件的监听器;

至于EventDispatcher.fireEvent(new ServerlistChangeEvent()); 不懂的可以看我之前的文章 【Nacos源码之配置管理 二】Nacos中的事件发布与订阅--观察者模式

一句话总结这个作用: 每五秒查询最新的服务器列表配置,如果配置中把之前不健康的移除掉了,则也从属性serverListUnhealth中移除掉;

CheckServerHealthTask 服务器健康检查

系统会每隔5秒执行一次服务器健康检查,那么是怎么检查是否健康呢?其实就是给所有的服务器列表发起一个Http请求; 根据返回值判断是否健康

private void checkServerHealth() {
        long startCheckTime = System.currentTimeMillis();
        for (String serverIp : serverList) {
            // Compatible with old codes,use status.taobao
            String url = "http://" + serverIp + servletContext.getContextPath() + Constants.HEALTH_CONTROLLER_PATH;
            // "/nacos/health";
            HttpGet request = new HttpGet(url);
            httpclient.execute(request, new AyscCheckServerHealthCallBack(serverIp));
        }
        long endCheckTime = System.currentTimeMillis();
        long cost = endCheckTime - startCheckTime;
        defaultLog.debug("checkServerHealth cost: {}", cost);
    }

代码中可以看到,最终是发起了一个http请求;这个请求的链接是

String url = "http://" + serverIp + servletContext.getContextPath() + Constants.HEALTH_CONTROLLER_PATH;

解析得到的链接是http://ip:port/nacos/v1/cs/health一句话说就是,访问每个服务器列表的nacos/v1/cs/health 方法;包括自己的; 最终请求的是HealthController 这个类的getHealth方法

Http异步请求回调

上面的

httpclient.execute(request, new AyscCheckServerHealthCallBack(serverIp));

是一个异步请求;AyscCheckServerHealthCallBack实现了FutureCallback类;

class AyscCheckServerHealthCallBack implements FutureCallback<HttpResponse> {
        private String serverIp;
        public AyscCheckServerHealthCallBack(String serverIp) {
            this.serverIp = serverIp;
        }
        @Override
        public void completed(HttpResponse response) {
            if (response.getStatusLine().getStatusCode() == HttpServletResponse.SC_OK) {
                serverIp2unhealthCount.put(serverIp, 0);
                if (serverListUnhealth.contains(serverIp)) {
                    serverListUnhealth.remove(serverIp);
                }
                HttpClientUtils.closeQuietly(response);
            }
        }
        @Override
        public void failed(Exception ex) {
            Integer failCount = serverIp2unhealthCount.get(serverIp);
            failCount = failCount == null ? Integer.valueOf(0) : failCount;
            failCount++;
            serverIp2unhealthCount.put(serverIp, failCount);
            if (failCount > maxFailCount) {
                if (!serverListUnhealth.contains(serverIp)) {
                    serverListUnhealth.add(serverIp);
                }
                defaultLog.error("unhealthIp:{}, unhealthCount:{}", serverIp, failCount);
                MetricsMonitor.getUnhealthException().increment();
            }
        }
    }

上面实现的是,当请求成功(返回码:200)说明服务器健康; 如果之前是不健康的状态,则将其从serverListUnhealth中移除; 如果请求失败了;则将请求的服务器加入到serverListUnhealth中;

注意:这里的检查是否健康是判断 返回码:200; 并不是 HealthController这个类的 getHealth 方法返回的值; (能够请求到接口,说明服务器是健康的;并不关心方法返回了什么数据)

当某一台机器宕机挂掉之后怎么处理的

当服务器挂掉或者宕机; 每五秒的健康检查会检查到服务宕机了,会将其剔除;

目录
相关文章
|
Cloud Native Java Nacos
微服务时代的新宠儿!Spring Cloud Nacos实战指南,带你玩转服务发现与配置管理,拥抱云原生潮流!
【8月更文挑战第29天】Spring Cloud Nacos作为微服务架构中的新兴之星,凭借其轻量、高效的特点,迅速成为服务发现、配置管理和治理的首选方案。Nacos(命名和配置服务)由阿里巴巴开源,为云原生应用提供了动态服务发现及配置管理等功能,简化了服务间的调用与依赖管理。本文将指导你通过五个步骤在Spring Boot项目中集成Nacos,实现服务注册、发现及配置动态管理,从而轻松搭建出高效的微服务环境。
643 0
|
Dubbo Cloud Native 应用服务中间件
阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。
在云原生时代,微服务架构成为主流。阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。示例代码展示了如何在项目中实现两者的整合,通过 Nacos 动态调整服务状态和配置,适应多变的业务需求。
393 2
|
弹性计算 数据库连接 Nacos
阿里云ECS服务器在docker中部署nacos
docker pull nacos 失败,docker部署nacos遇到的问题,nacos数据库连接,nacos端口映射
680 1
|
数据管理 Nacos 开发者
"Nacos架构深度解析:一篇文章带你掌握业务层四大核心功能,服务注册、配置管理、元数据与健康检查一网打尽!"
【10月更文挑战第23天】Nacos 是一个用于服务注册发现和配置管理的平台,支持动态服务发现、配置管理、元数据管理和健康检查。其业务层包括服务注册与发现、配置管理、元数据管理和健康检查四大核心功能。通过示例代码展示了如何在业务层中使用Nacos,帮助开发者构建高可用、动态扩展的微服务生态系统。
404 0
|
监控 安全 网络安全
inishConnect(..) failed: Connection refused,服务本地正常服务器网关报400,nacos服务实例不能下线
总之,这种问题需要通过多方面的检查和校验来定位和解决,并可能需要结合实际环境的具体情况来进行相应的调整。在处理分布式系统中这类问题时,耐心和细致的调试是必不可少的。
396 13
|
Java 数据库连接 Nacos
nacos配置管理拉取不到配置异常
在搭建Nacos配置时遇到异常,因配置了`file-extension: yaml`,服务尝试拉取`shared-jdbc.yaml`, `shared-log.yaml`, `shared-swagger.yaml`,但Nacos中这些共享配置的Data ID无后缀。修正方法是确保Data ID与预期文件名一致,包括.yaml扩展名。在验证中,修改了部分Data ID并导致服务因找不到未加后缀的`jdbc`配置而报错,提示在配置Data ID时应包含文件扩展名。
759 1
|
关系型数据库 MySQL Java
“惊呆了!无需改动Nacos源码,轻松实现SGJDBC连接MySQL?这操作太秀了,速来围观,错过等哭!”
【8月更文挑战第7天】在使用Nacos进行服务治理时,常需连接MySQL存储数据。使用特定的SGJDBC驱动连接MySQL时,一般无需修改Nacos源码。需确保SGJDBC已添加至类路径,并在Nacos配置文件中指定使用SGJDBC的JDBC URL。示例中展示如何配置Nacos使用MySQL及SGJDBC,并在应用中通过Nacos API获取配置信息建立数据库连接,实现灵活集成不同JDBC驱动的目标。
357 0
|
负载均衡 Java Linux
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,
黑马头条01,环境搭建,今日头条的介绍,今日头条的功能架构图,技术栈的说明,服务层,nacos(奶靠丝)安装,安装在Linux服务器上环境准备,
|
开发框架 .NET Nacos
使用 Nacos 在 C# (.NET Core) 应用程序中实现高效配置管理和服务发现
使用 Nacos 在 C# (.NET Core) 应用程序中实现高效配置管理和服务发现
1550 0
|
1月前
|
弹性计算 运维 安全
阿里云轻量应用服务器与云服务器ECS啥区别?新手帮助教程
阿里云轻量应用服务器适合个人开发者搭建博客、测试环境等低流量场景,操作简单、成本低;ECS适用于企业级高负载业务,功能强大、灵活可扩展。二者在性能、网络、镜像及运维管理上差异显著,用户应根据实际需求选择。
217 10

热门文章

最新文章