背景
我们经常使用一些组件但是不明确其中的原理 ,比如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类似的功能,可以用于在微服务架构中实现服务发现、配置管理和相关功能。选择组件取决于具体的需求、与技术栈的兼容性以及开发团队的偏好。