精彩推荐
一百期Java面试题汇总SpringBoot内容聚合IntelliJ IDEA内容聚合Mybatis内容聚合
最近正在负责将公司内部的服务注册中心做转移工作,正准备切入到nacos注册中心,于是花了些时间去整理学习关于使用nacos的一些笔记,并进行一番文档的输出。

使用版本 nacos-1.1.4版本

nacos注册中心的简单介绍

nacos是一款阿里巴巴开源的注册中心+配置中心,除此之外nacos还有很多强大的功能。

nacos的文件目录

在nacos安装包底下,我们可以大概看到nacos包含了以下几类文件夹,不同的文件底下存储了不同的数据信息。


最后在target文件夹底下发现了nacos的jar包,因此我好奇地将其打开来一探究竟。发现内部的jar将nacos的管理台源代码给打包了一份。

通过阅读源码发现在工程的内部有个lib目录文件夹,这里面似乎有自己希望找到的内容

但是目前还是没法看到内部的源码,在工程的外部主要就是结合一些源码的api做了一套可视化界面的控制台。

这里面有份jar命名为:nacos-server.jar。

在startup.sh脚本里面可以看到,nacos有着对应的脚本细节:

这里对应了java -jar的命令关键行进行控制台的启动:

配置中心源码分析

如何进行本地源码的debug

选择Nacos工程,然后设置对应的启动参数:

-Dnacos.standalone=true -Dnacos.home=F:\nacos-local-config

从github下载一份nacos的源码之后可以看到内部的基础结构为:

整体项目里面对应的工程有好几个,这里我们选择了Config工程这个模块进行分析,因为这里面包含了nacos控制台中的拉去服务详情,查看配置列表等常用接口,有助于我们对工作中常用功能的深入理解。

参照控制台的接口路径很快能定位到controller内容

下边这段接口是对应了查看配置属性的内容:

http://127.0.0.1:8848/nacos/v1/cs/configs?search=accurate&accessToken=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyIsImV4cCI6MTU5MDE1MzAwM30.TFlSFPTzKd0-2zmmsFjnghV74JfL_tWwo6BOFYAfdjk&dataId&group&pageNo=1&pageSize=10

配置信息查询接口对应源代码:

com.alibaba.nacos.config.server.controller.ConfigController#searchConfig --->com.alibaba.nacos.config.server.service.repository.PersistService#findConfigInfo4Page

在com.alibaba.nacos.config.server.service.repository.EmbeddedStoragePersistServiceImpl#findConfigInfo4Page里面看到有关于sql的查询,深入内部去查看:

单机版本的nacos使用的是 derby 数据库进行配置存储的持久化


具体的初始化步骤位于:com.alibaba.nacos.config.server.service.datasource.LocalDataSourceServiceImpl#init

初始化的时候会链接本地数据库:jdbc:derby:F:\nacos-local-config\data\derby-data;create=true

单机版本的nacos重启之后数据并不会丢失,依旧是从本地的存储文件中读取数据信息。

derby数据库的介绍

官网地址:http://db.apache.org/derby/ 一 款java语言编写的内嵌于jvm的数据库,可以支持sql查询,以及jdbc协议,关于其持久化,大概推断是存储到了指定的目录文件下边:


服务列表源码分析

服务注册原理跟踪

根据debug会发现,在com.alibaba.nacos.naming.core.ServiceManager 类里面包含了相关的服务列表存储信息:

在源码里面会发现存储这些服务列表的本质就是一个ConcurrentHashMap数据结构:

(采用了ConcurrentHashMap来解决并发冲突问题,1.8之前是采用了分段锁,但是这种方式的锁粒度过大,所以后边改为了采用cas+synchronized的方式来进行加锁,通过使用无所插入头结点,如果插入失败,说明同一时刻有其他线程进行头插入,再次循坏插入)

private Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

有点奇怪,这个map的数据是存储在内存里面的,那么服务在重启的时候应该是有进行初始化操作。并且当我们将provider的服务下架之后nacos依旧会有服务信息,在服务关闭之后的三十秒后nacos就查询不出任何信息了。

借此推测会有一个调度去专门维护这些数据信息。(猜测是心跳机制)

注册服务信息到nacos的接口:

