文章目录

前言

本节配套案例代码:​​Gitee仓库​​​、​​Github仓库​

所有博客文件目录索引:​​博客目录索引(持续更新)​

学习视频:​​动力节点最新SpringCloud视频教程|最适合自学的springcloud+springcloudAlibaba​

PS:本章节中部分图片是直接引用学习课程课件,如有侵权,请联系删除。

一、认识SpringCloud Eureka

1.1、介绍Eureka

注册发现中心

Eureka 来源于古希腊词汇,意为“发现了”。在软件领域, Eureka 是 Netflix 在线影片 公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡、 熔断器、网关等) 一起,被 Spring Cloud 社区整合为 Spring Cloud Netflix 模块。

Eureka 是 ​​Netflix​​ 贡献给 Spring Cloud 的一个框架!Netflix 给 Spring Cloud 贡 献了很多框架,后面我们会学习到!

1.2、Spring Cloud Eureka 和 Zookeeper 的区别

1.2.1、什么是CAP原则

在分布式 微服务里面 CAP 定理

问:为什么 zookeeper 不适合做注册中心?

CAP 原则又称 CAP 定理,指的是在一个分布式系统中,

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分区容错性(Partition tolerance)(这个特性是不可避免的)

CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

01、SpringCloud之Eureka学习笔记_maven

1.2.2、分布式特征

01、SpringCloud之Eureka学习笔记_eureka_02

C : 数据的一致性 (A,B,C 里面的数据是一致的)

  • Zk 注重数据的一致性。
  • Eureka 不是很注重数据的一致性!

A: 服务的可用性(若 zk 集群里面的 master 挂了怎么办)Paxos(多数派)

  • 在 zk 里面,若主机挂了,则 zk 集群整体不对外提供服务了,需要选一个新的出来(120s 左右)才能继续对外提供服务!
  • Eureka 注重服务的可用性,当 Eureka 集群只有一台活着,它就能对外提供服务

P:分区的容错性(在集群里面的机器,因为网络原因,机房的原因,可能导致数据不会里面 同步),它在分布式必须需要实现的特性!

Zookeeper 注重数据的一致性,CP zk(注册中心,配置文件中心,协调中心)。

Eureka 注重服务的可用性 AP ,eureka (注册中心)。

1.3、Spring Cloud 其他注册中心

Spring Cloud 还有别的注册中心 Consul ,阿里巴巴提供 Nacos 都能作为注册中心,我们 的选择还是很多。

Consul

​https://spring.io/projects/spring-cloud-consulConsul​

Nacos(阿里巴巴)

​https://nacos.io/zh-cn/​

但是我们学习还是选择 Eureka ,因为它的成熟度很高。面试时候问的也是它,不是别人! eureka nacos

二、SpringCloud Eureka快速入门

01、SpringCloud之Eureka学习笔记_maven_03

2.1、创建一个Eureka-server服务端

创建并配置

创建一个SpringBoot项目,选择server即可:

01、SpringCloud之Eureka学习笔记_spring cloud_04

配置pom.xml依赖:springboot依赖2.3.12.RELEASE,springcloud依赖Hoxton.SR12

<?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 https://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>
<!-- 指定springboot的版本 -->
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.changlu</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!-- 设置SpringCloud的版本依赖 -->
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

配置appliction.yaml:

# 单机
server:
port: 8761 # eureka的默认端口 6379 8080 3306 8848
spring:
application:
name: eureka-server # 应用名称

在SpringBoot的启动器上添加一个开启Eureka的注解:

@SpringBootApplication
@EnableEurekaServer //开启注解
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}

}

测试

访问网址:http://localhost:8761/

2.2、Eureka的服务大屏

01、SpringCloud之Eureka学习笔记_eureka_05

2.3、创建两个client来进行注册

同样是创建一个SpringBoot项目,依赖如下:

01、SpringCloud之Eureka学习笔记_spring cloud_06

01、SpringCloud之Eureka学习笔记_学习_07

pom.xml:

<?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 https://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>
<!-- 指定springboot的版本 -->
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.changlu</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!-- 设置SpringCloud的版本依赖 -->
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

①编写对应的yaml配置:其中包含当前的服务名,端口号,以及对应客户端注册的目标地址

eureka-client-a的配置:

server:
port: 8082 # 客户端的端口没有要求
spring:
application:
name: eureka-client-a

