【探索SpringCloud】服务发现

news2024/11/25 21:27:48

前言

今天,我们来聊聊SpringCloud服务发现。主要有如下几个议题:
一、服务发现的概念与方案;二、SpringCloud是如何与各个服务注册厂商进行集成的。

服务发现

在微服务架构中,我们不可避免的需要通过服务间的调用来完成系统功能。于是我们面临的第一个问题就是:怎么知道目标服务的IP?如果只有一个IP,问题不大,直接写死在URL里访问就是了。但是遗憾的是,为了保证服务的高可用,通常都是多台实例部署。除此之外,在某些时候,系统搞活动,存在突发流量,那么我们还会存在动态扩容。这意味着,我们的服务实例个数可能都是不固定的。我们总不能因为服务实例变更就发布版本改地址吧?怎么办呢?

我们需要一个能够自动地动态地感知服务实例的组件:服务注册中心。

怎么做到的

首先,需要提醒一下,服务发现的实现是一个协作的过程,并不是引入某个组件就自动实现了。协作的步骤:

  1. 服务提供者将自己的信息(包括,服务名、IP、端口等)注册到服务注册中心。
  2. 服务消费者从服务注册中心拉取目标服务实例信息。
  3. 服务消费者根据目标服务实例信息改写请求地址,请求目标服务。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjOJQM6V-1684068242693)(https://www.baeldung.com/wp-content/uploads/sites/4/2022/01/Service-Discovery-1-1.png)]

使用模式

当我们有了服务注册中心之后,我们便有了服务发现的基础。不过,RPC有两端(服务端、客户端),所以完成服务发现也就存在两种模式。其本质区别就是:谁来发现服务。

服务端-服务发现

由服务端负责服务发现,客户端只需要调用某个固定的地址就行。为了实现这个目标,服务端需要引入一个新的组件:路由,又叫网关。路由会从服务注册中心拉取服务实例,并选择实例,以及转发请求。

  • 典型应用:外部系统调用内部系统。
  • Spring Cloud提供的路由:Spring Cloud Gateway

服务端服务发现

客户端-服务发现

由客户端负责服务发现,自主选择服务实例进行调用。从服务发现的角度看,只要完成从服务注册中心拉取到服务实例列表,就算完成了。但是我们知道服务实例列表只是为了完成服务调用,仅仅只是个开始。客户端还要完成服务选择(负载均衡)、服务调用。

  • 典型应用:内部系统互相调用。

客户端服务发现

服务发现的实现

名称CAP一致性协议描述
NacosCP/APCP:蚂蚁金服-JRaft; AP:阿里-DistroNacos的服务提供者可自行选择CP/AP,默认AP
EurekaAP尽最大努力复制(非真正意义上的协议)来自Netflix. Spring Cloud Netflix虽然依然在更新,但其底层依赖的Eureka 2.0.0也已经不维护了
ZookeeperCPZABDubbo默认使用Zookeeper
ConsulCPRaft客户端使用gossip协议。大多在ServiceMesh使用

注:Nacos是个神奇又新鲜的玩意儿。他把一致性直接做到数据层面,意味着他可以同时存在基于AP协议实现一致性的数据,以及基于CP协议实现一致性的数据。对Nacos的设计和实现感兴趣的同学,可以看看官方的《Nacos架构&原理》

Spring Cloud的服务发现

到这里,我们知道服务发现有两个重大步骤:一是服务提供者注册服务,一是服务消费者拉取服务实例列表。为此,Spring Cloud也是有两个对应的抽象。

服务注册:ServiceRegistry

package org.springframework.cloud.client.serviceregistry;

/**
 * Contract to register and deregister instances with a Service Registry.
 * 按照服务注册中心的约定,注册/注销实例。
 *
 * @param <R> registration meta data
 * @author Spencer Gibb
 * @since 1.2.0
 */
public interface ServiceRegistry<R extends Registration> {

	/**
	 * 注册实例。一个典型的注册信息包括:主机名和端口
	 * @param registration 注册信息,实例元数据。
	 */
	void register(R registration);

	/**
	 * 注销实例。
	 * @param registration 注册信息,实例元数据。
	 */
	void deregister(R registration);

	/**
	 * 关闭服务注册,这是个生命周期方法.
     * 关闭服务注册,将会销毁注册信息,同时不再保持心跳等实例保活机制。但不影响服务注册中心的运行。
	 */
	void close();

	/**
	 * 设置注册信息的状态。状态值由独立的实现决定.
	 * @param registration 需要更新的注册信息.
	 * @param status 要设置的状态
	 * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
	 */
	void setStatus(R registration, String status);

	/**
	 * 获取特定的注册信息。
	 * @param registration 需要查询的注册信息.
	 * @param <T> 状态的类型信息.
	 * @return 注册信息的状态.
	 * @see org.springframework.cloud.client.serviceregistry.endpoint.ServiceRegistryEndpoint
	 */
	<T> T getStatus(R registration);

}

有了这个抽象,不管我们选择什么实现,都能方便地切换了。

服务发现客户端:DiscoveryClient

public interface DiscoveryClient extends Ordered { 
	// 获取某个服务的所有实例
	List<ServiceInstance> getInstances(String serviceId);
	// 获取所有服务名
	List<String> getServices();
}

DiscoveryClient作为SpringCloud的抽象层,便于SpringCloud统一调用接口,而无需关系底层实现。其核心方法就两个,见上面的源码。

自动注册原理

ServiceRegistry相当于提供了个工具,但怎么使用这个工具进行注册,自动注册,Spring Cloud提供的默认实现是:事件监听机制。

AutoServiceRegistration
这个接口目前算是个标记接口,但他也算提供了未来扩展的可能。
为了便于提供商接入,提供了AbstractAutoServiceRegistration抽象类,而他就是自动注册的关键。

/**
 * 为了便于ServiceRegistry的实现,提供的有用且通用的生命周期方法。
 */
public abstract class AbstractAutoServiceRegistration<R extends Registration>
		implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
			
	@Override
	@SuppressWarnings("deprecation")
	public void onApplicationEvent(WebServerInitializedEvent event) {
		// 会调用到start方法
	}
	
	public void start() {
		// 1. 检查是否开启自动注册

		// 2. 如果尚未注册过,则进行注册。
		if (!this.running.get()) {
			// 2.1 发布预注册事件:InstancePreRegisteredEvent
			... 

			// 2.2 注册实例,并检查是否需要注册本地管理服务(JMX)
			register();
			if (shouldRegisterManagement()) {
				registerManagement();
			}
			// 2.3 发布注册事件:InstanceRegisteredEvent
			...

			// 2.4 将状态改为已注册
			this.running.compareAndSet(false, true);
		}

	}	
	
	protected void register() {
		this.serviceRegistry.register(getRegistration());
	}

	/**
	 * Registration就是当前实例的基本信息
	 */
	protected abstract R getRegistration();

	@PreDestroy
	public void destroy() {
		// 服务关机下线,要取消注册
		stop();
	}

	public void stop() {
		if (this.getRunning().compareAndSet(true, false) && isEnabled()) {
			deregister();
			if (shouldRegisterManagement()) {
				deregisterManagement();
			}
			this.serviceRegistry.close();
		}
	}

}

