Nacos-手写配置中心基本原理

news2024/12/26 8:51:58
本文已收录于专栏
《中间件合集》

目录

  • 概念说明
    • Nacos配置中心
    • Naocs配置项
    • Naocs配置集
    • Naocs配置快照
  • 需求分析
  • 核心功能
  • 代码实现
    • AService模块
    • BService模块
    • NacosService模块
    • NacosSDK模块
  • 注意事项
  • 总结提升

概念说明

Nacos注册中心:https://blog.csdn.net/weixin_45490198/article/details/131256597

Nacos配置中心

  在系统开发过程中,开发者通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成。配置变更是调整系统运行时的行为的有效手段。

Naocs配置项

一个具体的可配置的参数与其值域,通常以 key-value 的形式存在。
在这里插入图片描述

Naocs配置集

一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
在这里插入图片描述

Naocs配置快照

Nacos 的客户端 SDK 会在本地生成配置的快照。当客户端无法连接到 Nacos Server 时,可以使用配置快照显示系统的整体容灾能力。配置快照类似于 Git 中的本地 commit,也类似于缓存,会在适当的时机更新,但是并没有缓存过期(expiration)的概念。
在这里插入图片描述
如果我们在项目中配置了快照拉取的路径,那么直接去配置的路径下就能看到拉去下来的配置文件了,如果在项目中没有指定快照的路径默认在C:\Users\Administrator下有一个nacos的文件夹。
在这里插入图片描述
在这里插入图片描述

需求分析

  在我们之前分析Nacos注册中心的时候已经明确了当程序启动的时候把各个服务注册到了Nacos的服务端,那我们在实时更新各个服务的配置文件的时候,需要去通知对应服务的SDK。然后对应服务的SDK在去Nacos服务端拉取一份新的配置文件即可。

  1. 需要有服务端(naocs)对客户端注册的信息进行储存和管理
  2. 需要有客户端向服务器注册自己信息包括IP地址端口号等内容
  3. 需要有服务端提供的SDK,用来修改服务的配置文件时同时对应的服务
    在这里插入图片描述

核心功能

  1. 「 配置管理 」:Nacos 配置中心允许将应用程序的配置信息集中存储和管理。可以通过 Web 控制台或 API 创建、更新、删除和查询配置信息。配置信息可以是键值对、JSON、XML 等格式。
  2. 「 动态配置 」:Nacos 配置中心支持动态配置,即应用程序可以在运行时动态获取最新的配置信息。当配置信息发生变化时,Nacos 会自动通知应用程序进行更新,无需重启应用程序。
  3. 「 配置共享 」:多个应用程序可以共享同一份配置信息,避免了配置信息的重复存储和管理。通过配置的分组和命名空间,可以实现不同应用程序之间的配置隔离和共享。
  4. 「配置监听 」:Nacos 配置中心支持配置的监听功能,应用程序可以注册监听器,当配置信息发生变化时,监听器会收到通知并执行相应的操作。
  5. 「 其他功能」:Nacos还支持其他的功能例如配置文件版本记录 、版本回滚等功能,本文中没有具体的说明。

代码实现

AService模块

业务部分只需要从nacos中获取IP地址请求其他服务器即可。

import com.example.client.Controller.SDKController;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @BelongsProject: ServiceA
 * @BelongsPackage: com.example.servicea.Controller
 * @Author: Wuzilong
 * @Description: A服务
 * @CreateTime: 2023-06-06 18:43
 * @Version: 1.0
 */
@RestController
@RequestMapping("/A")
public class ServiceAController {

    @Autowired
    SDKController sdkController;
    @GetMapping("/getServiceIp")
    public void getServiceIp() throws JsonProcessingException {
            String serviceIp = sdkController.random("B");
            String url = "http://"+serviceIp+"/B/receiveMessage";
            RestTemplate restTemplate=new RestTemplateBuilder().build();
            ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                System.out.println("调用B服务成功!IP地址为"+serviceIp);
            }
    }

}

配置文件中需要配置连接nacos服务的地址以及本服务的信息

server:
  port: 9001
  name: A
  url: localhost:9000
  key: ip

在pom文件中引入我们自己封装的NacosSDK服务

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>SDK</artifactId>
            <version>2.5-20230615.123611-1</version>
        </dependency>

BService模块

