手写nacos

news2024/11/25 20:46:30

目录

  • 背景
  • 过程
    • Demo1端
    • Demo2端
    • SDK端
    • Serve端
    • 1、某一个服务启动,将此服务信息放到注册表中
    • 2、当注册表中有新添加的信息,遍历整个注册列表,每个服务都拉下来一份新的注册列表
    • 3、哪个服务中的配置文件发生过改变,就让哪个服务重新拉取配置文件
    • 4、服务1调用服务2分为几步:服务1先去拿到最新的注册表,遍历注册表,获取服务2的信息。调用服务2
  • 升华

背景

我们经常使用一些组件但是不明确其中的原理 ,比如nacos,今天我们就来手写一个nacos。

Nacos(Naming and Configuration Service)是一个用于动态服务发现、配置管理和服务元数据管理的开源平台。它提供了一种简单且易于使用的方式,帮助开发人员管理和发现微服务。

Nacos的基本实现原理涉及以下几个关键组件和概念:

注册中心(Registry):Nacos作为一个服务注册中心,用于管理服务实例的注册和发现。服务提供者在启动时会向Nacos注册自己的信息,包括服务名、IP地址、端口等。消费者在需要调用服务时,可以向Nacos查询服务的地址信息。

命名服务(Naming Service):Nacos提供了命名服务来管理服务的命名空间。每个服务都有一个唯一的服务名,Nacos通过命名服务将服务名映射到实际的服务地址。

配置管理(Configuration Management):Nacos允许开发人员集中管理配置信息。配置可以是任何类型的数据,比如数据库连接信息、缓存配置、日志级别等。Nacos提供了一个统一的配置中心,开发人员可以在运行时动态地更新配置,而不需要重启服务。

服务发现(Service Discovery):Nacos通过注册中心来实现服务发现。服务提供者在启动时向注册中心注册自己的服务实例,消费者可以向注册中心查询服务的地址信息。当服务实例发生变化(如新增或下线)时,注册中心将及时通知消费者,以便其能够更新服务调用地址。

健康检查(Health Check):Nacos提供了健康检查的机制,用于检测服务实例的可用性。注册中心会定期发送心跳给服务实例,如果一个实例长时间未响应,注册中心将认为该实例不可用,并将其从服务列表中移除。

总体来说,Nacos的实现原理是通过注册中心管理服务实例的注册和发现,提供了命名服务来映射服务名到实际地址,以及配置中心用于集中管理配置信息。这些机制使得微服务的注册、发现和配置更新变得更加简单和高效。

在这里插入图片描述

过程

Demo1端

在这里插入图片描述

package com.example.demo1;

import com.example.sdk.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.*;
import org.springframework.web.client.RestTemplate;

import java.util.Map;


@RestController
@RequestMapping("/Demo1")
public class Controller {
    private String storedValue;

    @Autowired
    SDKController sdkController;

    @PostMapping
    public void useDemo2(@RequestBody String value) throws JsonProcessingException {

        //首先调用注册表  获取一整个注册表
        Map<String, Map<String, String>> mapRegister = sdkController.registerCenter;

        String url = null;

        //遍历这个map,将map中的所有服务都进行调用一遍
//        for (Map.Entry<String, Map<String, String>> entry : mapRegister.entrySet())
            if (mapRegister.keySet().contains("Demo2")) {
                Map<String,String> entry = mapRegister.get("Demo2");
                url = "http://" + entry.get("zy") + "/" +"Demo2";
                RestTemplate restTemplate = new RestTemplateBuilder().build();
                ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                if (forEntity.getStatusCode() == HttpStatus.OK) {
                    System.out.println("我调用了Demo2服务");
//
                }}}


        @GetMapping
        public String getValue () {
            System.out.printf("Demo1收到注册列表更新消息");
            return "Demo1收到注册列表更新消息";
        }


    @GetMapping("/getPeiZhi")
    public String getPeiZhi () {
        System.out.printf("Demo1拉取新的配置文件");
        return "Demo1收到注册列表更新消息";
    }


    }

Demo2端

package com.example.demo2;

import com.example.sdk.SDKController;
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.*;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@RequestMapping("/Demo2")
public class Controller {
    private String storedValue;
    @Autowired
    SDKController sdkController;

