主要内容
- Eureka服务注册与发现
- 单机Eureka构建
-
1 Eureka基础知识
前面我们没有服务注册中心,也可以服务间调用,为什么还要服务注册?
- 当服务很多时,单靠代码手动管理是很麻烦的,需要一个公共组件,统一管理多服务之间的相互调用。
- Eureka用于服务注册,目前官网已经停止更新。
1.1 什么是服务治理
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。 n个生产者与那个消费者之间相互调用
1.2 什么是服务注册与发现
如果你的服务注册中心只有一台机器,那么只要它一宕机,就会产生单点故障。所以一般服务注册中心能配多个就配多个,主要就是为了避免单点故障。
Eureka采用了CS(Client-Server)的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
下左图是Eureka系统架构,右图是Dubbo的架构,请对比
1.3 Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
2 单机Eureka构建步骤
2.1 建Module
2.2 改pom
1.x和2.x的Eureka依赖对比
1.x: server跟client合在一起
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
2.x: server跟client分开
<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>
我们当前模块时eureka的server端,那么引入依赖
<?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">
<parent>
<artifactId>jdk8cloud2021</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
2.3 写yml
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
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注册中心的服务器。
@SpringBootApplication
@EnableEurekaServer //表示当前是Eureka的服务注册中心
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
3.1.1 改pom
添加一个Eureka-Client依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.1.2 改yml
yml文件添加如下配置
eureka:
client:
# 表示将自己注册进EurekaServer默认为true
register-with-eureka: true
# 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
3.1.3 改主启动类
添加注解:@EnableEurekaClient。 指明这是一个Eureka客户端,要注册进EurekaServer中。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
//@MapperScan("com.atguigu.springcloud.dao")
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}
3.1.4 测试
启动8001模块
红色字体表示eureka的自我保护机制。
注意:注册进来的服务名,就是我们在该服务yml文件中配置的微服务名。
3.2 消费者微服务80注册进EurekaServer
3.2.1 改pom
3.2.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.2.3 修改主启动类
3.2.4 测试
启动80消费者端微服务
刷新http://localhost:7001/页面:消费者80微服务也注册进来了。
如果不想将服务注册进eureka
在yml文件中,设置 register-with-eureka: false
4 集群Eureka搭建
单机版Eureka构建到这就结束了,但是企业中不可能有谁敢说它的服务注册中心是单机版。 因为没有集群的高可用,就会带来一个严重的问题,单点故障。
- Eureka集群原理说明
- EurekaServer集群环境构建步骤
- 将支付服务8001微服务发布到上面2台Eureka集群配置中
- 将订单服务80微服务发布到上面2台Eureka集群配置中
- 测试01
- 支付服务提供者8001集群环境构建。
- 负载均衡
- 测试02
4.1 Eureka集群原理说明
Eureka流程原理:
问题:微服务RPC远程服务调用最核心的是什么?
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)”了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错
Eureka集群原理:互相注册,相互守望
4.2 EurekaServer集群环境构建
4.2.1 新建一个cloud-eureka-server7002模块
4.2.2 pom依赖
4.2.3 修改两个EurekaServer的yml配置
现有存在多台eureka服务器,因此每一台eureka服务器需要有自己的主机名,同时各服务器需要相互注册
修改域名映射配置文件
C:\Windows\System32\drivers\etc 路径下的host文件
配置之后我们可以直接通过http://eureka7001.com:7001和http://eureka7002.com:7002来访问这两个EurekaServer。
修改eureka的yml文件
eureka1:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称 这里跟host配置文件中的一致
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
# 单机就是自己
# defaultZone: http://eureka7001.com:7001/eureka/
# 集群指向其他eureka
#defaultZone: http://eureka7002.com:7002/eureka/
#写成这样可以直接通过可视化页面跳转到7002
defaultZone: http://eureka7002.com:7002/
eureka2:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称 这里跟host配置文件中的一致
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
# 单机就是自己
# defaultZone: http://eureka7002.com:7002/eureka/
#集群版
#defaultZone: http://eureka7001.com:7001/eureka/
#写成这样可以直接通过可视化页面跳转到7001
defaultZone: http://eureka7001.com:7001/
4.2.4 主配置类
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7002 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7002.class, args);
}
}
4.2.5 测试
开启eureka7001和eureka7002这两个服务
访问http://eureka7001.com:7001和http://eureka7002.com:7002
两个服务器的相互注册成功。
5 注册提供这微服务和消费者微服务
5.1 将提供者服务8001微服务和消费者服务80微服务发布到上面的2台Eureka集群配置中
修改yml
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
5.2 测试
- 先启动eureka7001/7002
- 再启动payment8001、order80
- 登陆http://eureka7001.com:7001测试
这里的CLOUD-ORDER-SERVICE和CLOUD-PAYMENT-SERVICE
分别对应了提供服务端和消费服务端的yml配置文件中的spring.application.name
6 生产者(provider)微服务集群构建
一个提供者微服务不可能应对所有的消费者微服务
6.1 新建cloud-provider-payment8002 微服务模块
1. pom与cloud-provider-payment8001一致
唯一区别在于<artifactId>cloud-provider-payment8002</artifactId>
2. 写yml
3. 主启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8002 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8002.class, args);
}
}
4. 其他业务诶和payment8001一致
6.2 修改controller
- 注意:两个provider微服务在注册中心中的注册名是一样的,这两个微服务对外暴露的都是同一个名字,即yml文件中spring.application.name均为
- 消费者微服务调用的是cloud-payment-service这个服务,但是在这个名字下面可能有多台机器。那么如何确定消费者微服务调用哪台机器上的cloud-payment-service服务呢?
通过端口号来区分,我们有两个provider微服务,端口号分别为8001、8002
修改两个生产者微服务的controller方法,改法一致。
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
//插入数据到数据库
@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("***********插入结果:" + result);
int age = 10 / 2;
if (result > 0) {
return new CommonResult(200, "插入数据库成功,serverPort:" + serverPort, result);
}
return new CommonResult(444, "插入数据库失败");
}
//查询数据库中指定id的记录
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("***********查询结果:" + payment);
if (payment != null) {
return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment);
}
return new CommonResult(404, "查询id失败:" + id);
}
}
测试启动EurekaServer
- 测试消费者微服务
发现若干次不同请求,走的都是provider8001。这是因为我们在消费者为服务中将地址写死了
所以在访问消费者微服务后,消费者微服务再去调用生产者中的方法。 调用发送的请求一直都是http://localhost:8001/… 所以一直调用了8001端口的微服务。 那么现在我们不指明具体的微服务端口,指定一个这两个生产者微服务共有的指向: 即微服务名:CLOUD-PAYMENT-SERVICE
那么消费者再去调用生产者中的方法时,就没有指定具体的端口(具体的微服务)。那么哪一个微服务被调用由其他方式来决定。
注意这个说法:
- 这个程序的使用者会知道这些微服务的端口或者地址吗?
- 消费者使用时,他们根本不想知道具体哪个微服务对应哪个端口或者具体的地址。他只关心微服务的名称。
- 他们只想通过看到的微服务名,来使用这些微服务。
- 也就是说,我们对外暴露的只能是微服务名,来供使用者使用
- 再次测试
报错:
原因: 这个微服务下面对应着多个微服务,这个微服务名下的多个微服务到底是哪个出来响应,没有办法识别。
6.3 使用@LoadBlanced注解赋予RestTemplate负载均衡的能力
- 以前我们把生产者微服务写死,没关系,因为只有一个生产者微服务,只认一个。
但是现在不能写死了,因为这个微服务名下对应有多个微服务,那么调用时,必须得说明 哪一个被调用了。 需要规定一种默认的负载均衡机制:@LoadBlanced;在Order80端的配置类上加入@LoadBlanced注解开启负载均衡7 actuator微服务信息完善
7.1 主机名称:服务名称的规范和修改
- 按照规范的要求只暴露服务名,不带有主机名。
2. 修改生产者微服务的yml
3.测试:仅暴露服务名7.2 访问信息有IP信息提示
1. 我现在点击这个微服务的链接,没有ip信息提示。 实际工作中,我们都会说这个微服务是部署在几号机器上面的几号端口。我们要让访问信息 有ip信息提示。
2. 在每个微服务的yml中添加如下8 服务发现Discovery
服务发现:对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
@EnableDiscoveryClient注解
不排除我们微服务要对外提供一种功能。那么我们就需要拿到在eureka上注册了的微服务的信息,例如:主机名称、端口号。
1 修改cloud-provider-payment8001的controller:写好提供给外部的信息
注意写在80和8002中也都可以获得所有的微服务信息,这里只是在8001上进行测试。 ```java @Resource //发现自己的服务信息 private DiscoveryClient discoveryClient;
//服务发现
@GetMapping(value = “/payment/discovery”)
public Object discovery() {
//得到全部服务清单
List
+ instance.getPort() + "\t" + instance.getUri());
}
return discoveryClient;
}
```
2 在8001的主启动类中开启注解:@EnableDiscoveryClient
测试,访问http://localhost:8001/payment/discovery
控制台打印信息
9 eureka自我保护
9.1 自我保护
- 概述: 保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式, EurekaServer将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,也就是不会注销任何微服务。
如果在EurekaServer的首页看到以下这段提示,则说明Eureka进入了保护模式:
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
属于CAP里面的AP分支。
Eureka它的设计思想: 分布式CAP里面的AP分支,某时刻某一个微服务不可用了,Eureka不会立刻清理, 依旧会对该微服务的信息进行保存。
2. 为什么会产生Eureka自我保护机制?
因为可能存在这样的情况: EurekaClient可以正常运行,但是与EurekaServer网络不通。 此时EurekaServer不会立刻将EurekaClient服务剔除。
3. 自我保护
如果一定时间内丢失大量该微服务的实例,这时Eureka就会开启自我保护机制,不会剔除该服务。 因为这个现象可能是因为网络暂时不通,出现了Eureka的假死,拥堵,卡顿。 稍等会客户端还能正常发送心跳。 这就是CAP里面的AP分支思想。
4.设计哲学
宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例CAP
- Consistency(一致性):即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
- Avaliability(可用性):即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
- Partition Tolerance(分区容错性):即分布式系统在遇到某节点或网络故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求应用虽然是一个分布式系统,但看上去切好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统要求,对于用户而言并没有什么体验上的影响。
- CP和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只能2选1
9.2 怎么禁止自我保护
修改7001的yml配置文件
修改eurekaClient端8001
它有两个默认的配置
# Eureka客户端向服务端发送心跳的时间间隔默认30秒
eureka.instance.lease-renewal-interval-in-seconds: 30 单位秒
# Eureka服务端在收到最后一次心跳后,90s没有收到心跳,剔除服务
eureka.instance.lease-expiration-duration-in-seconds: 90 单位秒