【探索SpringCloud】服务发现

前言

今天,我们来聊聊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为例,试着更深入的理解服务注册中心的:
服务续约、服务剔除、服务下线、服务发现。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝牙网状网络的基本原理及应用开发

借助蓝牙 5 的网状网络功能&#xff0c;开发人员可以增强无线连接系统&#xff08;如物联网设备&#xff09;的通信范围和网络可用性。但是&#xff0c;网状网络的低功耗无线硬件设计与网状网络软件开发之间存在着复杂的层次&#xff0c;这可能会使开发人员迅速陷入混乱并危及项…

今年的面试难度有点大....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

Windows Cygwin 配置

Windows Cygwin 配置 一、什么是Cygwin&#xff1f; Cygwin&#xff0c;原Cygnus出品&#xff08;已被红帽收购&#xff09;&#xff0c;目前是RedHat名下的项目。项目的目的是提供运行于 Windows 平台的类 Unix 环境&#xff08;以 GNU 工具为代表&#xff09;。为了达到这个…

一天吃透SpringCloud面试八股文

1、什么是Spring Cloud &#xff1f; Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序&#xff0c;提供与外部系统的集成。Spring cloud Task&#xff0c;一个生命周期短暂的微服务框架&#xff0c;用于快速构建执行有限数据处理的应用程序。 Sprin…

前端|想到什么写什么

记录当初伤害过我的一些概念&#x1f60a; 文章目录 一、闭包二、深拷贝、浅拷贝三、slice、splice、join、split、filter、concat、sort、some、every四、for in和for of、 map和foreach五、原型和原型链六、跨域七、vue相关1、生命周期2、响应式原理3、watch和computed4、vu…

协程切换原理与实践 -- 从ucontext api到x86_64汇编

目录 1.协程切换原理理解 2.ucontext实现协程切换 2.1 实现流程 2.2 根据ucontext流程看协程实现 2.3 回答开头提出的问题 3.x86_64汇编实现协程切换 3.1libco x86_64汇编代码分析 3.2.保存程序返回代码地址流程 3.3.恢复程序地址以及上下文 4.实现简单协程框架 1.协程…

MySQL 中 CONCAT 函数使用

1&#xff1a;创建数据表&#xff1a; CREATE TABLE user ( id int NOT NULL AUTO_INCREMENT, code varchar(255) NOT NULL, name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT3 DE…

8款数据迁移工具选型,主流且实用

前言&#xff1a;ETL(是Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程)&#xff0c;对于企业应用来说&#xff0c;我们经常会遇到各种数据的处理、转换、迁移的场景。今天特地给大家汇总了一些目前市面上比较常用的ETL数据迁移工具&#xff0c;希望对…

小黑子—Java从入门到入土过程:第九章-IO流

Java零基础入门9.0 Java系列第九章- IO流1. 初识IO流2. IO流的体系2.1 字节流2.1.1 FileOutputStream 字符串输出流2.1.1 - I 字符串输出流的细节2.1.1 - II FileOutputStream写数据的3种方式2.1.1 -III FileOutputStream写数据的两个小问题 2.1.2 FileInputStream 字符串输入流…

拼多多买家如何导出“个人中心”订单信息

经常在拼多多买东西&#xff0c;有时候需要把订单的物流信息导出来&#xff0c;方便记录和统计。现介绍如何使用dumuz工具来实现批量下载拼多多订单。 应用功能描述 模拟人工操作拼多多"个人中心-我的订单”订单网页&#xff0c;批量查询获取拼多多自己买的商品的订单数…

javaweb项目实战之myBlog

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

JavaScript实现计算100之间能被5整除的数的代码

以下为实现计算100之间能被5整除的数的程序代码和运行截图 目录 前言 一、计算100之间能被5整除的数 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博文代码可以根据题…

平衡二叉树理论详解

文章目录 基本概念平衡二叉树插入结点LL&#xff08;左单旋&#xff09;RR&#xff08;右单旋&#xff09;LR&#xff08;左右旋&#xff09;RL&#xff08;右左旋&#xff09; 示例插入推导过程 基本概念 平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1&…

linux环境下设置python定时任务

linux环境下设置python定时任务 Linux 系统提供了使用者控制计划任务的命令 :crontab 命令 1、在linux环境执行命令,进入编辑界面 crontab -e2、按键盘 i 键&#xff0c;进入编辑模式&#xff0c;输入以下内容&#xff0c;设置2个定时任务 定时任务1&#xff1a;每隔10分钟执…

C语言爬取HTML-爬取壁纸 文末附源码

前言&#xff1a;这学期计算机软件课程设计的其中一个题目是使用C语言爬取HTML&#xff0c;本打算使用C语言的CSpidr库来实现&#xff0c;但是因为它的依赖liburi没有找到在哪里安装&#xff0c;所以放弃了这个想法&#xff0c;使用的是curl以及libxml2这两个库&#xff0c;能够…

GOOGLE|只有大模型才能理解你举的例子(In-context learning)是什么

一、概述 title&#xff1a;LARGER LANGUAGE MODELS DO IN-CONTEXT LEARNING DIFFERENTLY 论文地址&#xff1a;https://arxiv.org/abs/2303.03846 参考&#xff1a;https://www.xiaohongshu.com/user/profile/5f01057f0000000001003c91/640aa237000000001303d871 1.1 Moti…

springboot基于vue的地方美食分享网站

开发技术介绍 Java介绍 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#xff0c;便于结构的分离&…

Python文件上传 S3(AWS) 简单实现

1.AWS设置 建立aws账户&#xff0c;进入到S3界面 点击 "Create bucket" 一系列操作之后——这里给bucket命名为csfyp 2. Python部分 python需要先&#xff1a; pip install loguru pip install boto3 这两个包含一些连接python和s3 连接的api 然后直接上代码…

Redis学习---05

一、Redis集群搭建&#xff0c;Redis主从复制&#xff0c;读写分离 默认情况下每台redis服务器都是主节点。 (1) 主从复制&#xff1a;是指将一台redis服务器的数据&#xff0c;复制道其他redis服务。前者成为主节点&#xff0c;后者成为从节点。默认情况下每一台redis服务器…

puppeteer-不需重构,无痛加强vue单页面应用的SEO,提升百度收录排名

背景 最近产品觉得我们网站在百度收录上排名太靠后了&#xff0c;又不肯花钱&#xff0c;就让我们想办法提升网站的SEO。由于项目是用vue3写的&#xff0c;并且已经迭代多个版本了&#xff0c;用nuxt实在不适宜&#xff0c;当然俺的开发水平也不够&#xff0c;周期也会拉得很长…