    @PostMapping
    public void storeValue(@RequestBody String value) {
        //        SDKController sdkController = new SDKController();
        //Demo1调用Demo2
        //首先调用注册表  获取一整个注册表
        Map<String, Map<String, String>> mapRegister = sdkController.registerCenter;

        System.out.printf("Demo2服务启动后获取最新的注册列表"+mapRegister);
        //查看注册表里边是是不是包含我们想要的服务
        //如果有我们想要的服务,调用那个服务
        String url = null;

        //遍历这个map,将map中的所有服务都进行调用一遍
//        for (Map.Entry<String, Map<String, String>> entry : mapRegister.entrySet())
        if (mapRegister.keySet().contains("Demo1")) {
            Map<String,String> entry = mapRegister.get("Demo1");
            url = "http://" + entry.get("zy") + "/" +"Demo1";
            RestTemplate restTemplate = new RestTemplateBuilder().build();
            ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                System.out.println("注册列表更新了,已通知到对应Demo1服务");
//
            }}}
    @GetMapping
    public String getValue() {
        System.out.printf("Demo2被其他服务调用了");
        return  "Demo2收到注册列表更新消息" ;
    }
    @GetMapping("/getPeiZhi")
    public String getPeiZhi () {
        System.out.printf("Demo2拉取新的配置文件");
        return "Demo2拉取新的配置文件";
    }


}

SDK端

package com.example.sdk;

import com.fasterxml.jackson.core.JsonProcessingException;
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.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

//


@RequestMapping("/sdk")
@RestController
@PropertySource("classpath:application.yml")
public class SDKController implements ApplicationRunner {

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

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

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

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

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

    /**
     * 获得配置文件
     *
     * @throws JsonProcessingException
     */


    //获取配置文件信息
    @GetMapping(value = {"/getConfig"})
    public void getConfigContext() throws JsonProcessingException {
        // 发送POST请求
        String url = "http://" + serverIp + "/Serve/getConfig";
        RestTemplate restTemplate = new RestTemplate();
        // 构造请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 构造请求实体对象
        HttpEntity<String> requestEntity = new HttpEntity<>(null, headers);

        // 发起 HTTP GET 请求,并指定返回类型
        ParameterizedTypeReference<Map<String, Map<String, String>>> responseType =
                new ParameterizedTypeReference<Map<String, Map<String, String>>>() {
                };
        ResponseEntity<Map<String, Map<String, String>>> responseEntity = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                responseType
        );
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            configCenter = responseEntity.getBody();
            System.out.printf("拉取最新的配置文件");
            System.out.println(configCenter);
        } else {
            System.out.println("Request failed with status: " + responseEntity.getStatusCode());
        }


//        return configCenter;
    }


    //获取注册表

    @GetMapping(value = {"/getList"})
    public Map<String, Map<String, String>> getRegisterContext() throws JsonProcessingException {
        // 发送POST请求
        String url = "http://" + serverIp + "/Serve/getList";
        RestTemplate restTemplate = new RestTemplate();
        // 构造请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        // 构造请求实体对象
        HttpEntity<String> requestEntity = new HttpEntity<>(null, headers);

        // 发起 HTTP GET 请求,并指定返回类型
        ParameterizedTypeReference<Map<String, Map<String, String>>> responseType =
                new ParameterizedTypeReference<Map<String, Map<String, String>>>() {
                };
        ResponseEntity<Map<String, Map<String, String>>> responseEntity = restTemplate.exchange(
                url,
                HttpMethod.GET,
                requestEntity,
                responseType
        );
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            registerCenter = responseEntity.getBody();
            System.out.printf("当前服务拉取最新的注册表如下");
            System.out.println(registerCenter);
        } else {
            System.out.println("Request failed with status: " + responseEntity.getStatusCode());
        }


        return registerCenter;
    }

    @PostMapping(value = {"/setConfigContext"})
    public void setConfigContext() throws JsonProcessingException {

    }

    /**
     * 注册上去
     *
     * @param args
     * @throws Exception
     */


    //正在运行的注册上去  调用serve的post请求
    @Override
    public void run(ApplicationArguments args) throws Exception {
        String url = "http://" + serverIp + "/Serve/saveList/";
        RestTemplate restTemplate = new RestTemplateBuilder().build();
        Map<String, Object> requestBody = new HashMap<>();
        Map<String, String> param = new HashMap<>();
        param.put("zy", InetAddress.getLocalHost().getHostAddress() + ":" + serverPort);
        requestBody.put(serverName, param);
        HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody);
        ResponseEntity<String> forEntity = restTemplate.postForEntity(url, request, String.class);

    }


}

