Nacos-手写注册中心基本原理

news2024/11/16 1:29:47
本文已收录于专栏
《中间件合集》

目录

  • 概念说明
  • 需求分析
  • 核心功能
  • 代码实现
    • AService模块
    • BService模块
    • NacosService模块
    • NacosSDK模块
  • 注意事项
  • 总结提升

概念说明

  注册中心是微服务架构中的纽带,类似于“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址并进行调用。注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的,更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。

需求分析

  我们在项目中使用nacos的时候只要进入一个nacos对应的依赖就可以了,不需要额外添加其他的代码,对于项目已启动关于客户端的信息注册到服务端,以及注册列表发生变化的时候拉取最新的注册列表等相关功能都不需要我们自己来写。我们只需要在pom文件中引入nacos的依赖这些功能就都有了。

  1. 需要有服务端(naocs)对客户端注册的信息进行储存和管理
  2. 需要有客户端向服务器注册自己信息包括IP地址端口号等内容
  3. 需要有服务端提供的SDK,用来项目启动注册信息和拉取最新注册列表等用能的
    在这里插入图片描述

核心功能

  1. 「 服务注册与发现 」:Nacos可以作为服务注册中心,服务提供者可以将自己的服务注册到Nacos中,而服务消费者可以从Nacos中发现并获取可用的服务实例。Nacos支持主流的服务注册与发现协议,包括基于HTTP的RESTful接口和基于DNS的服务发现。
  2. 「 动态配置管理 」:Nacos提供了一个统一的配置管理平台,可以集中管理应用程序的配置信息。它支持动态配置更新和推送,当配置发生变化时,Nacos会及时通知到应用程序。Nacos还支持配置的版本管理和灰度发布,可以方便地进行配置的管理和控制。
  3. 「 服务健康监测 」:Nacos可以对注册到其上的服务进行健康检查,通过定时发送心跳来判断服务的可用性。当服务不可用时,Nacos会将其从服务列表中移除,从而保证服务的高可用性和可靠性。
  4. 「 负载均衡 」:Nacos提供了负载均衡的能力,可以根据不同的负载均衡策略将请求分发到不同的服务实例上,从而实现请求的负载均衡。
  5. 「 服务路由与流量管理 」:Nacos支持服务的动态路由和流量管理,可以根据不同的规则将请求导向不同的服务实例,从而实现服务的灵活路由和流量控制。
  6. 「 配置共享与分组管理 」: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服务端主要的服务:更新注册列表、通知各客户端拉取最新注册列表、提供最新的注册列表

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: 描述什么人干什么事儿
 * @CreateTime: 2023-06-05 20:23
 * @Version: 1.0
 */
@RestController
@RequestMapping("/nacosServe")
public class ConfigCenterController {


        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");
                                        }
                                }

                        }
                }
        }



        //返回更新后的注册列表
        @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: 描述什么人干什么事儿
 * @CreateTime: 2023-06-06 19:40
 * @Version: 1.0
 */
@RestController
@RequestMapping("/configClientServe")
@PropertySource("classpath:application.yml")
public class SDKController  implements ApplicationRunner{

    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;


