使用Java自制一个一个Nacos

 更新时间:2024年01月19日 09:52:36   作者:乐乐家的乐乐  
Nacos是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台,本文将尝试用Java实现一个Nacos,感兴趣的可以了解下

什么是Nacos

Nacos是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos的主要功能:

  • 服务发现和服务健康监测
  • 动态配置服务
  • 动态 DNS 服务
  • 服务及其元数据管理

直接参考Nacos文档 :Nacos文档

我们要做的功能是

Nacos的功能实在是太成熟了,但是我们可以通过官网和文档总结出Nacos的两大核心功能:

  • 服务发现
  • 配置管理

一、实现 服务发现

服务发现,与其说是实现服务与发现,不如说是实现以下三个功能:

  • 服务启动时候进行注册
  • 查询已注册服务信息
  • 确认服务状态是否健康

1、创建一个SpringBoot项目,用来做服务端

这个项目要实现几个目的:

  • 首先我们的服务需要可以支持Http请求(GRPC更好,Nacos用的就是GRPC,Http请求我们更熟悉一点,以后我们会专门出有关于JAVA使用GRPC的文章)。
  • 其次我们需要我们的服务可以集成和连接一个关系型数据库(Mysql或者Oracle...)和非关系型数据库(Redis...)

所以我们需要创建一个SringBoot项目,因为我们需要一个配置与发现中心的这么一个服务,类似于搭建Nacos,我们只不过是将这个中间件变成一个我们熟悉的SpringBoot项目,方便我们开发。

2、服务端

注册中心 服务端 ,我们需要一个注册接口。

实例 数据库同理创建

public class ClientBody
{
    private static final long serialVersionUID=1L;

    /** 自增id */
    private Long id;

    /** 项目名 */
    private String projectName;
    
    /** 端口 */
    private String port;
    
    /** 健康检测回调接口 */
    private String CallbackInterface;

    /** 内网ip */
    private String inNetIp;

    /** 外网ip */
    private String outNetIp;

    /** 注册时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date regSerTime;

    /** 0-健康 1-异常 2-死亡 */
    private String serType;

    /** 异常时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date exceptTime;

    /** 死亡时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date deathTime;

    /** 检测次数 */
    private Long checkNum;

    /** 异常次数 */
    private Long exceNum;
}

服务端注册接口

/**
 * 令牌自定义标识
 */
@Value("${token.header}")
private String header;

/**
 * 服务注册
 */
@PostMapping("/serviceRegistration")
public Boolean add(HttpServletRequest request, @RequestBody ClientBody clientBody){
    /**
     * 获取token
     */
    String token = request.getHeader("header");
    /**
     * 验证请求合法性
     */
    Boolean isLegal = SecurityUtils.verLegal(token);
    if(isLegal){
        /**
         * 检测 serType!=2 端口+内网地址
         */
        ClientBody client = clientBodyService.checkClient(clientBody);
        if(ObjectUtils.isNotEmpty(client)){
            /**
             * 说明是在心跳检测期间重新启动。
             * 注销这台实例.
             *
             * Mysql client 这条数据
             * deathTime 改为现在时间
             * serType 改为 2
             *
             * Redis 直接删除这个数据id 为 Key的数据
             * 直接删除这台实例
             */
            clientBodyService.logoutCient(client);
            /**
             * redis 和 Mysql 中新增一台实例
             * regSerTime 现在时间
             * serType 为 0
             */
            clientBodyService.insert(clientBody);
        }else{
            /**
             * 启动了一台实例
             */
            clientBodyService.insert(clientBody);
        }

        return true;

    }else{
        return false;
    }
}

服务端健康检测定时任务

/**
 * 60秒定时健康检测
 */