# 注册的含义是什么? 就是将自己的一些信息(什么信息ip port...) 发送过去 (发到哪里)
eureka:
client: # 客户端的相关配置
service-url: # 指定注册的地址
defaultZone: http://localhost:8761/eureka
instance:
hostname: localhost # 应用的主机名称 最好写主机ip
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
prefer-ip-address: true # 显示ip
lease-renewal-interval-in-seconds: 10 # 示例续约的时间

eureka-client-b的配置:

②在启动器中添加注解:

@EnableEurekaClient

最后我们将三个服务进行启动:

01、SpringCloud之Eureka学习笔记_spring_08

访问地址:http://localhost:8761/

01、SpringCloud之Eureka学习笔记_学习_09

2.4、常用的配置文件设置

01、SpringCloud之Eureka学习笔记_eureka_10

2.4.1、server当中常用配置

01、SpringCloud之Eureka学习笔记_eureka_11

01、SpringCloud之Eureka学习笔记_学习_12

2.4.2、client端

01、SpringCloud之Eureka学习笔记_spring cloud_13

三、构建高可用的Eureka-Server集群

01、SpringCloud之Eureka学习笔记_eureka_14

3.1、配置三台Eureka-Server

01、SpringCloud之Eureka学习笔记_spring cloud_15

创建工程方式如2.1所示,三个配置如下:其中所有的服务名是一致的,端口不同,以及其中的service-url设置了另外两个

eureka-server:

# 集群
server:
port: 8761 # eureka的默认端口 6379 8080 3306 8848
spring:
application:
name: eureka-server # 应用名称 不要使用特殊字符
eureka:
server:
eviction-interval-timer-in-ms: 90000 #清除无效节点的评率(毫秒)
client:
service-url: # 你不写 默认 8761
defaultZone: http://localhost:8762/eureka,http://localhost:8763/eureka
fetch-registry: true #是否拉取服务列表
register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
instance: # 实例的配置
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 主机名称 : 应用名称 : 端口号
hostname: localhost # 主机名称 或者服务的ip
prefer-ip-address: true # 以ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 # 服务实例的续约的时间间隔

eureka-server-a:

# 集群
server:
port: 8762 # eureka的默认端口 6379 8080 3306 8848
spring:
application:
name: eureka-server # 应用名称 不要使用特殊字符
eureka:
server:
eviction-interval-timer-in-ms: 90000 #清除无效节点的评率(毫秒)
client:
service-url: # 你不写 默认 8761
defaultZone: http://localhost:8762/eureka,http://localhost:8763/eureka
fetch-registry: true #是否拉取服务列表
register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
instance: # 实例的配置
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 主机名称 : 应用名称 : 端口号
hostname: localhost # 主机名称 或者服务的ip
prefer-ip-address: true # 以ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 # 服务实例的续约的时间间隔

eureka-server-b:

# 集群
server:
port: 8763 # eureka的默认端口 6379 8080 3306 8848
spring:
application:
name: eureka-server # 应用名称 不要使用特殊字符
eureka:
server:
eviction-interval-timer-in-ms: 90000 #清除无效节点的评率(毫秒)
client:
service-url: # 你不写 默认 8761
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
fetch-registry: true #是否拉取服务列表
register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
instance: # 实例的配置
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 主机名称 : 应用名称 : 端口号
hostname: localhost # 主机名称 或者服务的ip
prefer-ip-address: true # 以ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 # 服务实例的续约的时间间隔

此时我们来进行启动三个server服务:

01、SpringCloud之Eureka学习笔记_eureka_16

任意访问一台:http://localhost:8763/

01、SpringCloud之Eureka学习笔记_spring cloud_17

发现并没有出现集群信息,只是同一个服务 server 启动了多台 没有数据交互 不是真正意义上的集群

原因是因为:http://localhost:8761/eureka/,http://localhost:8762/eureka/ 这样写,eureka 认为只有一个机器,就是 localhost

所以这里面不能写成一样。

3.2、解决3.1中未能构成集群情况

由于我们当前的主机名都是localhost,那么我们需要修改本地的hosts文件,来进行域名映射地址url即可。

修改对应路径下的hosts文件:​​C:\Windows\System32\drivers\etc​

127.0.0.1   changlu1
127.0.0.1 changlu2
127.0.0.1 changlu3

接着执行命令进行刷新:​​ipconfig /flushdns ​

然后我们来修改三个服务的配置文件

eureka-server:

defaultZone: http://changlu2:8762/eureka,http://changlu3:8763/eureka
hostname: changlu1 # 主机名称 或者服务的ip

eureka-server-a:

defaultZone: http://changlu1:8762/eureka,http://changlu3:8763/eureka
hostname: changlu2 # 主机名称 或者服务的ip