业务部分只需要编写响应A服务调用B服务的接口即可,说明调用成功

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @BelongsProject: ServiceB
 * @BelongsPackage: com.example.serviceb.Controller
 * @Author: Wuzilong
 * @Description: B服务
 * @CreateTime: 2023-06-07 19:08
 * @Version: 1.0
 */
@RestController
@RequestMapping("/B")
public class ServiceBController {


    @GetMapping("/receiveMessage")
    public void receiveMessage(){
        System.out.println("B:我被调用了");
    }
}

配置文件中需要配置连接nacos服务的地址以及本服务的信息

server:
  port: 9002
  name: B
  url: localhost:9000
  key: ip

在pom文件中引入我们自己封装的NacosSDK服务

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>SDK</artifactId>
            <version>2.5-20230615.123611-1</version>
        </dependency>

B服务可以启动多个为了验证负载均衡。当有高并发请求的时候我们可以把请求的压力分配到每一个B服务上,减少只有一个B服务的压力。还有就是当一个B服务不能正常访问的时候我们访问其他的B服务。

NacosService模块

nacos服务端主要的服务:在注册中心的基础上添加了更新配置文件通知对应服务的SDK拉取最新的配置文件

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: Serve
 * @BelongsPackage: com.example.controller
 * @Author: Wuzilong
 * @Description: nacos服务端
 * @CreateTime: 2023-06-05 20:23
 * @Version: 1.0
 */
@RestController
@RequestMapping("/nacosServe")
public class ConfigCenterController {


        Map<String,Map<String,String>> configCenter =new HashMap<>();

        Map<String,Map<String,String>> registerCenter =new HashMap<>();


        /**
        * @Author:Wuzilong
        * @Description: 将主机的信息注册进来并通知sdk更新注册列表
        * @CreateTime: 2023/6/9 10:25
        * @param:  主机信息
        * @return:  void
        **/
        @PostMapping(value = {"/setRegisterContext"})
        public void setRegisterContext( @RequestBody Map<String, Map<String,String>> registerContext) throws Exception {

                registerCenter.putAll(registerContext);
                System.out.println(registerContext.keySet().toString().replaceAll("\\[|\\]", "")+"服务,注册成功"+"注册的内容是"+registerCenter);

                if(registerCenter.size()>1){
                        for (Map.Entry<String, Map<String,String>> entry:registerCenter.entrySet()){
                                for (Map.Entry<String,String> entry1:entry.getValue().entrySet()){
                                        // 发送POST请求
                                        String url = "http://"+entry1.getValue()+"/"+"/configClientServe"+"/getRegisterContext";
                                        RestTemplate restTemplate=new RestTemplateBuilder().build();
                                        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                                        // 处理响应
                                        if (forEntity.getStatusCode() == HttpStatus.OK) {
                                                System.out.println("注册列表更新了,通知了"+entry.getKey()+"服务的SDK");
                                        }
                                }

                        }
                }
        }


        /**
        * @Author:Wuzilong
        * @Description: 更新配置文件并通知配置文件对应的服务拉取新的配置文件
        * @CreateTime: 2023/6/9 10:26
        * @param:  更新的配置文件
        * @return: void
        **/
        @PostMapping(value = {"/setConfigContext"})
        public void setConfigContext( @RequestBody Map<String, Map<String,String>> configContext) throws Exception {
                //更新A服务的配置文件
                configCenter.putAll(configContext);
                //通知对应服务的SDK获取最新的配置文件
                Map<String, String> stringStringMap = registerCenter.get(configContext.keySet().toString().replaceAll("\\[|\\]", ""));
                String serverIp = stringStringMap.get("ip");
                String url = "http://"+serverIp+"/"+"/configClientServe"+"/getConfigContext/?serverName="+configContext.keySet().toString().replaceAll("\\[|\\]", "");
                RestTemplate restTemplate=new RestTemplateBuilder().build();
                ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                // 处理响应
                if (forEntity.getStatusCode() == HttpStatus.OK) {
                        System.out.println("配置文件更新了,通知了"+configContext.keySet()+"服务");
                }

        }

        //返回更新后的配置信息
        @GetMapping(value = {"/getConfigContext"})
        public Map<String,String> getConfigContext(String serviceName){
                return configCenter.get(serviceName);
        }


        //返回更新后的注册列表
        @GetMapping(value = {"/getRegisterContext"})
        public Map<String,Map<String,String>> getRegisterContext(){
                return registerCenter;
        }

}

NacosSDK模块