@Scheduled(cron = "0/60 * * * * ?")
public void heartbeatCheck(){
    /**
     * 查询 serType = 0 健康的 实例List
     */
    List<ClientBody> clientBodyList = clientBodyService.selectOnlineServer();
    for (ClientBody clientBody : clientBodyList) {
        //回调接口
        String CallbackInterface = clientBody.getCallbackInterface();
        //内网地址
        String inNetIp = clientBody.getInNetIp();
        //拿到端口号
        String port = clientBody.getPort();
        
        //发送http请求 true为正常 false为异常
        Boolean state = HttpUtils.sendHead(CallbackInterface,inNetIp,port);

        if(state){
            //检测次数 + 1(checkNum + 1)
            clientBodyService.updateCientNormal(clientBody);
        }else{
            /**
             * 检测ping不通原因可能时网络波动
             * 检测十次 都是异常 才判定死亡
             */
            if(clientBody.getCheckNum() > 10){
                //修改这条数据为死亡 (serType = 2)
                clientBodyService.updateCientDeath(clientBody);
            }else{
                //修改这条数据为异常,然后检测次数 + 1,异常次数 + 1,异常时间[现在时间字符串拼接在原有数据之后](serType = 1;checkNum + 1;exceNum + 1;)
                clientBodyService.updateCientException(clientBody);
            }
        }
    }
}

3、客户端

在我们的 客户端服务 我们需要在启动的时候注册:

客户端注册接口

/**
 * 注册
 * @PostConstruct 解释:在项目启动时加载数据
 */
@PostConstruct
public void registerService(){
    HashMap<String, Object> map = new HashMap<String,Object>(){{
        //项目名
        put("projectName",MyConfig.getName());
        //我注册选择内网地址,这个可以根据自己项目的实际情况选用。
        put("inNetIp", MyConfig.getUrl());
        //回调接口,这个接口就是下边的健康检测接口
        put("CallbackInterface", "/checkHealthy");
        //项目端口号
        put("port", MyConfig.getPort());
    }};

    AjaxResult ajaxResult = HttpUtils.sendPostRequest('注册与配置中心url', "/serviceRegistration", map);
}

我们还需要一个健康检测客户端接口,以便于服务注册中心心跳检测。我们选择用轻量级的头请求。

客户端健康检测接口

好了,我们的服务注册就完成了!

二、实现 配置管理

配置管理只需要我们完成两件事情

  • 服务端管理配置
  • 客户端启动的时候拉取配置

1、服务端管理配置

第一步我们需要在服务注册中心 服务端管理配置,我们将所有application.yml中的文件用properties的方式存入数据库。

/**
 * 健康检测接口
 */
@RequestMapping(value = "/CallbackInterface",method = RequestMethod.HEAD)
public void healthyByHead(HttpServletResponse response) {
    response.setHeader("data","200");
}

入库的形式就是这种:

服务端拉取配置接口

public class ConfigData
{
    private static final long serialVersionUID=1L;

    /** id */
    private Long id;

    /** key */
    private String key;

    /** value */
    private String value;

    /** tag */
    private String tag;

    /** remark */
    private String remark;
}

2、客户端拉取配置

客户端拉取配置接口,我们客户端程序启动的时候需要从注册服务中心来拉取配置。

首先我们在路径src\main\resources\META-INF\spring.factories中的spring.factories文件中加入到最后一行。

@GetMapping("/getConfigDataByTag")
public List<Map<String, String>> getConfig(@RequestParam String tag) throws JsonProcessingException {
    Map<String, String> configData = configDataService.selectconfigDataList(new configData(tag));
    return list;
}

然后我们需要创建一个拉取配置文件的类ServerConfigProcessor实现EnvironmentPostProcessor

public class ServerConfigProcessor implements EnvironmentPostProcessor {
    
    private static final String PROPERTY_SOURCE_NAME = "databaseProperties";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        log.info("ServerConfig loading start");
        Map<String, Object> propertySource = new HashMap<>();