    //获取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/655758.html

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

相关文章

香蕉派(Banana Pi) BPI-M2 Zero 评测试,与树莓派 Zero同尺寸的开发板

Banana Pi M2 Zero 是一款微型计算机&#xff0c;配备四核处理器并内置 Wi-Fi 和蓝牙。这是一款非常适合基本计算任务甚至轻度游戏的小型设备。在这篇评论中&#xff0c;我们将了解 M2 Zero 的性能、功能和价值。 什么是 Banana Pi BPI-M2 Zero Banana Pi M2 Zero 是由深圳公…

MongoDB(Windows版)安装

首先需要下载 官网&#xff1a;MongoDB: The Developer Data Platform | MongoDB 安装过程 需要安装的版本 第一步&#xff1a;安装时&#xff0c;Custom是指可以自定义安装路径&#xff0c;然后傻瓜式安装即可&#xff08;注意&#xff1a;先不要安装图形化工具&#xff0…

2023-06-17 LeetCode每日一题(分割圆的最少切割次数)

2023-06-17每日一题 一、题目编号 2481. 分割圆的最少切割次数二、题目链接 点击跳转到题目位置 三、题目描述 圆内一个 有效切割 &#xff0c;符合以下二者之一&#xff1a; 该切割是两个端点在圆上的线段&#xff0c;且该线段经过圆心。该切割是一端在圆心另一端在圆上…

【Vue】学习笔记-Vue Router activated deactivated 路由守卫

Vue Router activated deactivated 路由守卫 activated deactivated路由守卫1.全局守卫2.独享守卫3.组件内守卫全局路由守卫路由器的两种工作模式 activated deactivated activated 和 deactivated 是路由组件所独有的两个钩子&#xff0c;用于捕获路由组件的激活状态 具体使用…

管理类联考——英语——趣味篇——阅读——考题的来源

Part One考研英语阅读——Part A 1.卫报 《卫报》( The Guardian)是英国的全国性综合内容日报。与《泰晤士报》、《每日电讯报》被合称为英国三大报。由约翰爱德华容泰勒创办于1821年5月5日。该报注重报道国际新闻&#xff0c;擅长发表评论和分析性专题文章。一般公众视《卫报…

【数据分析之道-Matplotlib(九)】Matplotlib棉棒图

文章目录 专栏导读1、Matplotlib棉棒图stem()基本语法2、Matplotlib棉棒图stem()定义样式2.1linefmt参数2.2markerfmt参数2.3举例一&#xff1a;直线样式2.4举例二&#xff1a;圆点样式 3、棉棒图案例实战3.1绘制每月销量的棉棒图3.2绘制每月销量与平均销量之差 专栏导读 ✍ 作…

Prometheus介绍安装和快速入门

Prometheus介绍安装和快速入门 1、Prometheus介绍 1.1 什么是 Prometheus? Prometheus&#xff08;普罗米修斯&#xff09;是古希腊的一个神明&#xff0c;名字的意思是「先见之明」。从它的名字可以看出&#xff0c; Prometheus 是做「先见之明」的监控告警用途。维基百科…

HJ26 字符串排序

题目&#xff1a; HJ26 字符串排序 题解&#xff1a; 规则 1 &#xff1a;英文字母从 A 到 Z 排列&#xff0c;不区分大小写。 统一转换&#xff0c;通过减去对应字母的起始值&#xff0c;得到一个相对值&#xff0c;抹平大小写&#xff0c;例如&#xff1a;B - A&#xff…

【Java项目】使用LruCache提高DSP广告主需求方 (Demand Side Platform)系统性能

文章目录 背景LruCache简介LruCache在DSP系统中的应用场景LruCacheRedis增加LruCache数据过期清除机制ConcurrentHashMapLruCache零拷贝机制 源码 背景 我之前工作的一家公司是一家传媒公司&#xff0c;公司的主要盈利方式为在公司项目中接入广告&#xff0c;以及自媒体广告宣…

Windows下编译安装Acise

Acise是济南友泉软件公司自主研发的一套跨平台的通用CAx(CAD/CAE)软件开发框架&#xff0c;本文旨在记录Windows下编译安装Acise的流程。 零、系统环境 操作系统Windows 10编译器Visual Studio 2019 CommunityCMake2.24.2Boost1.80.0Qt5.14.0OpenCASCADE7.6.0VTK9.0.0 一、依…

Linux之线程安全(下)

文章目录 前言一、Linux线程互斥1.mutex的理解锁原子性互斥锁实现原子性的原理 2.mutex的封装——Mutex.hpp3.可重入和线程安全可重入线程安全线程安全不一定是可重入的&#xff0c;而可重入函数一定是线程安全的。 4.死锁概念造成死锁的四个必要条件如何避免死锁 二、Linux线程…

Mendix低代码开发

Mendix低代码开发 目录概述需求&#xff1a; 设计思路实现思路分析1.URL管理2.LL3.Mendix 低代码可视化开发4.Mendix 低代码可视化开发 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip har…

许多智能算法并不智能

数学的精髓在于不断寻找简洁而优美的解决方法&#xff0c;而智能的精髓也在于尽可能地避免繁琐的计算&#xff0c;通过更高效的方式来解决问题。从实践角度看&#xff0c;现代人工智能技术的发展&#xff0c;正是基于这个思路不断推进的。在机器学习领域中&#xff0c;人们通过…

【C语言复习】第二篇、VS2017软件的使用以及常用小技巧

目录 1、VS2017软件无法打开stdio.h文件的解决办法 2、VS2017软件解决scanf函数问题以及如何建立初始模板 2.1、visual Studio使用scanf函数出现报错问题 2.2、如何实现新建一个.c文件就有初始模板 3、VS2017软件如何显示代码行号&#xff1f; 4、VS2017软件如何快速复制…

Redis(Windows版)安装

Redis安装过程 目前只是Windows下安装&#xff0c;后续会添加linux下安装过程 Windows安装 下载地址&#xff1a;Releases tporadowski/redis (github.com) Redis安装要根据系统平台的实际情况而定&#xff0c;我使用的是免安装的 下载完成&#xff0c;解压之后打开文件夹…

怎么计算 flex-shrink 的缩放尺寸

计算公式: 子元素的宽度 - (子元素的宽度的总和 - 父盒子的宽度) * (某个元素的flex-shrink / flex-shrink总和) 面试问题是这样的下面 left 和 right 的宽度分别是多少 * {padding: 0;margin: 0;}.container {width: 500px;height: 300px;display: flex;}.left {width: 500px…

MySQL8.0安装过程中starting the server报错的解决方案(史上最详细)

MySQL8.0安装过程中starting the server报错的解决方案&#xff08;史上最详细&#xff09; 目录 MySQL8.0安装过程中starting the server报错的解决方案&#xff08;史上最详细&#xff09;报错情况&#xff1a;starting the server报错解决办法 报错情况&#xff1a;starting…

盘点开源ChatGPT建立的私有知识库

ChatGPT 可以落地的一个行业就是建立私有知识库&#xff0c;将ChatGPT落地TO B行业&#xff0c;可基于ChatGPT和私有数据构建智能知识库和个性化AI。 这个应该是ChatGPT 最热的一个创业方向。 可能出现的产品&#xff0c;有智能AI客服、企业内部/外部知识库、个人知识库&…

C++(9):顺序容器

顺序容器概述 所有顺序容器都提供了快速顺序访问元素的能力。 vector//可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 deque//双端队列。支持快速随机访问。在头尾位置插入/删除速度很快 list//双向链表。只支持双向顺序访问。在list中任何位置进…

oVirt 4.4.10三节点超融合集群安装配置及集群扩容(二)

在上节安装完成3节点集群后&#xff0c;在此基础上扩容到6节点<oVirt每次扩容后的容量必须是3的倍数> 操作步骤 在原始第一台服务器访问https://192.168.5.100:9090/,在"Virtualiztion"->“Hosted Engin"下点击"Manage Gluster” 点击"Ex…