可以看到这个抽象类,通过监听WebServerInitializedEvent事件来做的自动注册。正是这点,也使得通过该接口实现自动注册的服务中心客户端,绑定了Spring内置的Tomcat。因为只有内置的tomcat,才会通过spring的上下文发布该事件。

对于客户端,额,对的。这些接口/组件都是客户端的,是集成我们的应用上的。这里提醒一下大家哈。对于我们的应用而言,只要启动完成后,将应用注册到服务中心就行了。因此,并不是所有的服务中心客户端都那样实现。只要我们能知道服务器的IP/机器名和端口,我们自己就能实现。举个简单的例子:自定义配置项把IP和端口配置上。实现上下文监听器监听ContextRefreshedEvent即可。

而关于如何在非内置tomcat的应用实现自动注册,在Nacos的Issue也有讨论,感兴趣的可以自己看看:tomcat部署没有自动注册服务#341

小结

从上面的抽象接口,可以发现一个服务注册中心的客户端,要对接SpringCloud,需要:

  1. 实现ServiceRegistry,以便通过他来与服务注册中心进行交互,完成注册。
  2. 实现DiscoveryClient,便于与SpringCloud的其他组件进行整合,为后续的负载均衡、远程调用打下基础。
  3. 实现Registration接口,统一服务实例信息的获取。这个接口没有单独拎出来,因为重要是一些getter方法。
  4. 实现AbstractAutoServiceRegistration,以便通过Spring的事件监听机制,触发向服务注册中心注册当前实例的这个动作。