Serve端

package com.example.serve;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.net.InetAddress;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/Serve")
public class Controller {

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

    private String storedValue;

    //存取配置
    //注册列表
    //serve   里边的两组存完之后   调A服务    存A的服务就调A的服务,调A服务之前要去注册列表里边读对应的IP地址
    //存注册列表   注册列表里边的循环全都调一遍


    @PostMapping("/savePeizhi")
    public void savePeizhi(@RequestBody Map<String, Map<String, String>> map) {
        //首先接收新的配置文件
         configCenter.putAll(map);

         String key = String.valueOf(map.keySet()).replaceAll("\\[|\\]", "");
        System.out.printf(map.keySet().toString());
        //根据配置文件的服务名称从注册表中获取 该服务的ip地址还有端口号之类的信息
         Map<String,String> ipAndPortMap = registerCenter.get(key) ;
         String ipAndPort = ipAndPortMap.get("zy");
        //调用该服务的SDK中获取配置文件的信息
        String url = "http://"+ipAndPort+"/sdk/getConfig";
        RestTemplate restTemplate=new RestTemplateBuilder().build();
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        // 处理响应
        if (forEntity.getStatusCode() == HttpStatus.OK) {
            System.out.println("配置更新了,通知了"+map.keySet()+"服务的SDK拉取最新的配置文件");
        }

    }

//    }


    @GetMapping("/getConfig")
    public Map<String, Map<String, String>> getConfig() {
        return configCenter;
    }

    String url;

    @PostMapping("/saveList")
    public void saveList(@RequestBody Map<String, Map<String, String>> registerContext) {
        registerCenter.putAll(registerContext);
        System.out.printf(registerContext.toString()+"服务启动了,放入注册表中");
        for (String key:registerContext.keySet()){
            registerCenter.put(String.valueOf(registerContext.keySet()).replaceAll("\\[|\\]", ""),(Map)registerContext.get(key));
            System.out.println(registerContext.keySet().toString().replaceAll("\\[|\\]", "")+"服务,注册成功"+"注册的内容是"+registerContext.get(key));
        }
        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()+"/sdk/getList";
//                    System.out.printf(url);
                    RestTemplate restTemplate=new RestTemplateBuilder().build();
                    ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                    // 处理响应
                    if (forEntity.getStatusCode() == HttpStatus.OK) {
                        System.out.println("注册列表更新了,通知了"+entry.getKey()+"服务的SDK");
                    }
                }

            }
        }
    }

    @GetMapping("/getList")
    public Map<String, Map<String, String>> getList() {
        return registerCenter;
    }
}

1、某一个服务启动,将此服务信息放到注册表中

首先启动Serve服务,然后启动服务1 ,将服务1注册进来

在这里插入图片描述

2、当注册表中有新添加的信息,遍历整个注册列表,每个服务都拉下来一份新的注册列表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、哪个服务中的配置文件发生过改变,就让哪个服务重新拉取配置文件

在这里插入图片描述

在这里插入图片描述

4、服务1调用服务2分为几步:服务1先去拿到最新的注册表,遍历注册表,获取服务2的信息。调用服务2

在这里插入图片描述
在这里插入图片描述

升华

我认为比较关键的几个点
1、找到一个项目一启动就会触发的操作,保证只要启动就会将此服务的信息同步放到注册表中。
2、想到要写一个SDK服务,将其打成jar包,放到各个业务服务中,通过嵌入的方式和读取配置文件的方式,实现服务之间的调用
3、使用注解,可以获取配置文件中的写的端口号等信息
4、使用restTemplate实现服务之间的调用。

原理类似于nacos的组件
是的,有几个类似于Nacos的组件,它们提供服务发现、配置管理和相关功能。以下是几个流行的与Nacos类似的替代品:

Consul:Consul是一个分布式服务网格和功能丰富的服务发现和配置管理工具。它提供服务注册和发现、健康检查、键值存储配置以及对分布式系统的支持。

etcd:etcd是一个分布式键值存储,通常用作可靠的服务发现和配置管理的数据存储。它提供了一个简单的分布式协调API,许多服务发现工具,包括Kubernetes,使用etcd作为其后端存储。

