写点什么

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

  • 2022 年 10 月 07 日
    江西
  • 本文字数:6567 字

    阅读完需:约 22 分钟

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

作者石臻臻,CSDN 博客之星 Top5Kafka Contributornacos Contributor华为云 MVP,腾讯云 TVP,滴滴 Kafka 技术专家 KnowStreaming


KnowStreaming 是滴滴开源的Kafka运维管控平台, 有兴趣一起参与参与开发的同学,但是怕自己能力不够的同学,可以联系我,当你导师带你参与开源!

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 注解的初始化方法;

@Servicepublic 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 方法返回的值; (能够请求到接口,说明服务器是健康的;并不关心方法返回了什么数据)

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

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

发布于: 刚刚阅读数: 3
用户头像

关注公众号: 石臻臻的杂货铺 获取最新文章 2019.09.06 加入

进高质量滴滴技术交流群,只交流技术不闲聊 加 szzdzhp001 进群 20w字《Kafka运维与实战宝典》PDF下载请关注公众号:石臻臻的杂货铺

评论

发布
暂无评论
【Nacos源码之配置管理 六】集群模式下服务器之间是如何互相感知的_十月月更_石臻臻的杂货铺_InfoQ写作社区