2--SpringCloud 和 Eureka 周阳老师
2021:2--SpringCloud 和 Eureka
https://www.cnblogs.com/coderD/p/14350076.html SpringCloud
https://www.cnblogs.com/coderD/p/14350073.html SpringCloud 和 Eureka
https://www.cnblogs.com/coderD/p/14350082.html SpringCloud 和 Zookeeper
https://www.cnblogs.com/coderD/p/14350086.html SpringCloud-Ribbon/OpenFeign
https://www.cnblogs.com/coderD/p/14350091.html SpringCloud:Hystrix 断路器
https://www.cnblogs.com/coderD/p/14350097.html SpringCloud:服务网关 gateway
https://www.cnblogs.com/coderD/p/14350099.html SpringCloud:Config/Bus
https://www.cnblogs.com/coderD/p/14350103.html SpringCloud:Stream/Sleuth
https://www.cnblogs.com/coderD/p/14350110.html SpringCloud Alibaba:Nacos
https://www.cnblogs.com/coderD/p/14350114.html SpringCloud Alibaba:Sentinel
https://www.cnblogs.com/coderD/p/14350119.html SpringCloud Alibaba:Seata
代码:https://gitee.com/xue--dong/spring-cloud
阳哥脑图:https://gitee.com/xue--dong/spring-cloud
主要内容
Eureka服务注册与发现
单机Eureka构建
集群Eureka构建
复制代码
1 Eureka 基础知识
前面我们没有服务注册中心,也可以服务间调用,为什么还要服务注册?
当服务很多时,单靠代码手动管理是很麻烦的,需要一个公共组件,统一管理多服务之间的相互调用。
Eureka用于服务注册,目前官网已经停止更新。
复制代码
1.1 什么是服务治理
SpringCloud 封装了 Netflix 公司开发的Eureka模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理,
管理服务与服务之间依赖关系,可以实现服务调用,负载均衡,容错等,实现服务发现与注册。
n个生产者和n个消费者之间相互调用。
复制代码
1.2 什么是服务注册与发现
如果你的服务注册中心只有一台机器,那么只要它一宕机,就会产生单点故障。
所以一般服务注册中心能配多个就配多个,主要就是为了避免单点故障。
复制代码
1.3 Eureka 的两个组件
1. Eureka Server:提供服务注册的服务。
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册
表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
2. Eureka Client:微服务通过其和Eureka Server保持通信。
它是一个Java客户端,用于简化微服务和EurekaServer的交互。客户端同时也具备一个内置的,
使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向EurekaServer发送心跳(默认周期30s)。如果EurekaServer在多个心跳
周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90s)。
复制代码
2 单机 Eureka 构建步骤
2.1 建 module:cloud-eureka-server7001
2.2 改 pom
1. 1.x和2.x的Eureka依赖对比说明
1.x:
复制代码
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
复制代码
2.x
复制代码
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
复制代码
2. 添加依赖
复制代码
<dependencies>
<!--eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入我们自定义的公共api jar包-->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--图形监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
复制代码
2.3. 写 yml
Eureka服务器 请求地址:
http://${eureka.instance.hostname}:${server.port}/eureka/
http:/localhost:7001/eureka/
复制代码
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
# false表示不向注册中心注册自己(中介不用注册了)
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
复制代码
2.4 主启动类
注意开启注解:@EnableEurekaServer
指明该模块作为Eureka注册中心的服务器。
复制代码
@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
复制代码
2.5 service 类
Eureka Server不需要写service类
复制代码
2.6 测试
启动,发送http://localhost:7001/ 请求
复制代码
测试成功
只是目前还没有任何实例(微服务)注册尽进来。
复制代码
3. 微服务注册
3.1 生产者微服务 8001 注册 EurekaServer
1. 改POM
添加一个Eureka-Client依赖:
复制代码
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
复制代码
2. 改yml:添加如下配置
复制代码
eureka:
client:
# 表示将自己注册进EurekaServer默认为true
register-with-eureka: true
# 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
复制代码
3. 修改主启动类
添加注解:@EnableEurekaClient。
指明这是一个Eureka客户端,要注册进EurekaServer中。
复制代码
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
复制代码
4. 测试
启动Payment8001模块:
复制代码
发现我们的8001服务注册进来了。
红色字体是Eureka的自我保护机制,后面再详细说。
注意:注册进来的服务名,就是我们在该服务yml文件中配置的微服务名
复制代码
3.2 消费者微服务 80 注册进 EurekaServer
1. 改POM
添加Eureka Client 依赖
复制代码
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
复制代码
2. 改yml
复制代码
# 微服务名称
spring:
application:
name: cloud-order-service
eureka:
client:
# 表示将自己注册进EurekaServer默认为true
register-with-eureka: true
# 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
复制代码
3. 修改主启动类
添加:@EnableEurekaClient
4. 测试:
启动消费者80微服务。
刷新http://localhost:7001/页面:消费者80微服务也注册进来了。
复制代码
5. 测试微服务调用
复制代码
6. 不想将那个服务注册,就在yml中配置
复制代码
eureka:
client:
register-with-eureka: false
复制代码
重启服务再刷新EurekaServer主页
复制代码
小结:单机Eureka构建步骤到此就创建完成了。
复制代码
4 集群 Eureka 构建步骤
单机版Eureka构建到这就结束了,但是企业中不可能有谁敢说它的服务注册中心是单机版。
因为没有集群的高可用,就会带来一个严重的问题,单点故障。
1. Eureka集群原理说明
2. EurekaServer集群环境构建步骤
3. 将支付服务8001微服务发布到上面2台Eureka集群配置中
4. 将订单服务80微服务发布到上面2台Eureka集群配置中
5. 测试01
6. 支付服务提供者8001集群环境构建。
7. 负载均衡
8. 测试02
复制代码
4.1 Eureka 集群原理说明
Eureka流程原理:
复制代码
Eureka集群原理:互相注册,相互守望
复制代码
4.2 EurekaServer 集群环境构建步骤
4.2.1 新建一个 cloud-eureka-server7002 模块
1. 新建一个cloud-eureka-server7002模块
复制代码
4.2.4 pom 依赖
2. pom依赖同cloud-eureka-server7001
复制代码
4.2.4 修改两个 EurekaServer 的 yml 配置
4. yml
现在有多台Eureka服务器,所以没台Eureka服务器都要有自己的主机名。
同时Eureka7001和Eureka7002要互相注册。
我们先修改映射配置文件:第3步
1. Eureka7001的yml
复制代码
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己(中介不用注册了)
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 以7001作为服务提供者,向该指定的服务注册中心的位置注册7001服务
defaultZone: http://eureka7002.com:7002/eureka/
复制代码
2. Eureka7002的yml
复制代码
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己(中介不用注册了)
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 以7002作为服务提供者,向该指定的服务注册中心的位置注册7002服务
defaultZone: http://eureka7001.com:7001/eureka/
复制代码
4.2.3 修改域名映射配置文件
3. 修改映射配置文件
修改:C:\Windows\System32\drivers\etc 路径下的host文件
受限于我们只有一台物理机器,我们用不同的端口号来映射同一个地址。
复制代码
这样修改是是什么意思:
由于我只有一台电脑,而且将这台电脑也作为服务器。每次我们发送localhost,就会被域名解析器
解析成127.0.0.1,也就是我们本机的ip回送地址,访问我们本机。
现在我们将eureka7001.com和eureka7002.com作为域名映射到127.0.0.1,那么我们就可以
模拟请求不同的服务器名。实际上都会解析到我们本机。
注意这个映射修改了不意味着localhost 127.0.0.1之间的映射关系没有,还有。
即localhost/eureka7001.com/eureka7002.com 都会被域名解析器解析成127.0.0.1
例子1:
之前的单机的EurekaServer的yml配置:
server.port=7001
eureka.instance.hostname=localhost
eureka.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
那么我通过发送http://localhost:7001/eureka能访问到服务器在本地主机的EurekaServer。
例子2:以两个集群为例
现在集群的EurekaServer的yml配置
server.port=7001
eureka.instance.hostname=eureka7001.com
eureka.service-url.defaultZone=http://eureka7002.com:7002/eureka/
server.port=7002
eureka.instance.hostname=eureka7002.com
eureka.service-url.defaultZone=http://eureka7001.com:7001/eureka/
那么这样配置后,我们就可以通过:
http://eureka7001.com:7001和http://eureka7002.com:7002来
分别访问这两个EurekaServer。
同时这两个EurekaServer实现了互相注册。
复制代码
4.2.5 主配置类
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7002 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7002.class, args);
}
}
复制代码
4.2.6 测试
开启eureka7002 和 eureka7001 这两个服务
测试: http://localhost:7001/ 和 http://localhost:7002/
都成功连接。
复制代码
测试: http://eureka7002.com:7002/ 和 http://eureka7001.com:7001/
都成功连接。
复制代码
并且实现了两个Euraka服务器的相互注册:
复制代码
4.2.7 我又测试一个 eureka7003
server:
port: 7003
eureka:
instance:
hostname: eureka7003.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己(中介不用注册了)
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 以7001作为服务提供者,向该指定的服务注册中心的位置注册7001服务
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/
复制代码
将其注册到eureka7002和eureka7001中。
复制代码
5 注册提供者微服务和消费者微服务
5.1 将提供者服务 8001 微服务发布到上面的 3 台 Eureka 集群配置中
1. 修改yml
复制代码
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
2. 其他不改
复制代码
5.2 将消费者服务 80 微服务发布到上面的 3 台 Eureka 集群配置中
1. 修改yml
复制代码
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
2. 其他不改
复制代码
5.3 测试
1. 启动顺序
先启动Eureka 7001/7002/7003
在启动payment8001,order80
2. 测试:http://eureka7003.com:7003/ 成功
复制代码
3. 测试:http://eureka7001.com:7001/和http://eureka7002.com:7002/
略...
4. 测试order80微服务
复制代码
6 生产者微服务集群构建
一个提供者微服务不可能应付所有的消费者微服务。
复制代码
6.1 新建 cloud-provider-payment8002 微服务
1. POM和cloud-provider-payment8001一致
此处有差别:
复制代码
2. 写yml:
和payment8001一致,只改一个端口。
复制代码
# 微服务端口号
server:
port: 8002
复制代码
3. 主启动类
复制代码
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8002 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8002.class, args);
}
}
复制代码
4. 其他业务类和payment8001一致一致。
其他业务类controller/service/dao 直接复制。
注意:报错的话,重新alt+enter导下包。
mapper映射文件直接复制
entities通过cloud-api-commons引入。
复制代码
6.2 修改 controller
1. 注意:两个生产者微服务在注册中心中的注册名是一样的
这两个微服务对外暴露的都是同一个名字。
复制代码
2. 消费者微服务调用的是cloud-payment-service这个服务,但是在这个名字下面可能有多台机器。
那么怎么确定消费者微服务调用哪台机器上的cloud-payment-service服务?
看端口号!
3. 修改两个生产者微服务的controller方法
两个生产者微服务的controller都改成这样。
复制代码
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}") //Value绑定单一的属性值
private String serverPort;
//传给前端JSON
@PostMapping(value = "/payment/create") //写操作POST
public CommonResult create(@RequestBody Payment payment){
//由于在mapper.xml配置了useGeneratedKeys="true" keyProperty="id",会将自增的id封装到实体类中
int result = paymentService.create(payment);
log.info("*****插入结果:" + result);
if(result > 0){
//restful风格:对象以json串返回到页面
return new CommonResult(200, "插入数据库成功,serverPort:"+serverPort, result);
}else {
return new CommonResult(444,"插入数据库失败", null);
}
}
//传给前端JSON
@GetMapping(value = "/payment/get/{id}") //写操作POST
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果:" + payment);
if(payment != null){
return new CommonResult(200, "查询数据库成功,serverPort:"+serverPort, payment);
}else {
return new CommonResult(444,"查询ID:"+id+"没有对应记录", null);
}
}
}
复制代码
4. 启动测试:EurekaServer
两个生产者微服务都注册进来了。
复制代码
5. 测试:生产者微服务
复制代码
6. 测试:消费者微服务
复制代码
发现测试若干次不同请求,但是走的都是8001端口的生产者微服务。
7. 原因是我们在消费者微服务中将地址写死了
复制代码
所以在访问消费者微服务后,消费者微服务再去调用生产者中的方法。
调用发送的请求一直都是http://localhost:8001/...
所以一直调用了8001端口的微服务。
那么现在我们不指明具体的微服务端口,指定一个这两个生产者微服务共有的指向:
即微服务名:CLOUD-PAYMENT-SERVICE
复制代码
那么消费者再去调用生产者中的方法时,就没有指定具体的端口(具体的微服务)。那么哪一个微服务
被调用有其他方式来决定。
注意这个说法:
这个程序的使用者会知道这些微服务的端口或者地址吗?
消费者使用时,他们根本不想知道具体哪个微服务对应哪个端口或者具体的地址。他只关心微服务的名称。
他们只想通过看到的微服务名,来使用这些微服务。
也就是说,我们对外暴露的只能是微服务名,来供使用者
8. 再次测试:
发现报错:
复制代码
原因:
这个微服务下面对应着多个微服务,这个微服务名下的多个微服务到底是哪个出来响应,没有办法识别。
复制代码
6.3 使用 @LoadBlanced 注解赋予 RestTemplate 负载均衡的能力
1. 以前我们把生产者微服务写死,没关系,因为只有一个生产者微服务,只认一个。
但是现在不能写死了,因为这个微服务名下对应有多个微服务,那么调用时,必须得说明
哪一个被调用了。
需要规定一种默认的负载均衡机制:@LoadBlanced
复制代码
我们在这里通过@LoadBlanced赋予了RestTemplate负载均衡的能力
2. 测试:
复制代码
开启了默认的轮询的负载机制,8001和8002交替出现(循环交替)
提前说一下这就是Ribbon的负载均衡功能默认就是轮询。
即:Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,
且该服务还有负载功能了。
消费者只关注生产者微服务名称,生产者微服务一定是一个集群,那就要有一个默认的
负载均衡。
复制代码
7 actuator 微服务信息完善
7.1 主机名称:服务名称的规范和修改
1. 按照规范的要求只暴露服务名,不带有主机名。
2. 修改生产者微服务的yml
复制代码
3. 测试:只暴露服务名
复制代码
4. actuator:监测检查
1. 显示应用的基本信息
复制代码
2. 显示应用的健康状态(健康检查)
复制代码
7.2 访问信息有 IP 信息提示
1. 我现在点击这个微服务的链接,没有ip信息提示。
实际工作中,我们都会说这个微服务是部署在几号机器上面的几号端口。我们要让访问信息
有ip信息提示。
2. 在每个微服务的yml中添加如下
复制代码
3. 实现
复制代码
8 服务发现 Discovery
1. 我们现在已经实现了这6个微服务的调用。
复制代码
不排除我们微服务自身,想对外提供功能。那么我们就要拿到在微服务中注册了的微服务的信息。
比如:主机名称,端口号。
2. 服务发现:
对于注册进eureka里面的微服务,可以通过服务发现来获得这些微服务的信息。
3. 修改cloud-provider-payment8001的controller:写好提供给外部的信息
复制代码
注意写在 80 和 8002 中也都可以获得所有的微服务信息,这里只是在 8001 上进行测试。
@Resource //发现自己的服务信息
protected DiscoveryClient discoveryClient;
//服务发现Discovery
@GetMapping(value = "/payment/discovery")
public Object discovery(){
//得到服务清单列表
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("*********service: "+ service);
}
//根据微服务的具体服务名称:得到微服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
复制代码
注入一个DiscoveryClient类,可以通过其中的两个方法得到我们注册了的服务的信息
3.1 得到服务清单:
discoveryClient.getServices();
3.2 根据微服务的具体服务名称:得到微服务实例
discoveryClient.getInstances("cloud-payment-service");
4. 在8001主启动类开启注解:@EnableDiscoveryClient
5. 测试
发型请求:http://localhost:8001/payment/discovery
页面返回的是discoveryClient的json传:
复制代码
控制台打印的信息:
复制代码
6. 所以我们今后只要对外暴露这样的一个rest服务接口地址。
调用者就可以访问这个地址获取微服务的信息。
复制代码
9 eureka 自我保护
9.1 自我保护
1. 概述:
保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,
EurekaServer将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,也就是不会注销
任何微服务。
如果在EurekaServer的首页看到以下这段提示,则说明Eureka进入了保护模式:
复制代码
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
属于CAP里面的AP分支。
Eureka它的设计思想:
分布式CAP里面的AP分支,某时刻某一个微服务不可用了,Eureka不会立刻清理,
依旧会对该微服务的信息进行保存。
2. 为什么会产生Eureka自我保护机制
因为可能存在这样的情况:
EurekaClient可以正常运行,但是与EurekaServer网络不通。
此时EurekaServer不会立刻将EurekaClient服务剔除。
3. 自我保护
复制代码
如果一定时间内丢失大量该微服务的实例,这时Eureka就会开启自我保护机制,不会剔除该服务。
因为这个现象可能是因为网络暂时不通,出现了Eureka的假死,拥堵,卡顿。
稍等会客户端还能正常发送心跳。
这就是CAP里面的AP分支思想。
4. 设计哲学
宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
复制代码
9.2 怎么禁止自我保护
默认是开启自我保护。
禁止自我保护:只要掉线了,立马删掉该服务实例
1. 注册中心:7001
出厂默认,自我保护机制是开启的
eureka.server.enable-self-preservation=true
默认服务实例掉线9s后删掉
eviction-interval-timer-in-ms: 9000
2. 修改一下yml
关闭自我保护,并且掉线2s就删除。
复制代码
3. 启动7001
自我保护已关闭。
复制代码
4. 修改eurekaClient端8001
它有两个默认的配置
# Eureka客户端向服务端发送心跳的时间间隔默认30秒
eureka.instance.lease-renewal-interval-in-seconds: 30 单位秒
# Eureka服务端在收到最后一次心跳后,90s没有收到心跳,剔除服务
eureka.instance.lease-expiration-duration-in-seconds: 90 单位秒
5. 测试:
8001能正常注册
复制代码
6. 模拟发生故障:
关闭8001
如果是默认的话,由于Eureka自我保护机制,会在90s后剔除该服务。
现在关闭了自我保护机制,并且在7001设置了掉线两秒就会剔除。
复制代码
迅速刷新几下后:该服务被剔除了。
复制代码