SDK是我们自己封装的用来让其他客户端集成使用的,其中包括了:项目启动把客户端注册到注册列表中、接收到注册列表更新的消息拉取最新的注册列表、负载均衡的两种策略(轮询和随机)和最新的配置文件

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.net.InetAddress;
import java.util.*;

/**
 * @BelongsProject: Client
 * @BelongsPackage: com.example.client.Controller
 * @Author: Wuzilong
 * @Description: nacos提供的sdk
 * @CreateTime: 2023-06-06 19:40
 * @Version: 1.0
 */
@RestController
@RequestMapping("/configClientServe")
@PropertySource("classpath:application.yml")
public class SDKController  implements ApplicationRunner{

    public  Map<String,String> configCenter =new HashMap<>();

    public Map<String, Map<String,String>> registerCenter =new HashMap<>();


    int index = 0;

    @Value("${server.port}")
    private String serverPort;

    @Value("${server.name}")
    private String serverName;

    @Value("${server.url}")
    private String serverIp;

    @Value("${server.key}")
    private String serverKey;


    @GetMapping(value = {"/getConfigContext"})
    public void getConfigContext(String serverName) throws JsonProcessingException {
        String url = "http://"+serverIp+"/nacosServe/getConfigContext?serviceName="+serverName;
        RestTemplate restTemplate=new RestTemplateBuilder().build();
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        if (forEntity.getStatusCode() == HttpStatus.OK) {
            String body = forEntity.getBody();
            configCenter = new ObjectMapper().readValue(body, new TypeReference<>() {});
            System.out.println("获取了"+serverName+"服务最新的配置文件,配置文件的内容是"+configCenter);
        }
    }


    //获取server中的注册列表
    @GetMapping(value = {"/getRegisterContext"})
    public void getRegisterContext() throws JsonProcessingException {
        System.out.println("注册列表更新了,去拉取一份新的注册列表");
        String url = "http://"+serverIp+"/nacosServe/getRegisterContext";
        RestTemplate restTemplate=new RestTemplateBuilder().build();
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        if (forEntity.getStatusCode() == HttpStatus.OK) {
            String body = forEntity.getBody();
            ObjectMapper objectMapper = new ObjectMapper();
            registerCenter = objectMapper.readValue(body, new TypeReference<>() {});
            System.out.println("新的注册列表拉取完毕,注册列表的内容为"+registerCenter);

        }
    }


    //项目启动后把本服务的信息注册到nacosServe上
    @Override
    public void run(ApplicationArguments args) throws Exception {

        String url = "http://"+serverIp+"/nacosServe/setRegisterContext/";
        RestTemplate restTemplate=new RestTemplateBuilder().build();
        Map<String, Object> requestBody = new HashMap<>();
        Map<String,String> param=new HashMap<>();
        param.put(serverKey, InetAddress.getLocalHost().getHostAddress()+":"+serverPort);
        requestBody.put(serverName, param);
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody);
        restTemplate.postForEntity(url,request,String.class);
    }


    //轮询获取服务的IP地址
    public  String polling(String serverName){
        List<String> pollingList = this.getList(serverName);
        String ipContext = pollingList.get(index);
        index=(index+1)%pollingList.size();
        return ipContext;
    }


    //随机获取可用的IP地址
    public String random(String serverName){
        List<String> randomList = this.getList(serverName);
        Random random =new Random();
        int randomIndex = random.nextInt(randomList.size());
       return randomList.get(randomIndex);
    }



    //获取客户端想要请求服务的可用IP地址
    public List<String> getList(String serverName){
        List<String> list=new ArrayList<>();
        for (Map.Entry<String, Map<String,String>> entry:registerCenter.entrySet()){
            if(entry.getKey().contains(serverName)){
                for (Map.Entry<String,String> entry1:entry.getValue().entrySet()){
                    list.add(entry1.getValue());
                }
            }
        }
        return  list;
    }
}

注意事项

  • 找到一个项目一启动就会触发的操作,保证只要启动就会将此服务的信息同步放到注册表中。
  • 想到要写一个SDK服务,将其打成jar包,放到各个业务服务中,通过嵌入的方式和读取配置文件的方式,实现服务之间的调用
  • 使用注解,可以获取配置文件中的写的端口号等信息,可以进行灵活变更。
  • 使用restTemplate实现服务之间的调用
  • 引入SDK服务需要考虑启动类和业务类的路径,确保程序启动能够扫描到引入的业务类