ZooKeeper:ZooKeeper是一个集中式服务,用于维护配置信息、命名、同步和提供群组服务。它广泛用于分布式协调,并具有实现服务发现和配置管理的特性。

Eureka:Eureka是Netflix OSS提供的服务注册和发现组件。它允许服务自行注册,并为客户端提供了一个RESTful API来发现服务。Eureka还包括健康检查和自动服务注册等功能。

Spring Cloud Config:Spring Cloud Config是一个配置管理工具,为分布式系统提供了一个集中管理外部配置的位置。它支持客户端和服务器组件,允许应用从集中的服务器检索其配置。

这些组件提供了与Nacos类似的功能,可以用于在微服务架构中实现服务发现、配置管理和相关功能。选择组件取决于具体的需求、与技术栈的兼容性以及开发团队的偏好。

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

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

相关文章

微软的三门课程帮你入门AI

现在AI技术如火如荼的&#xff0c;还没有入门AI技术的&#xff0c;可能在找一些入门的资料&#xff0c;现在好很多大公司都推出了自己的学习课程&#xff0c;这里整理了三门微软推出的课程&#xff0c;希望能一步一步的带你进入AI的技术大门。 Getting started with Python 这…

Flume学习---2、Flume进阶(事务)、负载均衡、故障转移、聚合

1、Flume进阶 1.1 Flume事务 1.2 Flume Agent内存原理 1、ChannelSelector ChannelSelector的作用就是选出Event将要被发往哪个Channel。其共有两种类型&#xff0c;分别是Replicating&#xff08;复制&#xff09;和Multiplexing&#xff08;多路复用&#xff09;。 Replicat…

Django-入门

文章目录 一、Django框架介绍二、后台管理第一步:项目的创建与运行第二步:应用的创建和使用第三步: 项目的数据库模型第四步: 启用后台Admin站点管理 三、前台管理第一步: URLconf 路由管理第二步: 视图函数处理业务逻辑第三步: 模板管理实现好看的HTML页面&#xff08;可参考菜…

一文带你读懂:TCP连接的三次握手和四次挥手(下篇)

天下没有不散的宴席&#xff0c;对于 TCP 连接也是这样&#xff0c; TCP 断开连接是通过四次挥手方式。下面我们通过实操&#xff0c;来彻底理解四次挥手。 对TCP连接建立三次握手感兴趣的同学&#xff0c;可以看我上一篇文章&#xff1a;一文带你读懂&#xff1a;TCP连接的三次…

[环境配置]让sd自动翻译提示词插件sd-webui-prompt-all-in-one安装

安装方式 方式一&#xff08;使用git克隆&#xff09;&#xff1a; 此方法需要你的电脑上安装了 git&#xff0c;如果没有安装&#xff0c;可参考 git 官方文档 进行安装。 打开终端&#xff0c;进入到你的 stable-diffusion-webui 目录下。 使用 git 克隆 sd-webui-prompt…

RobinKarp(字符串哈希)---分析与实现(C++)

1. 简述 给定字符串pattern和串text。求串pattern在串text中出现的位置。 暴力比较是逐个字符比较来确定两个串是否相等&#xff0c;若当前比较失败 则回到开始字符对应字符的后一个字符重复过程。 哈希就是一个大范围到小范围的映射 字符串哈希则是通过比较两个串的哈希值相…

Leetcode 剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 设计一个支持在平均 时间复杂度 O(1) 下&#xff0c;执行以下操作…

使用NLPAUG 进行文本数据的扩充增强

在机器学习中&#xff0c;训练数据集的质量在很大程度上决定了模型的有效性。我们往往没有足够的多样化数据&#xff0c;这影响了模型的准确性。这时数据增强技术就派上了用场。 数据增强可以通过添加对现有数据进行略微修改的副本或从现有数据中新创建的合成数据来增加数据量…

代码随想录训练营Day53|1143.最长公共子序列;1035.不相交的栈;53.最大子序和