下面是比较常见的几个注册中心的相关组件实现。

ServiceRegistryDiscoveryClientRegistrationAbstractAutoServiceRegistration
ZookeeperServiceRegistryZookeeperDiscoveryClientZookeeperRegistrationZookeeperAutoServiceRegistration
CousulServiceRegistryCousulDiscoveryClientCousulRegistrationCousulAutoServiceRegistration
NacosServiceRegistryNacosDiscoveryClientNacosRegistrationNacosAutoServiceRegistration
EurekaServiceRegistryEurekaDiscoveryClientEurekaRegistration-

如上面聊到并不是所有的服务中心客户端都选择AbstractAutoServiceRegistration来实现自动注册那样,上面表格中的Eureka就是一个例子。Eureka留了一手,我们一起来看看:

public class EurekaAutoServiceRegistration implements AutoServiceRegistration,
		SmartLifecycle, Ordered, SmartApplicationListener {
			
	@Override
	public void start() {
		// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
		if (this.port.get() != 0) {
			if (this.registration.getNonSecurePort() == 0) {
				this.registration.setNonSecurePort(this.port.get());
			}

			if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
				this.registration.setSecurePort(this.port.get());
			}
		}

		// only initialize if nonSecurePort is greater than 0 and it isn't already running
		// because of containerPortInitializer below
		if (!this.running.get() && this.registration.getNonSecurePort() > 0) {

			this.serviceRegistry.register(this.registration);

			this.context.publishEvent(new InstanceRegisteredEvent<>(this,
					this.registration.getInstanceConfig()));
			this.running.set(true);
		}
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof WebServerInitializedEvent) {
			onApplicationEvent((WebServerInitializedEvent) event);
		}
		else if (event instanceof ContextClosedEvent) {
			onApplicationEvent((ContextClosedEvent) event);
		}
	}
}

虽然没有实现AbstractAutoServiceRegistration,但实现了几个关键接口:

  1. 自动注册的标记接口:AutoServiceRegistration
  2. SmartLifeCycle,通过start方法来完成注册。
  3. SmartApplicationListener,通过监听WebServerInitializedEvent事件来完成注册。

这样当应用运行在外置的tomcat中,则SmartLifeCycle发挥作用,完成注册。而当运行在内置的tomcat中,则与SpringCloud原生的接口一样,通过监听WebServerInitializedEvent完成注册。

参考

Pattern: Server-side service discovery

Pattern: Client-side service discovery

Service Discovery in Microservices

服务发现技术选型那点事儿

5种微服务注册中心如何选型?这几个维度告诉你

java源码详解系列(十二)–Eureka的使用和源码

Springboot War包部署下nacos无法注册问题

Eureka核心源码解析系列(一)- 服务注册、续约篇

后记

这次咱们探讨了在SpringCloud中是如何完成服务自动注册的。下次,咱们就以Nacos为例,试着更深入的理解服务注册中心的:
服务续约、服务剔除、服务下线、服务发现。