        try {
            /**
             * 拉取配置
             * MyConfig.ServerConfigHttpUrl 服务端url
             * MyConfig.ServerConfigInterface 服务端接口
             * MyConfig.MyServerTag 本服务标识(用来判断拉取的配置条件)
             */
            Map<String, Object> configSource = HttpUtils.sendGet(MyConfig.ServerConfigHttpUrl,MyConfig.ServerConfigInterface,MyConfig.MyServerTag);

            propertySource.putAll(configSource);

            environment.getPropertySources().addFirst(new MapPropertySource(PROPERTY_SOURCE_NAME, propertySource));

            log.info("ServerConfig loading Success");

        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

OK到此为止我们的自定义Nacos就做好了。

总结

我们的自定义Nacos就做好了,它可以实现的功能有:

  • 客户端启动可以注册到服务端。
  • 服务端可以心跳检测每个客户端的项目。
  • 数据分析,比如我们什么时候项目异常?总共启动多少次项目?...
  • 可以不用application.yml,配置文件全部放在数据库中。(和Nacos一样)
  • 可以实现热部署配置文件,远程更改,实时有效

其他问题

src\main\resources\META-INF\spring.factories为什么可以自动装载配置?

这就要涉及SpringBoot源码啦,关于SpringBoot启动时候加载配置的优先级和环境配置的上下文,请参考SpringBoot源码。(读源码啦~ 必须要过这一关的嘛)

自定义拓展功能

可以把服务注册发现和配置管理都用前端展示到页面上方便管理。以下是我自己实现的前端界面,不美观无所谓,看的懂就行。

服务注册发现

配置管理(可以和Nacos一样在页面进行修改和热部署)

以上就是使用Java自制一个一个Nacos的详细内容,更多关于Java Nacos的资料请关注脚本之家其它相关文章!

相关文章

  • Java中WeakHashMap的回收问题详解

    Java中WeakHashMap的回收问题详解

    这篇文章主要介绍了Java中WeakHashMap的回收问题详解,WeakHashMap弱键大致上是通过WeakReference和ReferenceQueue实现,WeakHashMap的key是"弱键",即是WeakReference类型的,ReferenceQueue是一个队列,它会保存被GC回收的"弱键",需要的朋友可以参考下
    2023-09-09
  • SpringBoot+Redis海量重复提交问题解决

    SpringBoot+Redis海量重复提交问题解决

    在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,所以本文介绍一下SpringBoot+Redis海量重复提交问题解决,感兴趣的可以了解一下
    2023-12-12
  • spring boot 本地图片不能加载(图片路径)的问题及解决方法

    spring boot 本地图片不能加载(图片路径)的问题及解决方法

    这篇文章主要介绍了spring boot 本地图片不能加载(图片路径)的问题,解决的办法其实很简单,只要写一个配置文件,也就是图片位置的转化器,原理是虚拟一个在服务器上的文件夹,与本地图片的位置进行匹配。需要的朋友可以参考下
    2018-04-04
  • 基于springboot与axios的整合问题

    基于springboot与axios的整合问题

    这篇文章主要介绍了springboot与axios的整合问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • MyBatis框架中mybatis配置文件详细介绍

    MyBatis框架中mybatis配置文件详细介绍

    这篇文章主要介绍了MyBatis框架中mybatis配置文件详细介绍,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Java异常架构和异常关键字图文详解

    Java异常架构和异常关键字图文详解

    Java异常是Java提供的一种识别及响应错误的一致性机制,下面这篇文章主要给大家介绍了关于Java异常架构和异常关键字的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • Java泛型与注解全面分析讲解

    Java泛型与注解全面分析讲解

    Java 泛型(generics)是 Jdk 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。Annotation(注解)是JDK1.5及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。需要的可以参考一下
    2022-08-08
  • Java文件读写详解

    Java文件读写详解

    在真实的应用场景中,很多时候需要使用 Java 读写文件。比如说,读取配置文件信息、读取用户输入等。本篇文章将会详细介绍 Java 文件读写的相关知识,其中包括:读取文件、写入文件、复制文件和删除文件等操作,需要的朋友可以参考下
    2023-05-05
  • 修改jar包package目录结构操作方法

    修改jar包package目录结构操作方法

    这篇文章主要介绍了修改jar包package目录结构操作方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-07-07
  • JDK17在Windows安装及环境变量配置超详细的教程

    JDK17在Windows安装及环境变量配置超详细的教程

    这篇文章主要介绍了JDK17在Windows安装及环境变量配置超详细的教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-11-11

最新评论