1143.最长公共子序列 class Solution {public int longestCommonSubsequence(String text1, String text2) {int[][] dp new int[text1.length()1][text2.length()1];for(int i1;i<text1.length();i){for(int j1;j<text2.length();j){if(text1.charAt(i-1)text2.charAt(…

12性能提升:如何提升gRPC系统性能

这篇文章我们来一起学习下如何提升gRPC系统服务的性能。 gRPC 是一个高性能、开源的 RPC 框架,设计目标是支持多种编程语言和多种平台。它基于 Google 发布的 Protobuf(Protocol Buffers)序列化协议,可以在不同的应用程序之间传输数据。gRPC 具有高效率和可扩展性的特点,…

python3 爬虫相关学习7:使用 BeautifulSoup下载网页图片到本地文件夹

目录 1 一个爬图片pic的代码的例子 1.1 学习的原文章 1.2 原始代码的问题总结 问题1 问题2 问题3 其他问题 1.3 原始代码 2 直接在cmd里 python运行报错 和 处理 2.1 运行报错 2.2 报错原因&#xff1a; 没有提前安装这个bs4 模块 2.3 如何提前知道我的python环境…

【微信小程序】wxml、wxss、js、json文件介绍

&#x1f609;博主&#xff1a;初映CY的前说(前端领域) ,&#x1f4d2;本文核心&#xff1a;微信小程序的入门介绍 【前言】书接上回&#xff0c;我们知道了一个小程序的构成结构&#xff0c;接下来我们来进一步学习小程序的目录结构中的.wxml、.wxss、.js、.json。 目录 ⭐ 一…

一学就会-----链表中倒数第K个节点

文章目录 题目描述思路一代码示例思路二代码示例 题目描述 输入一个链表&#xff0c;输出该链表中倒数第k个结点。 图片示例&#xff1a; 思路一 由于这道题目并没有要求时间复杂度&#xff0c;我们完全可以先遍历一遍链表&#xff0c;得到链表的结点总数&#xff08;count&am…

利用Zookeeper实现集群选举

什么是Zookeeper 分布式开源协调系统&#xff0c;数据模型简单&#xff0c;可以实现同步&#xff0c;配置管理&#xff0c;分组管理&#xff0c;分命名空间管理等。 技术本质 一个原子消息传递系统&#xff0c;它使所有服务器保持同步 FLP(3个科学家名字命名) 理论角度&…

【Spring Security】的RememberMe功能流程与源码详解,基础-进阶-升级-扩展,你学会了吗?

文章目录 前言原理 基础版搭建初始化sql依赖引入配置类验证 源码分析 进阶版集成源码分析疑问1疑问2 鉴权 升级版集成初始化sql配置类验证 源码分析鉴权流程 扩展版 前言 之前我已经写过好几篇权限认证相关的文章了&#xff0c;有想复习的同学可以查看【身份权限认证合集】。今…

OpenAI官方提示词课(三)如何总结文章

现在是信息爆炸时代&#xff0c;打开手机&#xff0c;各种文章扑面而来。我们的精力是有限的。如果有人帮忙把文章总结好给我们&#xff0c;这不就节省了很多时间嘛&#xff01;我们也就可以阅读更多的文章了。 恰好大语言模型在总结文章方面非常有天赋。 下面来看看示例。 …

数学基础第二天

介绍 对于Hissian矩阵是正定的&#xff0c;在这一点是整个范围内的最小值&#xff0c;y在各个方向的二阶导数都是>0的 对于Hissian矩阵是负定的&#xff0c;在这一点是整个范围内的最大值&#xff0c;y在各个方向的二阶导数都是<0的, 对于Hissian矩阵是不定的&#xff…

有了这个工具,支付宝商家多个账号下的账单管理更方便了

大家好&#xff0c;我是小悟 为方便拥有多个支付宝账号的商家获取自身业务、资金数据及下载对账单的能力&#xff0c;为商家提供了商家账单产品&#xff0c;商家可以通过该产品系统化接入账单数据&#xff0c;实现支付宝商家多个账号账单管理的功能。 为拥有多个支付宝账号的…

华为OD机试真题 JavaScript 实现【求符合要求的结对方式】【2023Q1 100分】,附详细解题思路

一、题目描述 用一个数组A代表程序员的工作能力&#xff0c;公司想通过结对编程的方式提高员工的能力&#xff0c;假设结对后的能力为两个员工的能力之和&#xff0c;求一共有多少种结对方式使结对后能力为N。 二、输入描述 6 2 3 3 4 5 1 6 第一行为员工的总人数&#xff…

centos 7 安装git并配置ssh

一、安装 1、查看是否安装git <span style"color:#333333"><span style"background-color:#ffffff"><code class"language-perl">rpm -qa|<span style"color:#0000ff">grep</span> git </code>…