这篇文章拖太久了,最近虽然有些忙,但自己确实懒惰了一些。加油吧。

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

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

相关文章

Fourier分析入门——第1章——数学预备知识

第 1 章 学习Fourier分析的数学预备知识 目录 第 1 章 学习Fourier分析的数学预备知识 1.1 引言 1.2 几何和代数的一些相关概念的回顾 1.2.1 标量运算(scalar arithmetic) 1.2.2 向量运算(vector arithmetic) 1.2.3 向量乘法(vector multiplication) 1.2.4 向量长度 …

设计模式(java)-观察者模式

1. 简介 观察者模式&#xff0c;行为型设计模式。观察者模式在实际项目实践中&#xff0c;是一种使用较频繁的设计模式&#xff0c;它主要解决的是信息同步的问题&#xff0c;当多个对象需要从同一个主题中得到自身所需要的信息或状态&#xff0c;并通过这些信息或状态做出相应…

以太网外设ETH

1. 概述 近几年&#xff0c;项目需要&#xff0c;在多款单片机上使用了以太网外设。 本文为阶段知识整理&#xff0c;查缺补漏&#xff0c;方便以后再次遇到相关任务时&#xff0c;可以游刃有余的完成工作。 1.1 修改时间 2023年5月6日创建本文。包含STM32的ETH外设。2023年…

利用CNN对车牌进行智能识别(python代码,解压缩后直接运行)

1.代码流程 该段代码主要利用卷积神经网络&#xff08;CNN&#xff09;来识别车牌。下面是代码的主要流程&#xff1a; 导入所需的库和模块&#xff0c;包括matplotlib、numpy、cv2、tensorflow等。 加载用于检测车牌的级联分类器&#xff08;cascade classifier&#xff09;…

可见光遥感目标检测(一)任务概要介绍

前言 本篇开始对遥感图像的目标检测进行介绍&#xff0c;介绍了其目标前景、数据集以及评价指标。 本教程禁止转载。同时&#xff0c;本教程来自知识星球【CV技术指南】更多技术教程&#xff0c;可加入星球学习。 Transformer、目标检测、语义分割交流群 欢迎关注公众号CV技…

机器学习13(正则化)

文章目录 简介正则化经验风险和结构风险过拟合正则化建模策略 逻辑回归逻辑回归评估器 练习评估器训练与过拟合实验评估器的手动调参 简介 这一节详细探讨关于正则化的相关内容&#xff0c;并就 sklearn 中逻辑回归&#xff08;评估器&#xff09;的参数进行详细解释由于 skle…

javaweb项目实战之myBlog

项目简介 技术栈&#xff1a; Java Mysql Html Ajax Css JS Json 项目说明 &#xff1a;项目使用maven创建&#xff0c;使用MVC架构模式 表示层&#xff1a;通俗讲就是展现给用户的界面和控制器层Servlet&#xff0c;接受请求、封装数据、调用业务 逻辑层&#xff0c;响…

libevent高并发网络编程 - 05_libevent实现http客户端

文章目录 1 http客户端相关的APIevhttp_uri_parse()evhttp_uri_get_scheme()evhttp_uri_get_port()evhttp_uri_get_host()evhttp_uri_get_path()evhttp_uri_get_query()evhttp_connection_base_bufferevent_new()evhttp_request_new()evhttp_make_request()evhttp_request_get_…

刷题刷题,开心

一先来每日一题 在一个仓库里&#xff0c;有一排条形码&#xff0c;其中第 i 个条形码为 barcodes[i]。 请你重新排列这些条形码&#xff0c;使其中任意两个相邻的条形码不能相等。 你可以返回任何满足该要求的答案&#xff0c;此题保证存在答案。 示例 1&#xff1a; 输入&…

基于html+css图展示59

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Prompt learning 教学[最终篇]:Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐

Prompt learning 教学[最终篇]&#xff1a;Chatgpt使用场景推荐、优秀学习资料推荐、AI工具推荐 1.chatgpt使用场景推荐 各位应该在各种平台看到不少可以尝试使用的场景&#xff0c;我这里仅收录&#xff1a; 有意思的场景&#xff1a;一般比较垂直或者小众&#xff0c;或者出…

CobaltStrike项目实战

环境介绍 模拟内网有三台机器&#xff1a;WEB、PC和DC。 WEB服务器有两个网络适配器&#xff0c;适配器1处于NAT模式用于连接外网&#xff0c;适配器2用于内网。 PC和WEB服务器一样&#xff0c;有两个适配器&#xff0c;能够同时访问外网和内网&#xff1b;DC作为域控制器&…

神经网络的训练过程、常见的训练算法、如何避免过拟合

神经网络的训练是深度学习中的核心问题之一。神经网络的训练过程是指通过输入训练数据&#xff0c;不断调整神经网络的参数&#xff0c;使其输出结果更加接近于实际值的过程。本文将介绍神经网络的训练过程、常见的训练算法以及如何避免过拟合等问题。 神经网络的训练过程 神…

henan Problem E. 矩阵游戏

hunan Problem E. 矩阵游戏 Attachments - 2023 CCPC Henan Provincial Collegiate Programming Contest - Codeforces 思路&#xff1a; 我们考虑用dp,定义f[i][j][k],代表从1,1走到i,j并且使用k次变换操作能够获得的最大 价值&#xff0c;那么类似于01背包&#xff0c;接下…

分布式数据库集成解决方案2

分布式数据库集成解决方案2 扩展阅读内部结构1.表空间&#xff08;TABLESPACE&#xff09; # 摘要 : 本文讨论了某公司发货系统的分布式数据库集成解决方案。该公司由于业务的发展&#xff0c;要在另三个城市设立货仓进行发货。为此&#xff0c;需要增加原先的MIS系统实现这一功…

javaweb系列-js函数、数组、字符串

1.4 函数 JavaScript中的函数被设计为执行特定任务的代码块&#xff0c;通过关键字function来定义。JavaScript中定义函数有2种语法。 第一种方法&#xff1a; <script>//定义function add(a,b){return a b;}//调用var a add(1,2);alert(a); </script> 第二种方…

【LeetCode】204.计数质数

204.计数质数&#xff08;中等&#xff09; 思路 埃拉托斯特斯筛法&#xff08;简称埃氏筛法&#xff09;&#xff0c;适用于「判断一个整数是否是质数」&#xff0c;该方法可以在判断一个整数 n 时&#xff0c;同时判断所有小于 n 的整数。 从 1 到 n 进行遍历&#xff0c;假…

内网渗透之权限维持-域控后门-SSPHOOKDSRMSIDSkeleton-Key

权限维持-基于验证DLL加载-SSP 方法一:但如果域控制器重启&#xff0c;被注入内存的伪造的SSP将会丢失。 mimikatz privilege::debug misc::memsspC:\Windows\System32\mimilsa.log 记录登录的账号密码 方法二:使用此方法即使系统重启&#xff0c;也不会影响到持久化的效果…

Plus and Multiply

题目&#xff1a; 题意解析&#xff1a; 有一个无穷大的正整数集合 S&#xff0c;该集合按下面所述方法生成&#xff1a; 数字 1 在集合 S 中。 若数字 x 在该集合中&#xff0c;那么数 xa 和数 xb 均在集合 S 中。&#xff08;其中 a 与 b 为给定常数&#xff09; 现在给出…

快速创作攻略:10分钟让你实现从文章撰写到多平台发布,支持公众号、B站、微博、知乎、头条等20种

快速创作攻略&#xff1a;10分钟让你实现从文章撰写到多平台发布&#xff0c;支持公众号、B站、微博、知乎、头条等20种 很多文字内容创作者&#xff0c; 想分享一个idea&#xff0c; 想介绍一个工具&#xff0c; 想分享经验或知识。 常碰到以下难题&#xff1a; 有个灵感但是要…