eureka-server-b:

defaultZone: http://changlu1:8762/eureka,http://changlu2:8763/eureka
hostname: changlu3 # 主机名称 或者服务的ip

接着再来启动服务。

此时我们来访问网址:http://localhost:8763/

01、SpringCloud之Eureka学习笔记_maven_18

  • DS Replicas:表示的是数据同步的地址。

思考:此时我们是通过创建工程的方式来创建多个Eureka Server,我们能否使用一个工程来实现Eureka集群呢?

答案:可以,见最终方案。

3.3、最终方案

3.3.1、配置方案

思路:我们创建一个Eureka-server工程,启动三个服务,在每次启动的时候重新指定端口,然后指定的集群包含所有的服务地址。

我们将eureka-server作为我们集群服务的工程,修改其配置文件:

01、SpringCloud之Eureka学习笔记_maven_19

#集群的终极方案
server:
port: 8761 # eureka的默认端口 6379 8080 3306 8848
spring:
application:
name: eureka-server # 应用名称 不要使用特殊字符
eureka:
client:
service-url: # 你不写 默认 8761
defaultZone: http://changlu1:8761/eureka,http://changlu2:8762/eureka,http://changlu3:8763/eureka
instance: # 实例的配置
# 修改1:不需要hostname了
instance-id: ${spring.application.name}:${server.port} # 主机名称 : 应用名称 : 端口号
# 修改2:无需指定主机名称也可以
# hostname: peer1 # 主机名称 或者服务的ip
prefer-ip-address: true # 以ip的形式显示具体的服务信息
lease-renewal-interval-in-seconds: 5 # 服务实例的续约的时间间隔

我们复制对应的EurekServer的启动类:

01、SpringCloud之Eureka学习笔记_学习_20

然后我们修改启动类配置:

01、SpringCloud之Eureka学习笔记_spring_21

--server.port=8762
--server.port=8763

01、SpringCloud之Eureka学习笔记_学习_22

然后我们来访问:http://localhost:8762/

01、SpringCloud之Eureka学习笔记_eureka_23

3.3.2、修改client配置

01、SpringCloud之Eureka学习笔记_spring_24

当Eureka配置集群之后,我们只要指定一个注册地址即可:

# 改造:注册访问集群eureka-server
server:
port: 8082 # 客户端的端口没有要求
spring:
application:
name: eureka-client-a
# 注册的含义是什么? 就是将自己的一些信息(什么信息ip port...) 发送过去 (发到哪里)
eureka:
client: # 客户端的相关配置
service-url: # 指定注册的地址
# 指定任意一个服务交互地址
defaultZone: http://changlu1:8761/eureka
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
hostname: localhost # 应用的主机名称 最好写主机ip
lease-renewal-interval-in-seconds: 10 # 示例续约的时间

01、SpringCloud之Eureka学习笔记_maven_25

OK,此时我们的client服务启动起来了,我们看下8762或者其他的集群有没有该服务的信息:

01、SpringCloud之Eureka学习笔记_spring cloud_26

3.3.3、宕机一台server

我们宕机掉一个服务:

01、SpringCloud之Eureka学习笔记_学习_27

可以看到当前会有些脏数据,也就是会出现不一致的情况,但是服务是依旧可用的:

01、SpringCloud之Eureka学习笔记_spring cloud_28

Eureka server 的集群里面,没有主机和从机的概念,节点都是对等的,只有集群里面有一 个集群存活,就能保证服务的可用性。 (主机 (写) 从 (读))

只要有一台存活,服务就能注册和调用。

3.3.4、认识分布式数据一致性协议

协议包含:Paxos raft

  • Zookeeper Paxos
  • Nacos raft

eureka 没有分布式数据一致性的机制 节点都是相同的

在有主从模式的集群中 一般都要遵循这样的协议 才可以稳定对外提供服务

四、Eureka概念的理解

4.1、服务的注册

当项目启动时(eureka 的客户端),就会向 eureka-server 发送自己的元数据(原始数据) (运行的 ip,端口 port,健康的状态监控

等,因为使用的是 http/ResuFul 请求风格), eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful 风

格,以 http 动词的请求方式,完成对 url 资源的操作)

4.2、服务的续约

项目启动成功了,除了向 eureka-server 注册自己成功,还会定时的向 eureka-server 汇 报自己,心跳,表示自己还活着。(修改一个时间)

4.3、服务的下线

当项目关闭时,会给 eureka-server 报告,说明自己要下机了。

