【深入理解SpringCloud微服务】浅析微服务注册中心Eureka与nacos,手写实现一个微服务注册中心

【深入理解SpringCloud微服务】浅析微服务注册中心Eureka与nacos,手写实现一个微服务注册中心

  • 注册中心
  • 手写实现一个注册中心
    • 服务端设计
    • 客户端设计

注册中心

注册中心是微服务体系里面非常重要的一个核心组件,它最重要的作用就是实现服务注册与发现

在过去还没有微服务和注册中心的时候,一个服务存在对另一个服务的调用关系时,需要在自己服务的配置文件里面配置对方的ip端口,当发生调用时,需要读取配置文件里面对方的ip端口,组装请求url,发送请求。这种方式非常的不灵活,当被调用的服务集群用机器上下线时,调用方不能动态感知,需要手动修改配置然后重启服务,并且服务一旦多起来,维护这些配置也是一项繁琐的工作,很容易出错。

在这里插入图片描述

为了解决这个问题,于是就有了微服务注册中心。

注册中心是部署在分布式或者微服务环境下的一个中间件服务,提供服务的注册与发现功能。当使用了注册中心之后,调用方被称为服务消费者,被调用方被称为服务提供者。服务提供者启动时往注册中心注册,注册信息包括自己的服务名称、服务实例id、ip地址和端口等,注册中心把这些信息维护到内存注册表。服务消费者启动时(饿汉式加载)或者在发生调用时(懒加载)想注册中心发起服务发现请求,从注册中心拉取服务提供者注册上来的注册信息,缓存到本地的服务列表中。这样,服务消费者通过查询服务列表就能得知要调用的服务提供者的ip端口,无需在配置文件中进行配置,并且当服务提供者对应的服务集群有服务实例上下线时,服务消费者可以通过定时轮询注册中心或者注册中心主动通知的方式动态感知。

在这里插入图片描述

注册中心会在内存中维护一个服务注册表,用于存储服务提供者注册上来的信息。比如用一个双层Map,外层key是服务名,内层key是服务实例id(同一服务的不同实例组成集群,因此需要一个类似于id的唯一标识),value是ip端口。

在这里插入图片描述

内存中的注册信息有可能还会持久化或者存到外部的存储服务中,比如Mysql、redis、MongoDB、文件都可以。

在这里插入图片描述

注册中心为了避免单点故障,往往也是集群部署。因此,注册中心实例之间会有服务注册信息的同步。

在这里插入图片描述

当注册中心是集群式部署时,服务提供者启动时就通过某种方式选取到一台注册中心实例注册即可,注册中心会通过集群内同步把注册信息同步到其他注册中心实例。

在这里插入图片描述

由于服务有可能因为某些原因而出问题或者下线,服务注册中心需要通过某种方式对服务提供者进行健康检查,把不健康的服务实例从注册表中剔除。但是有的注册中心不会对服务提供者进行健康检查,而是给服务提供者注册上来的信息设置一个过期时间,服务提供者需要定期的进行服务续约,如果超过指定时间不续约,服务提供者的注册信息将会被注册中心从注册表中剔除。注册中心的注册表发生表动,会通知服务消费者,或者由服务消费者自己轮询感知注册表的变化。

在这里插入图片描述

手写实现一个注册中心

我们对注册中心已经有了一个认识,总结下来就是有一个服务注册服务端,维护了一个内存注册表,客户端请求服务端进行服务注册与发现,实际上就是读写内存注册表。然后注册中心服务端还要实现注册信息在集群内的同步、服务变更通知客户端、服务健康检查等功能。

那么,我们也可以实现一个自己的注册中心了。

服务端设计

我们还是参考Eureka和1.x版本的nacos,采用http服务端的实现方式。我们定义一个自己的Controller,名字就叫RegistryCenterController,是一个SpringMVC的Controller,接收客户端发来的http请求。

然后我们定义一个Service,名字叫RegistryCenterService,由它来处理内存注册表的读写,内存注册表就直接放在RegistryCenterService中。RegistryCenterController接收到请求之后,会调用registryCenterService进行请求处理。