总结提升

  1. 思想上移,行动下移:之前对nacos配置中心都是在概念,通过手动实现把nacos注册中心写出来之后对于nacos配置中心有了更深入的理解
  2. 知其然也要知其所以然:之气只是简单的使用,哪里出现了问题也不清楚只能靠蒙和猜来解决问题。现在可以非常明确的知道是哪个环节出现了问题。底层原理明确使用起来也非常的简单。
  3. 不断迭代完善:这个版本没有添加每次修改配置文件的记录和每个版本配置文件的回滚等相关的内容。

🎯 此文章对你有用的话记得留言+点赞+收藏哦🎯

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/675997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vs中运行时库简要说明

vs中右键单击工程 -->属性–>c/c->代码生成&#xff0c;进入如下菜单中&#xff1a; 可以看出有如下几个选项&#xff1a; 多线程(/MT)&#xff1a;链接目标库为libcmt.lib 多线程调试(/MTd)&#xff1a;链接目标库为libcmtd.lib 多线程DLL(/MD)&#xff1a;链接目标…

02.GLM-130B

文章目录 前言泛读相关知识GPTBERTT5小结 背景介绍主要贡献和创新点GLM 6B 精读自定义Mask模型量化1TB 的中英双语指令微调RLHFPEFT训练策略 实验分析与讨论模型参数六个指标其他测评结果 代码复现&#xff08;6B&#xff09;环境准备运行调用代码调用网页服务命令行调用 模型微…

在 Python 中生成随机 4 位数字

文章目录 在 Python 中生成随机数使用 random 模块在 Python 中生成随机数使用 random.randint() 方法使用 random.randrange() 方法 使用替代方法在 Python 中生成随机数总结 Python 是一种高级解释型编程语言&#xff0c;全球大多数程序员都在使用它。 它在面向对象编程 (OOP…

SpringCloud Alibaba入门5之Hystrix的使用

我们继续在前一章的基础上进行学习。 SpringCloud Alibaba入门5之使用OpenFegin调用服务_qinxun2008081的博客-CSDN博客 上一节我们已经使用OpenFeign完成了服务间的调用&#xff0c;如果现在存在大量的服务&#xff0c;每个服务有若干个节点&#xff0c;其中一个节点发生故障…

Word技巧之【文档自动保存】

打工人的噩梦—电脑突然坏掉&#xff0c;文档还没保存&#xff01;你是否遇到这种情况&#xff1f; 如果Word文档设置了自动保存&#xff0c;就不用太过担心了&#xff0c;只需要几个简单的操作就能设置好。还不知道的小伙伴&#xff0c;跟着小编一起看看吧。 设置Word文档自动…

一起来学R编程把—do.call 函数的应用

R语言由一个个基础函数组成&#xff0c;熟练灵活应用这些基础函数&#xff0c;有助于我们更好的学习R包及编程,这个专栏可能很多人不感兴趣&#xff0c;但是对提升自己很有帮助&#xff0c;感兴趣的朋友一起来学习。今天我们来介绍一下do.call函数的用法. do.call函数是一个非…

ChatGLM-6B阿里云部署

机器配置 重点关注指标&#xff1a;CPU、内存、GPU、GPU驱动 类型CPU内存GPU机器配置16核125GNVIDIA A100 80G # 查看显卡安装状态 nvidia-smi 安装必要的软件 git sudo apt-get update sudo apt-get install git git-lfs(大文件管理) sudo apt-get install git-lfs py…

从零开始手搓一个STM32与机智云的小项目——GPIO模拟时序控制外设1

文章目录 前言WS2812B1.模块简介2.时序介绍3.硬件介绍4.传输速率&#xff0c;以及帧数要求 代码1.初始化2.模拟时序1.复位函数2.发送0、1码3.封装发送函数 总结 前言 上一篇文章中介绍了整个板子的最基本功能模块——使用GPIO的通用输入输出实现简单的按键输入以及推挽输出控制…

基于Java+Swing+Mysql实现图书管理系统V2.0

基于JavaSwingMysql实现图书管理系统V2.0 一、系统介绍二、功能展示1.项目内容2.项目骨架3.数据库表4.主界面5.添加6、修改7、查询8、删除 四、其它1.其他系统实现五.获取源码 一、系统介绍 本系统主要有对图书信息的增删改查操作功能。 项目类型&#xff1a;Java SE项目&…

到底还有谁学不会 MySQL 中的视图?