4.4、服务的剔除(被动下线,主动剔除)

当项目超过了指定时间没有向 eureka-server 汇报自己,那么 eureka-server 就会认为此节点死掉了,会把它剔除掉,也不会放流量和请

求到此节点了。

五、Eureka原理分析

5.1、Eureka 运作原理的特点

Eureka-server 对外提供的是 restful 风格的服务 :以 http 动词的形式对 url 资源进行操作 get post put delete。

http 服务 + 特定的请求方式 + 特定的 url 地址:只要利用这些 restful 我们就能对项目实现注册和发现。

只不过,eureka 已经帮我们使用 java 语言写了 client,让我们的项目只要依赖 client 就能实现注册和发现!

只要你会发起 Http 请求,那你就有可能自己实现服务的注册和发现。不管你是什么语言!

5.2、服务注册的源码分析【重点】

01、SpringCloud之Eureka学习笔记_spring cloud_29

5.2.1、Eureka-client发起注册请求

5.2.1.1、源码位置

01、SpringCloud之Eureka学习笔记_maven_30

5.2.1.2、如何发送信息来进行注册?

01、SpringCloud之Eureka学习笔记_spring cloud_31

真正去执行注册的是在一个抽象类中:

01、SpringCloud之Eureka学习笔记_spring_32

总结: 当 eureka 启动的时候,会向我们指定的 serviceUrl 发送请求,把自己节点的数据以 post 请求的方式,数据以 json 形式发送过去。 当返回的状态码为 204 的时候,表示注册成功。

5.2.2、Eureka-server实现注册+保存

01、SpringCloud之Eureka学习笔记_学习_33

1、接受客户端的请求

com.netflix.eureka.resources.ApplicationResource

01、SpringCloud之Eureka学习笔记_spring_34

2、实际服务端的源码位置

01、SpringCloud之Eureka学习笔记_eureka_35

3、处理请求(注册自己,向其他节点注册)

上面第二段super.register()

public void register(InstanceInfo info, boolean isReplication) {
int leaseDuration = 90;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//真正注册自己
super.register(info, leaseDuration, isReplication);
this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}

4、真正的注册自己

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
this.read.lock();

try {
//通过服务名称得到注册的实例
Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
EurekaMonitors.REGISTER.increment(isReplication);
//因为之前没有实例,肯定为 null
if (gMap == null) {
//新建一个集合来存放实例
ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
//gMap 就是该服务的实例
Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
if (existingLease != null && existingLease.getHolder() != null) {
Long existingLastDirtyTimestamp = ((InstanceInfo)existingLease.getHolder()).getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = (InstanceInfo)existingLease.getHolder();
}
} else {
synchronized(this.lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
++this.expectedNumberOfClientsSendingRenews;
this.updateRenewsPerMinThreshold();
}
}

logger.debug("No previous lease information found; it is new registration");
}
//新建一个服务的实例节点
Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//放到注册 map 的列表里
((Map)gMap).put(registrant.getId(), lease);
this.recentRegisteredQueue.add(new Pair(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")"));
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!this.overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
this.overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}

InstanceStatus overriddenStatusFromMap = (InstanceStatus)this.overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}

InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
//设置心跳时间等参数
registrant.setActionType(ActionType.ADDED);
this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication});
} finally {
this.read.unlock();
}

}

5.2.3、服务注册总结

重要的类:

  • DiscoveryClient 里面的 register()方法完后注册的总体构造
  • AbstractJerseyEurekaHttpClient 里面的 register()方法具体发送注册请求(post)
  • InstanceRegistry 里面 register()方法接受客户端的注册请求
  • PeerAwareInstanceRegistryImpl 里面调用父类的 register()方法实现注册
  • AbstractInstanceRegistry 里面的 register()方法完成具体的注册保留数据到 map 集合保存服务实例数据的集合:

保存服务实例数据的集合:

  • 第一个key是应用名称(全大写)spring.application.name
  • Value中的key是应用的实例ideureka.instance.instance-id
  • Value中的value是 具体的服务节点信息
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

5.3、服务剔除的源码分析(被动下线)

AbstractInstanceRegistry 的evict()方法中筛选剔除的节点。

在 internalCancel 方法里面真正实现剔除:其实就是对map执行remove操作。

在服务剔除中涉及到哪些重要的点

怎么删除一个集合里面过期的数据?

Redis 怎么清除过期的 key LRU(热点 key)

1 定时(k-thread)

2 惰性 (在再次访问该 key 时有作用)

3 定期 (使用一个线程来完成清除任务)

操作:定期(实时性差) + 惰性