/nacos/v1/ns/instancecom.alibaba.nacos.naming.controllers.InstanceController#register->com.alibaba.nacos.naming.core.ServiceManager#registerInstance

那么,假设我们通过启动dubbo工程,注册dubbo服务到nacos服务中心之后会看到哪些情况呢?

发现循环调用某些接口
•【DistroFilter request url】/nacos/v1/ns/instance/beat
•【DistroFilter request url】/nacos/v1/ns/instance/list

通过日志过滤发现会循环调用这两个接口,后来查询文档估计是某些调度在维护两端的数据。

客户端会重复发送心跳包到nacos这边,这份心跳包包含的数据还挺多的。关于心跳模块涉及到的类为:

com.alibaba.nacos.client.naming.beat.BeatReactor

发送的心跳数据基本格式通过BeatInfo格式进行数据传输。

关于循环发送心跳数据包的核心是借助了jdk内部的

ScheduledExecutorService

这个api来实现的,相关模板代码:


这样就能实现每个三秒发送一次心跳的功能。

同理,在nacos的服务端和客户端之间也存在心跳协调的代码:

 class BeatTask implements Runnable {        BeatInfo beatInfo;        public BeatTask(BeatInfo beatInfo) {            this.beatInfo = beatInfo;        }        @Override        public void run() {            if (beatInfo.isStopped()) {                return;            }            long nextTime = beatInfo.getPeriod();            try {            //发送心跳包                JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);                long interval = result.getIntValue("clientBeatInterval");                boolean lightBeatEnabled = false;                if (result.containsKey(CommonParams.LIGHT_BEAT_ENABLED)) {                    lightBeatEnabled = result.getBooleanValue(CommonParams.LIGHT_BEAT_ENABLED);                }                BeatReactor.this.lightBeatEnabled = lightBeatEnabled;                if (interval > 0) {                    nextTime = interval;                }                int code = NamingResponseCode.OK;                if (result.containsKey(CommonParams.CODE)) {                    code = result.getIntValue(CommonParams.CODE);                }                if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {                //如果服务实例消失或者不存在,则注册一个服务实例                    Instance instance = new Instance();                    instance.setPort(beatInfo.getPort());                    instance.setIp(beatInfo.getIp());                    instance.setWeight(beatInfo.getWeight());                    instance.setMetadata(beatInfo.getMetadata());                    instance.setClusterName(beatInfo.getCluster());                    instance.setServiceName(beatInfo.getServiceName());                    instance.setInstanceId(instance.getInstanceId());                    instance.setEphemeral(true);                    try {                        serverProxy.registerService(beatInfo.getServiceName(),                            NamingUtils.getGroupName(beatInfo.getServiceName()), instance);                    } catch (Exception ignore) {                    }                }            } catch (NacosException ne) {                NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",                    JSON.toJSONString(beatInfo), ne.getErrCode(), ne.getErrMsg());            }            //每隔5秒重新发送一次心跳包            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);        }    }

其实我们深入sendbeat函数可以看到最底层就是请求nacos服务端的心跳接口

   public JSONObject sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {        if (NAMING_LOGGER.isDebugEnabled()) {            NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());        }        Map<String, String> params = new HashMap<String, String>(8);        String body = StringUtils.EMPTY;        if (!lightBeatEnabled) {            try {                body = "beat=" + URLEncoder.encode(JSON.toJSONString(beatInfo), "UTF-8");            } catch (UnsupportedEncodingException e) {                throw new NacosException(NacosException.SERVER_ERROR, "encode beatInfo error", e);            }        }        params.put(CommonParams.NAMESPACE_ID, namespaceId);        params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());        params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());        params.put("ip", beatInfo.getIp());        params.put("port", String.valueOf(beatInfo.getPort()));        String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, body, HttpMethod.PUT);        return JSON.parseObject(result);    }

结合springboot的starter如何做服务发现

首先你可能会有思路推断,加入了一个starter就能生效,估计是有什么springboot的自动化配置在生效吧。

springboot也有自己的一套spi机制,将spirng.factories配置文件下的类进行实例化操作。


然后根据这些配置的类进行初始化操作。

这里面有个 NacosServiceRegistryAutoConfiguration 类

参考源代码:

com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfigurationcom.alibaba.cloud.nacos.registry.NacosAutoServiceRegistrationorg.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration

这个类里面继承类spring的事件,ApplicationListener,当spring容器启动的时候会去触发onApplicationEvent函数的。

bind(event)-->start --> register--> com.alibaba.nacos.api.naming.NamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)

其实本质就是在这里调用类nacos的一个远程方法,关于nacos的远程方法看看源码包就了解了,这个不难。

注册的参数

private Instance getNacosInstanceFromRegistration(Registration registration) {   Instance instance = new Instance();   instance.setIp(registration.getHost());   instance.setPort(registration.getPort());   instance.setWeight(nacosDiscoveryProperties.getWeight());   instance.setClusterName(nacosDiscoveryProperties.getClusterName());   instance.setMetadata(registration.getMetadata());   return instance;}

整体的注册源码其实可以浓缩为下边这张图

在这里插入图片描述
nacos的集群化

基本配置条件:

一般集群需要至少3个节点。我们先准备3台机器,我这里选择了三台机器作为集群搭建基础:

192.168.11.200:8748192.168.11.196:8748192.168.11.126:8748

首先需要有三台基本的服务器用于运行多个nacos服务端程序。

然后修改conf配置文件:

[root@localhost conf]# lsapplication.properties  application.properties.example  cluster.conf  cluster.conf.example.bak  nacos-logback.xml  nacos-mysql.sql  schema.sql[root@localhost conf]# cat cluster.conf#it is ip#example192.168.164.131:8848192.168.164.132:8848192.168.164.133:8848

最后再配置一下数据库连接部分:

### Count of DB: db.num=1### Connect URL of DB: db.url.0=jdbc:mysql://10.11.9.243:3306/linhao_test?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=crm db.password=USszJ497whda

启动之后日志会有明显说明提示nacos的集群已经部署成功。

如果需要方便操作可以借助使用nginx来做页面的转发。

upstream nacos_server {  server 192.168.11.200:8748;  server 192.168.11.196:8748;  server 192.168.11.126:8748;}server {  listen 80;  server_name localhost;  #charset koi8-r;  #access_log logs/host.access.log main;  location / {    proxy_pass http://nacos_server;    index index.html index.htm;  }}

初始化登录账号

登录账号可以从源码里面翻查,然后根据这里的加密方式在数据库里面设置账号信息:

package com.alibaba.nacos.console.utils;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;/** * Password encoder tool * * @author nacos */public class PasswordEncoderUtil {    public static void main(String[] args) {        System.out.println(new BCryptPasswordEncoder().encode("nacos"));    }    public static Boolean matches(String raw, String encoded) {        return new BCryptPasswordEncoder().matches(raw, encoded);    }    public static String encode(String raw) {        return new BCryptPasswordEncoder().encode(raw);    }}

下边这段是nacos初始化时候给定的账号密码:

INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

经过检测,不同账号登录nacos看到的基础配置信息大多都是相似的。

nacos里面的日志输出在nacos-logback.xml 配置了日志输出位置和等级,如果需要跟踪或者调整可以进去进行修改。

©著作权归作者所有:来自51CTO博客作者mob604756f6460e的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. 分布式系统架构中高可用方案技术选型:Hystrix 框架实现服务保护使
  2. 微服务开发神器之JRebel 插件破解和实现本地及远程热部署教程
  3. 推荐使用 SSH 方式连接 Git 服务
  4. OpenDaylight实现轮询策略的负载均衡服务
  5. 传统架构云化后的运维,维护的是什么?
  6. 「网络架构」网络代理第一部分: 代理概述
  7. ODL源码分析之flowmod下发流程
  8. 微服务架构系列01:容器设计原则
  9. 「企业微服务架构」怎么弥合不同微服务团队之间的差距

随机推荐

  1. 为什么需要Docker?
  2. 面试前必须要知道的Redis面试题
  3. 策略模式原来就这么简单!
  4. Docker入门为什么可以这么简单?
  5. JAVA中的“抽象接口”
  6. 三分钟学会门面模式!
  7. 海康网络摄像机SDK Linux对接
  8. 2018再见,2019你好
  9. 国外版《从入门到放弃》大全,脑洞无极限!
  10. 使用Python开发鸿蒙应用--2021.01.07直播