文章目录 MySQL中的视图视图的概念视图的用法简化查询操作提高查询效率保护数据的安全性 视图的代码示例总结 MySQL中的视图 在MySQL中&#xff0c;视图是一种虚拟表&#xff0c;它是由一个或多个基本表的行或列组成的。视图并不实际存储数据&#xff0c;而是根据定义的查询语…

6-端午练习

目录标题 6_221. 进程和线程2. 数据7>>1 6_231. 用户相关指令2. 创建用户(1. 查看id(2. 查看当前用户(3. 创建用户(4. 给新用户添加sudo权限>1 修改sudoers文件 2. 删除用户3. 修改用户名2. 磁盘1. 保证ubuntu链接上U盘(1. VM弹窗&#xff0c;选择链接到虚拟机(2. 虚拟…

DELL的交换机PowerSwitch学习手册-ONIE篇

下面是最近学习DELL的网络交换机PowerSwitch的一些笔记&#xff0c;供朋友们参考。如果还有问题&#xff0c;可以add wechat at StorageExpert。 在具体学习产品之前&#xff0c;先来了解下DELL的ONIE&#xff0c;什么是ONIE&#xff1f;和如何使用ONIE&#xff1f; ONIE是 O…

关于Nginx网站服务

目录 一、首先搭建Nginx服务 二、授权的访问控制 第一步 安装依赖包 第二步 生成用户密码认证文件 第三步 修改文件属性和权限 第四步 修改配置文件 第五步 用浏览器测试网站 三、基于IP地址进行限制 第一步 修改配置文件 第二步 用两台设备进行访问测试 四、基于域…

Linux - 内存、swap、内存回收机制

参考 2023年6月22日 https://zhuanlan.zhihu.com/p/107350459 —— 讨论的swap基于Linux4.4内核代码 内存深度科普: 从堆内存到虚拟内存管理 2023年6月22日 qbittorrent swap 问题 https://github.com/qbittorrent/qBittorrent/issues/12947 massif valgrind --toolmassif qb…

taro实现小程序地图打点

使用taro的map标签&#xff0c;往markers里放入点位&#xff1a; <map v-if"mapLoading" id"mapId":longitude"userPosition.x":latitude"userPosition.y":show-location"false":markers"markerList":scale&q…

Spring Boot 如何使用 JUL 进行日志记录

Spring Boot 如何使用 JUL 进行日志记录 在 Spring Boot 中&#xff0c;我们可以使用多种日志框架进行日志记录。其中&#xff0c;JUL (Java Util Logging) 是 Java 平台自带的日志框架&#xff0c;它提供了简单的 API 和配置&#xff0c;可以轻松地进行日志记录。本文将介绍如…

RocketMQ 常见面试题(一)

RocketMQ Broker 中的消息被消费后会立即删除吗&#xff1f; 不会&#xff0c;每条消息都会持久化到 CommitLog 中&#xff0c;每个 Consumer 连接到 Broker 后会维持消费进度信息&#xff0c;当有消息消费后只是当前Consumer 的消费进度&#xff08;CommitLog 的 offset&…

【计算机视觉】在计算机视觉里,传统卷积已经彻底输给Transformer了吗?

文章目录 一、传统卷积 & Transformer1.1 传统卷积1.2 Transformer 二、知乎高赞回答2.1 作者&#xff1a;知乎用户2.2 作者&#xff1a;王云鹤2.3 作者&#xff1a;知乎用户 一、传统卷积 & Transformer 1.1 传统卷积 传统卷积&#xff08;Traditional Convolution&…

【初识C语言(3)】选择语句+循环语句+函数+数组

文章目录 1. 选择语句2. 循环语句3. 函数4. 数组 C语言是一门结构化的程序设计语言 顺序结构&#xff1b; 选择结构&#xff1b; 循环结构。 1. 选择语句 生活中处处面临着选择&#xff0c;如果你好好学习&#xff0c;校招时拿一个好offer&#xff0c;走上人生巅峰。如果你不学…

案例突破——悲观锁和乐观锁

悲观锁和乐观锁 一、背景介绍二、悲观锁和乐观锁什么是悲观锁什么是乐观锁 三、 在项目中如何使用悲观锁和乐观锁在项目中使用悲观锁实体结构实体对象的xml配置文件对应生成的表结构往表中初始化数据运行之后的结果模拟触发悲观锁的条件核心代码 在项目中使用乐观锁实体结构(添…