在定义内存注册表的结构前,我们要定义一个用于存放注册信息的对象,我们定义一个MicroService对象用于封装服务提供者的注册信息,比如ip地址端口号等。

然后内存注册表的结构还是使用双层ConcurrentHashMap,外层key就是服务名serviceName(我们这里不考虑什么namespace和cluster之类的东西),内存key就是服务实例id,value就是MicroService,这样就是一个非常简单的双层ConcurrentHashMap结构的内存注册表,我们给它命名为registryTable。

客户端通过发送http请求来进行服务发现和服务注册,服务端通过RegistryCenterController接收http请求并调用RegistryCenterService读写内存注册表registryTable。

在这里插入图片描述

RegistryCenterController:

/*** @author huangjunyi* @date 2023/11/30 10:23* @desc*/
@RestController
@RequestMapping("/registry/center")
public class RegistryCenterController {@Autowiredprivate RegistryCenterService registryCenterService;...}

RegistryCenterService :

/*** @author huangjunyi* @date 2023/11/30 10:40* @desc*/
@Service
public class RegistryCenterService {// 内存注册表,双层ConcurrentHashMap:[serviceName, [id, 服务实例信息]]private Map<String, Map<String, MicroService>> registryTable = new ConcurrentHashMap<>();...
}

MicroService :

/*** @author huangjunyi* @date 2023/11/30 10:41* @desc*/
public class MicroService implements Serializable {private String serviceName;private String id;private String ip;private int port;private long lastTime;...
}    

至于服务同步,我们也是做异步处理。在RegistryCenterService 内部定义一个LinkedBlockingQueue类型的变量作为队列,把注册上来的信息放到这个队列里面,就把MicroService对象放进去。然后使用一个后台线程去轮询这个队列,把MicroService同步到集群中的其他注册中心实例。

在这里插入图片描述
RegistryCenterService :

@Service
public class RegistryCenterService {// 内存注册表,双层ConcurrentHashMap:[serviceName, [id, 服务实例信息]]private Map<String, Map<String, MicroService>> registryTable = new ConcurrentHashMap<>();// 集群同步队列private LinkedBlockingQueue<MicroService> syncQueue = new LinkedBlockingQueue<>();...public boolean registry(MicroService microService) throws InterruptedException {...// 注册上来的服务实例放入集群同步队列syncQueue.put(microService);return true;}...public void syncService() throws InterruptedException {...// 循环取出队列中的服务实例while (syncQueue.peek() != null) {MicroService service = syncQueue.take();// 轮询每个集群节点for (String node : nodes) {...// 通过http请求把实例信息同步过去Result result = restTemplate.postForObject(String.format("http://%s:%d/registry/center/sync", ip, port), service, Result.class);...}}}
}

RegistryCenterController通过“/sync”接口接收到注册信息同步后,会调用registryCenterService.addService(microService)方法:

    @PostMapping("/sync")public Result<?> sync(@RequestBody MicroService microService) {if (registryCenterService.addService(microService)) {return Result.ok(null);}return Result.error("sync failed");}

registryCenterService.addService(microService)方法把注册信息写入内存注册表:

    public boolean addService(MicroService microService) {registryTable.putIfAbsent(microService.getServiceName(), new ConcurrentHashMap<>());registryTable.get(microService.getServiceName()).put(microService.getId(), microService);LOGGER.info("this service[{}] added", microService.getServiceName() + ":" + microService.getId());return true;}

SyncServiceTask :

