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

这篇具有很好参考价值的文章主要介绍了Nacos-手写配置中心基本原理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文已收录于专栏
《中间件合集》

概念说明

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

Nacos配置中心

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

Naocs配置项

一个具体的可配置的参数与其值域,通常以 key-value 的形式存在。
Nacos-手写配置中心基本原理

Naocs配置集

一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。
Nacos-手写配置中心基本原理

Naocs配置快照

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

需求分析

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

  1. 需要有服务端(naocs)对客户端注册的信息进行储存和管理
  2. 需要有客户端向服务器注册自己信息包括IP地址端口号等内容
  3. 需要有服务端提供的SDK,用来修改服务的配置文件时同时对应的服务
    Nacos-手写配置中心基本原理

核心功能

  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是我们自己封装的用来让其他客户端集成使用的,其中包括了:项目启动把客户端注册到注册列表中、接收到注册列表更新的消息拉取最新的注册列表、负载均衡的两种策略(轮询和随机)和最新的配置文件文章来源地址https://www.toymoban.com/news/detail-496873.html

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. 不断迭代完善:这个版本没有添加每次修改配置文件的记录和每个版本配置文件的回滚等相关的内容。

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

到了这里,关于Nacos-手写配置中心基本原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 三、nacos注册中心实现原理分析

    1.Nacos架构图 Provider App:服务提供方-是指提供可复用和可调用服务的应用方。 Consumer App:服务消费方-是指会发起对某个服务调用的应用方。 Name Service:用过VIP(Vritual IP)或者DNS的方法实现Nacos高可用的服务路由。 Nacos Service:Nacos服务提供者,里面包含Open API 是功能访问入口

    2024年02月04日
    浏览(35)
  • Nacos配置中心中配置文件的创建、微服务读取nacos配置中心

    在企业项目中会有非常多的服务,不同的开发环境还有不同的配置文件,所以就导致配置文件非常多。 那么肯定就会有一些公共配置,多个服务都是使用过一样的,那么就可以使用配置中心来进行统一管理,避免修改一个配置项要去各个服务都改一遍。 使用传统方式的配置

    2024年02月02日
    浏览(39)
  • Nacos 注册中心和配置中心

                            打开nacos虚拟机

    2024年02月06日
    浏览(34)
  • 手写Ribbon基本原理

    本文已收录于专栏 《中间件合集》   Ribbon 是一个客户端负载均衡器,它是Spring Cloud Netflix开源的一个组件,用于在分布式系统中实现对服务实例的负载均衡。它可以作为一个独立的组件使用,也可以与 Spring Cloud 等微服务框架集成使用。   Ribbon 的主要功能是根据一定的

    2024年02月09日
    浏览(39)
  • SpringCloud实用篇1——eureka注册中心 Ribbon负载均衡原理 nacos注册中心

    单体架构: 将业务的所有功能集中在一个项目中开发,打成一个包部署。 优点:架构简单;部署成本低(打jar包、部署、负载均衡就完成了) 缺点:耦合度高(维护困难、升级困难,不利于大项目开发) 分布式架构 根据业务功能对系统做拆分,每个业务功能模块作为独立

    2024年02月13日
    浏览(34)
  • Nacos配置中心 (介绍与配置)

    当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。 Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

    2024年02月02日
    浏览(37)
  • Nacos 的底层实现原理 & 注册中心的两种调用方式

    目录 1. Nacos 的底层实现原理 1.1 配置中心自动刷新实现原理 1.2  注册中心底层实现原理 2. Nacos 注册中心的两种调用方式  2.1 RestTemplate + Spring Cloud LoadBalancer 的调用方式 2.2 使用 OpenFeign + Spring Cloud LoadBalancer  Nacos 配置中心的自动刷新,其底层是基于 长轮询+事件驱动 的方式来

    2024年02月05日
    浏览(44)
  • Nacos基础介绍—配置中心

    在微服务架构中,配置中心就是统一管理各个微服务配置文件的服务。把传统的单体jar包拆分成多个微服务后,配置文件也要拆分,每个微服务都要有自己的配置文件。为了统一维护,方便管理,所以出现了配置中心的概念。所有的微服务配置文件都在配置中心中管理和读取

    2024年02月12日
    浏览(40)
  • Nacos实现配置中心

    统一管理不同环境、不同集群的配置 配置修改实时生效(热发布) 版本管理,每次配置的修改都会有版本记录,可随时回滚 权限管理。配置中心可交给专人来管理,做到配置的权限管理 使用配置中心之前,一般这样实现配置,把配置都放到配置文件中,Java代码通过@Value去拿

    2024年02月16日
    浏览(61)
  • Nacos源码 (4) 配置中心

    本文阅读nacos-2.0.2的config源码,编写示例,分析推送配置、监听配置的原理。 构造方法: 创建ConfigFilterChainManager - 过滤器链 创建ServerListManager - 服务器列表管理 创建ClientWorker - 用来发送请求,内部封装了一个ConfigRpcTransportClient类型对象agent,它能够获取到RpcClient与服务端进行

    2024年02月12日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包