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

简介: 手写注册中心基本原理

概念说明

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

需求分析

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

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

核心功能

「 服务注册与发现 」:Nacos可以作为服务注册中心,服务提供者可以将自己的服务注册到Nacos中,而服务消费者可以从Nacos中发现并获取可用的服务实例。Nacos支持主流的服务注册与发现协议,包括基于HTTP的RESTful接口和基于DNS的服务发现。

「 动态配置管理 」:Nacos提供了一个统一的配置管理平台,可以集中管理应用程序的配置信息。它支持动态配置更新和推送,当配置发生变化时,Nacos会及时通知到应用程序。Nacos还支持配置的版本管理和灰度发布,可以方便地进行配置的管理和控制。

「 服务健康监测 」:Nacos可以对注册到其上的服务进行健康检查,通过定时发送心跳来判断服务的可用性。当服务不可用时,Nacos会将其从服务列表中移除,从而保证服务的高可用性和可靠性。

「 负载均衡 」:Nacos提供了负载均衡的能力,可以根据不同的负载均衡策略将请求分发到不同的服务实例上,从而实现请求的负载均衡。

「 服务路由与流量管理 」:Nacos支持服务的动态路由和流量管理,可以根据不同的规则将请求导向不同的服务实例,从而实现服务的灵活路由和流量控制。

「 配置共享与分组管理 」: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服务需要考虑启动类和业务类的路径,确保程序启动能够扫描到引入的业务类

总结提升

思想上移,行动下移:之前对nacos注册中心都是在概念,通过手动实现把nacos注册中心写出来之后对于nacos注册中心有了更深入的理解

知其然也要知其所以然:之气只是简单的使用,哪里出现了问题也不清楚只能靠蒙和猜来解决问题。现在可以非常明确的知道是哪个环节出现了问题。底层原理明确使用起来也非常的简单。

不断迭代完善:这个版本没有添加健康检测机制后面随着不断的版本迭代会非常其他相关的内容。


相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
6天前
|
SpringCloudAlibaba 负载均衡 Java
【微服务 SpringCloudAlibaba】实用篇 · Nacos注册中心
【微服务 SpringCloudAlibaba】实用篇 · Nacos注册中心
21 3
|
6天前
|
安全 Linux Nacos
如何使用公网地址远程访问内网Nacos UI界面查看注册服务
如何使用公网地址远程访问内网Nacos UI界面查看注册服务
25 0
|
6天前
|
负载均衡 Cloud Native Java
Nacos 注册中心(2023旧笔记)
Nacos 注册中心(2023旧笔记)
21 0
|
6天前
|
Dubbo Java 应用服务中间件
深度剖析:Dubbo使用Nacos注册中心的坑
2020年笔者在做微服务部件升级时,Dubbo的注册中心从Zookeeper切换到Nacos碰到个问题,最近刷Github又有网友提到类似的问题,就在这篇文章里做个梳理和总结。
深度剖析:Dubbo使用Nacos注册中心的坑
|
6天前
|
SpringCloudAlibaba Java Nacos
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
|
6天前
|
Dubbo Java 应用服务中间件
双活工作下的数据迁移:Nacos注册中心实战解析
这篇内容介绍了如何使用NacosSync组件进行双活项目中的注册中心数据迁移。首先,准备包括64位OS、JDK 1.8+、Maven 3.2+和MySQL 5.6+的环境。接着,获取并解压NacosSync安装包,配置数据库连接,启动服务,并通过访问特定URL检查系统状态。然后,通过NacosSync控制台进行集群配置,添加Zookeeper和Nacos集群,并设置同步任务。当数据同步完成后,Dubbo客户端(Consumer和Provider)更新配置以连接Nacos注册中心。最后,迁移完成后,原有的Zookeeper集群可下线,整个过程确保了服务的平滑迁移。
46 1
|
6天前
|
XML Dubbo Java
【Dubbo3高级特性】「框架与服务」 Nacos作为注册中心-服务分组及服务分组聚合实现
【Dubbo3高级特性】「框架与服务」 Nacos作为注册中心-服务分组及服务分组聚合实现
66 0
|
6天前
|
运维 Kubernetes Nacos
nacos常见问题之服务注册IP白名单如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
34 0
|
6天前
|
Dubbo 关系型数据库 MySQL
nacos常见问题之命名空间配置数据上线修改如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
106 1
|
6天前
|
SpringCloudAlibaba 应用服务中间件 Nacos
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心(下)
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心
20 0

热门文章

最新文章