何时进行剔除操作?

通过一个定时任务:

01、SpringCloud之Eureka学习笔记_maven_36

01、SpringCloud之Eureka学习笔记_spring cloud_37

5.4、服务下线的源码分析

eureka-client发起下线请求

①通过unregister()方法来进行发起下线请求

01、SpringCloud之Eureka学习笔记_spring_38

②真正的发起请求下线:AbstractJerseyEurekaHttpClient

01、SpringCloud之Eureka学习笔记_spring cloud_39

eureka-server处理下线请求

①接受下线请求

01、SpringCloud之Eureka学习笔记_spring_40

②真正的下线服务

01、SpringCloud之Eureka学习笔记_spring cloud_41

5.5、服务发现的源码分析

5.5.1、服务发现流程分析

从 discoveryClient.getInstances(serviceId);方法进去,找到eureka 的实现

01、SpringCloud之Eureka学习笔记_maven_42

从 getInstancesByVipAddress 方法进去看到真正的服务发现

01、SpringCloud之Eureka学习笔记_maven_43

在 getInstancesByVirtualHostName 方法里面做真正的服务发现

01、SpringCloud之Eureka学习笔记_eureka_44

01、SpringCloud之Eureka学习笔记_spring cloud_45

5.5.2、在 eureka-client 客户端也有 map 集合存放服务列表?

01、SpringCloud之Eureka学习笔记_maven_46

我们发现,当我们还没有做服务发现之前,集合里面已经有值了,说明项目启动的时候就去server 端拉取服务列表并且缓存了起来

到底何时从 server 拉取服务放进去的呢?

在 eureka 的 DiscoverClient 类的一个构造方法里面,有一个任务调度线程池:

01、SpringCloud之Eureka学习笔记_学习_47

01、SpringCloud之Eureka学习笔记_学习_48

查看 initScheduledTasks()这个方法

01、SpringCloud之Eureka学习笔记_spring cloud_49

在 CacheRefreshThread()中

01、SpringCloud之Eureka学习笔记_eureka_50

fetchRegistry()方法中判断决定是全量拉取还是增量拉取

01、SpringCloud之Eureka学习笔记_spring cloud_51

getAndStoreFullRegistry()全量拉取:

01、SpringCloud之Eureka学习笔记_spring_52

getAndUpdateDelta()增量拉取:

01、SpringCloud之Eureka学习笔记_spring_53

5.5.3、服务发现总结

重要的类:

  • DiscoveryClient 类里面的构造方法执行线程初始化调用
  • CacheRefreshThread 类里面的 run 方法执行服务列表的拉取(方便后期做服务发现)
  • fetchRegistry()方法去判断全量拉取还是增量拉取

全量拉取发生在:当服务列表为 null 的情况 当项目刚启动就全量拉取

增量拉取发生:当列表不为 null ,只拉取 eureka-server 的修改的数据(注册新的服务, 上线服务)

eureka 客户端会把服务列表缓存到本地 为了提高性能 ,但是有脏读问题,当你启动一个新的应用的时候 不会被老的应用快速发现

六、服务发现

6.1、什么是服务发现?

根据服务名称发现服务的实例过程

客户端会在本地缓存服务端的列表

拉取列表是有间隔周期的 (导致服务上线 客户端不能第一时间感知到 (可以容忍))

其实每次做服务发现 都是从本地的列表来进行的

01、SpringCloud之Eureka学习笔记_eureka_54

6.2、简单实现服务发现功能

我们在eureka-client-a中编写对应的服务发现控制器:

01、SpringCloud之Eureka学习笔记_学习_55

package com.changlu.eurekaclienta.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* @Description: 我的服务注册发现
* @Author: changlu
* @Date: 10:04 PM
*/
@RestController
public class MyDiscoveryController {

@Autowired
private DiscoveryClient discoveryClient;//这个接口是springcloud提供的,然后eureka提供了对应的实现类

@GetMapping("/find")
public String find(String serviceId) {
//调用服务发现
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
instances.forEach(System.out::println);
return instances.toString();
}

}

启动一个server服务以及两个客户端:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dgfv64lB-1657597370320)(C:\Users\93997\AppData\Roaming\Typora\typora-user-images\image-20220703221052050.png)]

访问下:http://localhost:8761/,查看下当前的服务

01、SpringCloud之Eureka学习笔记_maven_56

接着我们来进行测试服务发现:http://localhost:8082/find?serviceId=eureka-client-a

01、SpringCloud之Eureka学习笔记_学习_57


我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接