/*** @author huangjunyi* @date 2023/11/30 17:33* @desc*/
@Component
public class SyncServiceTask {@Autowiredprivate RegistryCenterService registryCenterService;@Scheduled(cron = "0/10 * * * * ?")public void syncService() throws InterruptedException {// 定时任务调用registryCenterService的syncService方法进行集群同步registryCenterService.syncService();}}

然后是健康检查,我们也是开一个后台线程,定时检查内存注册表中的服务实例信息里面的最近一次续约时间,超过30s没有续约,就把他踢掉,然后同步到集群中的其他注册中心。

在这里插入图片描述

/*** @author huangjunyi* @date 2023/11/30 11:43* @desc*/
@Component
public class HealthCheckTask {@Autowiredprivate RegistryCenterService registryCenterService;@Scheduled(cron = "0/30 * * * * ?")public void healthCheck() {// 定时任务调用registryCenterService的healthCheck方法进行监控检查registryCenterService.healthCheck();}}

最后,我们接入SpringBoot提供的自动装配机制,完成我们注册中心的自动配置,spring.factories文件配置指定我的配置类RegistryCenterServerConfig,然后我们的配置类RegistryCenterServerConfig通过@ComponentScan注解扫描RegistryCenterController、RegistryCenterService、定时任务类等一些核心类,定时任务使用Spring的@EnableScheduling和@Scheduled注解。

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.huangjunyi1993.simple.microservice.registry.center.server.config.RegistryCenterServerConfig

RegistryCenterServerConfig

@Configuration
@EnableScheduling
@ComponentScan(basePackages = {"com.huangjunyi1993.simple.microservice.registry.center.server.controller","com.huangjunyi1993.simple.microservice.registry.center.server.service","com.huangjunyi1993.simple.microservice.registry.center.server.task"})
@EnableConfigurationProperties({NodesProperties.class})
public class RegistryCenterServerConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}}

客户端设计

客户端方面,我们定义一个RegistryCenterClient用于通过OkHttp请求服务端进行服务注册和发现,RegistryCenterClient实现ApplicationListener<ContextRefreshedEvent>接口,监听ContextRefreshedEvent事件触发服务注册和服务发现。然后通过Spring的@EnableScheduling和@Scheduled注解开启定时任务使用OkHttp定时发送心跳。

在这里插入图片描述

/*** @author huangjunyi* @date 2023/11/30 20:21* @desc*/
public class SimpleRegistryCenterClient implements RegistryCenterClient, EnvironmentAware, ApplicationListener<ContextRefreshedEvent> {...@Overridepublic boolean sendHeartbeatToServer() {// 发送心跳,就是使用OkHttp请求注册中心服务端...}// 服务注册和服务发现@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {// 解析注册中心集群各实例的ip地址和端口String registryCenterServers = registryCenterConfig.getServers();String[] registryCenterServerArr = registryCenterServers.split(",");registryCenterServerList = new ArrayList<Server>();for (String registryCenterServer : registryCenterServerArr) {String[] ipPort = registryCenterServer.split(":");Server server = new Server();server.setIp(ipPort[0]);server.setPort(Integer.valueOf(ipPort[1]));registryCenterServerList.add(server);}// 服务注册if (microserviceConfig.isRegistry()) {registryToServer(); }// 服务发现if (!CollectionUtils.isEmpty(microserviceConfig.getSubscribeServiceNames())) {for (String subscribeServiceName : microserviceConfig.getSubscribeServiceNames()) {updateServiceList(subscribeServiceName);}}}@Overridepublic boolean registryToServer() {// 服务注册,就是使用OkHttp请求注册中心服务端...}// 服务发现public boolean updateServiceList(String serviceName) {serviceListMap.putIfAbsent(serviceName, new CopyOnWriteArrayList<>());// 轮询每个注册中心服务端实例for (Server server : registryCenterServerList) {// OKHttp代码....try {response = client.newCall(request).execute();if (response.isSuccessful()) {// 将拉取到的注册信息缓存到serviceListMap中Result result = JSONObject.parseObject(Objects.requireNonNull(response.body()).string(), Result.class);List<MicroService> microServiceList = JSONObject.parseArray(JSONObject.toJSONString(result.getData()), MicroService.class);if (CollectionUtils.isEmpty(microServiceList)) {LOGGER.warn("serviceList is Empty, serviceName={}", serviceName);continue;}serviceListMap.get(serviceName).clear();serviceListMap.get(serviceName).addAll(microServiceList);return true;}LOGGER.warn("updateServiceList {} failed: {}", server, response.body() != null ? response.body().toString() : "");} catch (IOException e) {LOGGER.error("updateServiceList {} error ", server, e);}}return false;}...
}

客户端的三个定时任务,代码就不看了:
在这里插入图片描述

RegistryCenterClient也是通过SpringBoot的自动装配机制自动创建。

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.huangjunyi1993.simple.microservice.registry.center.client.config.RegistryCenterClientConfig

RegistryCenterClientConfig

@Configuration
@EnableScheduling
@ComponentScan(basePackages = {"com.huangjunyi1993.simple.microservice.registry.center.client.task"})
@EnableConfigurationProperties({MicroserviceConfig.class, RegistryCenterConfig.class})
public class RegistryCenterClientConfig {@Bean@ConditionalOnMissingBean(RegistryCenterClient.class)public RegistryCenterClient registryCenterClient() {return new SimpleRegistryCenterClient();}}

详细代码可以从git上下载:https://gitee.com/huang_junyi/simple-microservice

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

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

相关文章

【MyBatisPlus】快速掌握MP插件使用方法

一、MyBatis-Plus简介 1.1 简介 1.2 特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑损耗小&#xff1a;启动即会自动注入基本 CURD&#xff0c;性能基本无损耗&#xff0c;直接面向对象操作强大的 CRUD 操作&#x…

【ACM独立出版|EI检索稳定】2024年智能感知与模式识别国际学术会议(ISPC 2024,9月6日-8)

2024年智能感知与模式识别国际学术会议 (ISPC 2024)将于2024年9月6日-8日于中国青岛召开。 会议将围绕智能感知与模式识别等领域中的最新研究成果&#xff0c;为来自国内外高等院校、科学研究所、企事业单位的专家、教授、学者、工程师等提供一个分享专业经验&#xff0c;扩大…

初谈Linux信号-=-信号的产生

文章目录 概述从生活角度理解信号Linux中信号信号常见的处理方式理解信号的发送与保存 信号的产生core、term区别 概述 从生活角度理解信号 你在网上买了很多件商品&#xff0c;再等待不同商品快递的到来。但即便快递没有到来&#xff0c;你也知道快递来临时&#xff0c; 你该…

机械臂泡水维修|机器人雨后进水维修措施

如果机器人不慎被水淹&#xff0c;别慌&#xff01;我们为你准备了一份紧急的机械臂泡水维修抢修指南&#xff0c;帮助你解决这个问题。 【机器人浸水被淹后紧急维修抢修&#xff5c;如何处理&#xff1f;】 机械臂被淹进水后维修处理方式 1. 机械手淹水后断电断网 首先&am…

spring整合mybatis,junit纯注解开发(包括连接druid报错的所有解决方法)

目录 Spring整合mybatis开发步骤 第一步&#xff1a;创建我们的数据表 第二步&#xff1a;编写对应的实体类 第三步&#xff1a;在pom.xml中导入我们所需要的坐标 spring所依赖的坐标 mybatis所依赖的坐标 druid数据源坐标 数据库驱动依赖 第四步&#xff1a;编写SpringC…

linux在ssh的时候询问,yes or no 如何关闭

解决&#xff1a; 在~/.ssh/config文件中添加如下配置项&#xff1a; Host *StrictHostKeyChecking no

数据可视化配色新工具,颜色盘多达2500+类

好看的配色,不仅能让图表突出主要信息,更能吸引读者,之前分享过很多配色工具,例如, 👉可视化配色工具:颜色盘多达3000+类,数万种颜色! 本次再分享一个配色工具pypalettes,颜色盘多达2500+类。 安装pypalettes pip install pypalettes pypalettes使用 第1步,挑选…

【LeetCode】分隔链表

目录 一、题目二、解法完整代码 一、题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&a…

JVM中的GC流程与对象晋升机制

JVM中的GC流程与对象晋升机制 1、JVM堆内存结构2、Minor GC流程3、Full GC流程4、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;垃圾回收&#xff08;GC&#xff09;是自动管…

VTK源码分析:Type System

作为一款开源跨平台的数据可视化代码库&#xff0c;VTK以其清晰的流水线工作方式、丰富的后处理算法、异种渲染/交互方式&#xff0c;而被众多CAx软件选作后处理实施方案。而异种渲染/交互方式的实现&#xff0c;主要是倚重于VTK的类型系统&#xff0c;因此&#xff0c;有必要对…

最新 Docker 下载镜像超时解决方案:Docker proxy

现在Docker换源也下载失败太常见了&#xff0c;至于原因&#xff0c;大家懂得都懂。本文提供一种简洁的方案&#xff0c; 利用 Docker 的http-proxy&#xff0c;代理至本机的 proxy。 文章目录 前言Docker proxy 前言 这里默认你会安装 clash&#xff0c;然后有配置和数据库。…

排序算法

排序算法 内部排序&#xff1a;指将需要处理的所有数据都加载到内部存储器中进行排序 外部排序&#xff1a;数据量过大&#xff0c;无法全部加载到内存中&#xff0c;需要借助外部存储进行排序 算法的时间复杂度 一个算法花费的时间与算法中语句的执行次数成正比&#xff0c;…

Unity XR Interaction Toolkit(VR、AR交互工具包)记录安装到开发的流程,以及遇到的常见问题(一)!

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神不吝指教&#xff01; 文章目录 前言一、XR Interaction Toolkit是什么&#xff1f;二、跨平台交互三、 AR 功能四、XR Interaction Toolkit的特点五、XR Interaction Toolkit 示例总结 前言 随着VR行业的发展&#…

一文搞懂 Java 基础:新手入门必备

目录 &#x1f4dd; Java基础Java起源第一个Java程序基础语法Java标识符Java变量Java注释Java数据类型Java运算符Java流程控制语句 &#x1f4dd; Java基础 Java起源 Java programming language具有大部分编程语言所共有的一些特征&#xff0c;被特意设计用于互联网的分布式环…

《算法笔记》总结No.10——链表

从第10期破例插叙一期单链表的实现&#xff0c;这个东东相当重要&#xff01;考研的同学也可以看&#xff1a;相较于王道考研的伪码不太相同&#xff0c;专注于可以运行。如果是笔试中的伪码&#xff0c;意思正确即可~ 注&#xff1a;博主之前写过一个版本的顺序表和单链表的C实…

Jolt路线图

1. 引言 a16z crypto团队2024年7月更新了其Jolt路线图&#xff1a; 主要分为3大维度&#xff1a; 1&#xff09;链上验证维度&#xff1a; 1.1&#xff09;Zeromorph&#xff1a;见Aztec Labs团队2023年论文 Zeromorph: Zero-Knowledge Multilinear-Evaluation Proofs from…

视觉巡线小车——STM32+OpenMV

系列文章目录 第一章&#xff1a;视觉巡线小车——STM32OpenMV&#xff08;一&#xff09; 第二章&#xff1a;视觉巡线小车——STM32OpenMV&#xff08;二&#xff09; 第三章&#xff1a;视觉巡线小车——STM32OpenMV&#xff08;三&#xff09; 第四章&#xff1a;视觉巡…

【过题记录】 7.21

Mad MAD Sum 算法&#xff1a;思维&#xff0c;前缀最大值 模拟一下他的运行过程就会发现&#xff0c;两次之后整个数组就固定了&#xff0c;之后每次都是每个数往后移动一位&#xff0c;可以模拟两次之后计算每个数的存活轮数&#xff0c;计算贡献。 #include<bits/stdc.h…

JavaSE 知识梳理(下)

1.继承 继承是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特 性 的基础上进行扩展&#xff0c;增加新功能&#xff0c;这样产生新的类&#xff0c;称派生类。 继承主要解决的问题是&#xff1a;共性的抽取&#xff0c;实现代码复用&a…

【D3.js in Action 3 精译_018】2.4 向选择集添加元素

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可视化最佳实践&#xff08;下&#xff09;1.4 本章小结 第二章…