Eureka
Spring Cloud 服务注册中心
1 注册中心的主要作用
服务注册中心(也称注册中心)是微服务架构非常重要的一个组件,在微服务架构里主要起到了协调者
的一个作用。注册中心-般包含如下几个功能:
1.1.服务发现:
·服务注册/反注册:保存服务提供者和服务调用者的信息
·服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能
·服务路由(可选) :具有筛选整合服务提供者的能力。
1.2.服务配置:
·配置订阅:服务提供者和服务调用者订阅微服务相关的配置
·配置下发:主动将配置推送给服务提供者和服务调用者
1.3.服务健康检测
·P检测服务提供者的健康情况
2 常见的注册中心
2.1 Zokeeper
zookeeper它是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应
用中经常遇到的一些数据管理问题,如:统-命名服务、状态同步服务、集群管理、分布式应用配置项
的管理等。简单来说zookeeper=文件系统+监听通知机制。
2.2 Eureka
Eureka是在Java语言上,基于Restful Api开发的服务注册与发现组件, Springcloud Netflix中的重要组
件
2.3 Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,
采用Raft算法保证服务的一致性,且支持健康检查。
2.4 Nacos
Nacos是一个更易 于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说Nacos就是
注册中心+配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册
与发现,服务配置,服务管理等问题。Nacos 还是Spring Cloud Alibaba组件之一,负责 服务注册与
发现。
最后我们通过一张表格大致了解Eureka、Consul. Zookeeper的异同点。选择什么类型的服务注册与
发现组件可以根据自身项目要求决定。
3.EUREKA概述
3.1 eureka基础知识
Eureka是Netflix开发的服务发现框架, SpringCloud将它集成在自己的子项目spring-cloud-netflix中,
实现SpringCloud的服务发现功能。
上图简要描述了Eureka的基本架构,由3个角色组成:
3.1.1、Eureka Server
·提供服务注册和发现
3.1.2、Service Provider
·服务提供方
·将自身服务注册到Eureka ,从而使服务消费方能够找到
3.1.3、Service Consumer
·服务消费方
·从Eureka获取注册服务列表,从而能够消费服务
3.2 Eureka的交互流程与原理
4 使用eureka的步骤
4.1.搭建eureka server
4.1.1 创建工程
4.1.1 导入依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>org.example</groupId> <artifactId>euraka-server</artifactId> <version>1.0-SNAPSHOT</version> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
4.1.1 修改配置文件
server: port: 9000 eureka: instance: hostname: localhost client: register-with-eureka: false #是否将自己注册到eureka fetch-registry: false #是否从eureka获取注册信息 service-url: #配置暴露给eureka-client请求的地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
4.1.1 配置启动类
@SpringBootApplication //开启EurekaServer @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); } }
4.2.将服务提供者注册到eurekaServer上
4.2.1 引入euraka-client
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
4.2.2 修改配置文件,添加eureka-server的信息
#配置eureka-server信息 eureka: client: service-url: defaulrZone: http://localhost:9000/euraka/ instance: prefer-ip-address: true #使用ip地址注册
4.2.3 修改启动类,添加服务发现的支持(可选)
@SpringBootApplication @EntityScan(value = "com.jpa.product.entity") //开启注册 //@EnableEurekaClient @EnableDiscoveryClient public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class,args); } }
4.3.服务消费者通过注册中心获取服务列表,并调用
eureka的元数据:服务的主机名,ip等信息,可以通过eureka-server来获取,用于服务之间的相互调用
4.3.1 引入euraka-client
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
4.3.2 修改配置文件,添加eureka-server的信息
#配置eureka-server信息
eureka:
client:
service-url:
defaulrZone: http://localhost:9000/euraka/
instance:
prefer-ip-address: true #使用ip地址注册
4.3.3 修改启动类,添加服务发现的支持(可选)
@SpringBootApplication
@EntityScan(value = "com.jpa.product.entity")
//开启注册
//@EnableEurekaClient
@EnableDiscoveryClient
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
4.4 Eureka深究
4.4.1 Eureka基本规则
- 服务启动时会生成服务的基本信息对象InstanceInfo,然后在启动时会register到服务治理中心。
- 注册完成后会从服务治理中心拉取所有的服务信息,缓存在本地。
- 之后服务会被30s(可配置)发送一个心跳信息,续约服务。
- 如果服务治理中心在90s内没有收到一个服务的续约,就会认为服务已经挂了,会把服务注册信息删掉。
- 服务停止前,服务会主动发送一个停止请求,服务治理中心会删除这个服务的信息。
- 如果Eureka Server收到的心跳包不足正常值的85%(可配置)就会进入自我保护模式,在这种模式下,Eureka Server不会删除任何服务信息。
有两个比较重要的配置
服务过期时间配置:eureka.instance.lease-expiration-duration-in-seconds
服务刷新时间配置:eureka.instance.lease-renewal-interval-in-seconds
4.4.2 Eureka服务注册中心的缓存
Eureka注册中心有一个Map来保存所有的服务及映射的机器信息
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
- 服务注册时,会把服务的信息写到这个registry中
- 服务从治理中心拉取服务列表信息时,不会从这个registry中拉取,而是从一个ResponseCache中拉取,这样读写分离的原因应该是为了支持高并发。
而ResponseCache又分为了两个部分,一个是ReadWriteMap,一个是ReadOnlyMap。
- ReadWriteMap的数据是从registry中来的,可以认为是registry的缓存,当服务注册时,除了把信息写到registry中外,还会让ReadWriteMap主动过期,使得会去从registry重新拉取数据。
- ReadOnlyMap的数据是从ReadWriteMap来的,可以认为是ReadWriteMap的缓存(所以它是registry缓存的缓存,双层缓存了),当服务需要获取服务列表是,会直接取这个ReadOnlyMap的数据,当这个数据不存在时,才会从ReadWriteMap中更新。
- ReadWriteMap与registry的数据是实时一致的(因为有注册后让ReadWriteMap失效的机制),但是ReadWriteMap与ReadOnlyMap不是实时一致的。
- 有定时任务会定时从ReadWriteMap同步到ReadOnlyMap,这个时间配置是:eureka.server.responseCacheUpdateInvervalMs
- EurekaServer内部有定时任务,每隔检查过期实例时间,扫描Registry里面过期的实例并删除,并且使对应的ReadWriteMap缓存失效,这个时间是eureka.server.eviction-interval-timer-in-ms
4.4.3 服务注册的细节
4.4.3.1 客户端做了什么事?
服务在启动完成后,会启动一个线程,去执行注册信息,具体代码在InstanceInfoReplicator中,这个类实现了Runnable:
public void run() { try { discoveryClient.refreshInstanceInfo(); Long dirtyTimestamp = instanceInfo.isDirtyWithTime(); if (dirtyTimestamp != null) { discoveryClient.register(); instanceInfo.unsetIsDirty(dirtyTimestamp); } } catch (Throwable t) { logger.warn("There was a problem with the instance info replicator", t); } finally { Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } }
- 在run方法中,会先去检测服务信息是否发生了变更,如果发生了,会调用 discoveryClient.register();方法重新注册,第一次的时候也会认为是发生了变更,从而发起第一次注册。
- 在finally块中,schedule了this,说明会在指定时间后再次执行这个方法。
4.4.3.2 服务端做了什么事?
- 首先会处理上面第二节所说的registry以及它的缓存对象。
- 其次会把这次的注册信息添加到一个“最近变更队列中”
private ConcurrentLinkedQueue<RecentlyChangedItem> recentlyChangedQueue = new ConcurrentLinkedQueue<RecentlyChangedItem>();
之所以要维护一个这样的队列,是为了方便服务增量更新服务列表的信息。
- 然后会把注册的信息发送到其他的Eureka节点,发送的方式就是调用其他节点的register方法。
4.4.4 服务续约的细节
在服务启动时,会创建一个后台心跳任务,定时去续约服务信息:
scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS);
HeartbeatThread就是心跳线程,这里把它包装成了一个TimedSupervisorTask,
注意:scheduler.schedule方法只会在延时一定时间后执行一次提交的任务,所以这里会延时执行,同时,虽然schedule只会执行一次,但是TimedSupervisorTask里封装了循环调用的信息,所以其实是定时调用了。而HeartbeatThread非常简单:
private class HeartbeatThread implements Runnable { public void run() { if (renew()) { lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis(); } } }
- if语句中的renew()方法就是服务续约的方法,里面的逻辑就是向服务治理中心发送了一个http请求。
- 如果http请求返回的状态码是404,则会认为这个服务没有注册或者注册了但是已经失效,因此会直接调用register()方法进行注册。
4.4.5 服务取消的细节
服务停止前会向服务治理中心发一个服务取消的请求,用于注销服务。收到服务注销的请求之后,服务治理中心会做以下操作:
- 从registry中删除对应的服务信息
- 使ReadWriteMap缓存失效
- 将服务取消的信息加入到最近变更队列中
5 eureka服务的高可用集群
在上面,实现了单节点的Eureka Server的服务注册与服务发现功能。Eureka Client会定时连接Eureka Server ,获取注册表中的信息并缓存到本地。微服务在消费远程API时总是使用本地缓存中的数据。因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。但如果EurekaServer宕机时,某些微服务也出现了不可用的情况, Eureka Server中的缓存若不被刷新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用。因此,在生成环境中,通常会部署-个高可用的Eureka Server集群。Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署, Eureka Server实例会彼此增量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行
为。
5.1 准备3个eureka-server的yml文件
5.1.1 主配置文件
spring:
application:
name: eureka
profiles:
active: server1
5.1.2 eureka1的配置文件
server: port: 8001 eureka: instance: hostname: server1 client: # 表示是否注册自身到eureka服务器 # register-with-eureka: false # 是否从eureka上获取注册信息 # fetch-registry: false service-url: defaultZone: http://eureka1:8002/eureka/
5.1.3 eureka2的配置文件
server: port: 8002 eureka: instance: hostname: server2 client: # 表示是否注册自身到eureka服务器 # register-with-eureka: false # 是否从eureka上获取注册信息 # fetch-registry: false service-url: defaultZone: http://eureka2:8001/eureka/
5.1.4 修改主机hosts文件
127.0.0.1 eureka1
127.0.0.1 eureka2
5.1.5 分别运行两次项目(指定配置文件)
5.1.6 最终效果
6 eureka常见的问题
6.1 自我保护机制
现象:页面出现如下错误“EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.”,如下图
6.2、自我保护模式介绍
参考官网:https://github.com/Netflix/eureka/wiki/Understanding-Eureka-Peer-to-Peer-Communication
保护模式,是Eureka 提供的一个特性,在默认的情况下,这个属性是打开的,而且也建议线上都使用这个特性。如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,此时会触发Eureka Server进入保护模式,进入自我保护模式后,将会保护服务注册表中的信息,不再删除服务注册表中的数据。
6.3、关于上面的问题
在上面测试过程中,只有一个Eureka Client,当这个Eureka Client服务注册后,又被关停了,就导致Eureka Client进入了保护模式。所以上面警告信息是Eureka Server进入自我保护模式之后的显示的。
6.4、相关属性设置
对于自我保护模式属性,建议都是使用默认的配置,不需要要设置这些属性。
(1)Eureka Server端
默认情况下自我保护机制是打开的,线上建议都是打开的,即不需要设置。如果在测试环境需要设置为关闭,可以通过如下配置:
# 设为false,关闭自我保护。默认是打开的。 eureka.server.enable-self-preservation=false
设置清零失效服务的间隔时间,但是不建议更改
# 清理间隔(单位毫秒,默认是60*1000)
eureka.server.eviction-interval-timer-in-ms=4000
这个“eureka.server.eviction-interval-timer-in-ms”时间,是Eureka Server执行清理无效服务的时间间隔,执行新清理任务时,如果下面判断生效,则清除服务。
当前时间 - 上次心跳时间 > lease-expiration-duration-in-seconds
其中,lease-expiration-duration-in-seconds 属性在客户端进行配置
这里一个规范就是:服务端的“eureka.server.eviction-interval-timer-in-ms” 值 要比 客户端配置 “lease-expiration-duration-in-seconds ”的时间短。
(2) Eureka Client端
开启健康检查,默认是开启的,如下
eureka.client.healthcheck.enabled=true
心跳相关的设置
# 单位是秒,默认30秒。此客户端发送心跳的频率 eureka.instance.lease-renewal-interval-in-seconds=30 # 单位是秒,默认90秒,表示eureka server在收到此client上次心跳之后,间隔多久没有收到,就摘除此服务。 eureka.instance.lease-expiration-duration-in-seconds=10
控制台显示ip地址配置
#向注册中心注册服务id
instance-id: ${spring.cloud.client.ip-address}:${server.port}
7 源码分析
7.1 回忆Eureka的一些概念:
在Eureka的服务治理中,会涉及到下面一些概念:
服务注册:Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。
服务续约:在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒发送一次心跳来进行服务续约。
服务同步:Eureka Server之间会互相进行注册,构建Eureka Server集群,不同Eureka Server之间会进行服务同步,用来保证服务信息的一致性。
获取服务:服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
服务调用:服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone,在进行服务调用时,优先访问处于同一个Zone中的服务提供者。
服务下线:当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
服务剔除:有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒)的服务剔除。
自我保护:既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。
从这些概念中,就可以知道大体的流程,Eureka Client向Eureka Server注册,并且维护心跳来进行续约,如果长时间不续约,就会被剔除。Eureka Server之间进行数据同步来形成集群,Eureka Client从Eureka Server获取服务列表,用来进行服务调用,Eureka Client服务重启前调用Eureka Server的接口进行下线操作。
7.2 spring boot 的自动装载
( 1 ) InfportSelector
ImportSelector接口是Spring导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)中起到了决定性的作用。当在@Configuration标注的Class 上使用@lmport引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。
pub1ic interface Importselector { String[] select Imports (Annotati onMetadata var1);
DeferredlmportSelector接口继承ImportSelector,他和ImportSelector的区别在于装载bean的时机
上,DeferredlmportSelector需要等所有的@Configuration都执行完毕后才会进行装载
public interface DeferredImportselector extends Importselector { //...省略
7.3 eureka源码
7.3.1 分析eureka-server
1,查看需要加载的配置类
2,查看需要加载的配置类
3,查看EurekaServerAutoConfiguration类
4,查看EurekaController类
以上源码完成了eureka页面的控制
在core包下提供了eureka-client调用的方法
7.3.2 分析eureka-client源码
最终由DiscoveryClient类中的register方法调用register方法请求注册接口进行注册
详细源码分析请查看:https://blog.csdn.net/chayangdz/article/details/82012937
入门项目地址:https://gitee.com/aliuputin/spring_cloud_demo
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了