此文章主要讲解 springcloud 中服务注册中心 Eureka 的相关知识。
微服务的注册中心
概述
- 注册中心可以说是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其他服务的时候,就从这里找到服务的地址,进行调用。
注册中心的主要作用
-
服务注册中心(简称注册中心)是微服务架构非常重要的一个组件,在微服务架构里面起到了协调者的作用。注册中心一般包含如下几个功能:
-
服务发现:
-
- 服务注册/反注册:保存服务提供者和服务调用者的信息。
-
- 服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能。
-
- 服务路由(可选):具有筛选整合服务提供者的能力。
-
服务配置:
-
- 配置订阅:服务提供者和服务调用者订阅服务相关的配置。
-
- 配置下发:主动将配置推送给服务提供者和服务调用者。
-
服务健康检测:
-
- 检测服务提供者的健康状况。
常见的注册中心
zookeeper
-
zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用经常遇到的一些数据管理问题,如统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。
-
简单的说,zookeeper=文件系统+监听通知机制。
Eureka
- Eureka是在Java语言上,基于RESTful API开发的服务注册和发现组件,SpringCloud Netflix中的重要组件。
Consul
- Consul是HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发现和注册软件,采用Raft算法保证服务的一致性,且支持健康检查。
Nacos
- Nacos是一个更基于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单的说,Nacos就是注册中心+配置中心的组合,提供简单易用的特性集,帮助我们解决微服务开发必会涉及到的服务注册和发现、服务配置、服务管理等问题。Nacos还是SpringCloud Alibaba组件之一,负责服务注册和发现。
总结
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 |
---|---|---|---|---|---|
Eureka | Java | AP | 无 | 可以配置支持 | HTTP |
Consul | Go | CP | Raft | 支持 | HTTP/DNS |
Zookeeper | Java | CP | Paxos | 支持 | 客户端 |
Nacos | Java | AP | Raft | 支持 | HTTP |
服务注册中心Eureka(已过时)
如果是上面只有两个微服务,通过 RestTemplate ,是可以相互调用的,但是当微服务项目的数量增大,就需要服务注册中心。目前没有学习服务调用相关技术,使用 SpringCloud 自带的 RestTemplate 来实现RPC。
Eureka的概述
官方停更不停用,以后可能用的越来越少。
- Eureka是Netflix开发的服务发现框架,SpringCloud将它集成到自己的子项目spring-cloud-netflix中,实现SpringCloud的服务发现功能。
-
上图简要描述了Eureka的基本架构,由3个角色组成:
-
Eureka Server:提供服务注册和发现。
-
Service Provider:服务提供方(将自身服务注册到Eureka,从而使得服务消费方能够找到)。
-
Service Consumer:服务消费方(从Eureka获取注册服务列表,从而能够消费服务)。
Eureka的交互流程和原理
-
由上图可知,Eureka包含两个组件:Eureka Server和Eureka Client,它们的作用如下:
-
- Eureka Client是一个Java客户端,用于简化和Eureka Server的交互。
-
- Eureka Server提供服务发现能力,各个微服务启动时,会通过Eureka Client向Eureka Server进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息。
-
- 微服务启动后,会周期性的向Eureka Server发送心跳(
默认周期为30秒
)以续约自己的信息。如果Eureka Server在一定时间内(默认为90秒
)没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点。
- 微服务启动后,会周期性的向Eureka Server发送心跳(
-
- 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册表的同步。
-
- Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕机,服务消费者依然可以使用缓存中的信息找到服务提供者。
-
综上,Eureka通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性。
概念和理论
它是用来服务治理,以及服务注册和发现,服务注册如下图:
Eureka的使用步骤
-
搭建Eureka Server。
-
- 创建子模块工程( cloud-eureka-server7001 )。
-
- 导入Eureka对应的坐标。
-
- 配置 application.yml 。
-
- 配置启动类。
-
将服务提供者注册到Eureka Server上。
-
服务消费者通过注册中心获取服务列表,并调用。
搭建Eureka Server(注册中心)
新建工程
创建子模块
创建 cloud-eureka-server7001 模块
改 POM 文件
引入对应的依赖
版本说明:
<?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>spring_cloud_atguigu_2020</artifactId>
<groupId>com.itjing.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</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>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入自己定义的api-->
<dependency>
<groupId>com.itjing.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
配置 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/
主启动类
// exclude :启动时不启用 DataSource 的自动配置检查
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaServer // 表示它是服务注册中心
public class EurekaServerMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerMain7001.class, args);
}
}
测试
打开浏览器访问 http://localhost:7001/,即可进入Eureka Server内置的管理控制台,显示效果如下:
服务注册到Eureka注册中心
这里的提供者和消费者,还是使用 上面的 cloud-provider-payment8001 和 cloud-consumer-order80 模块,做如下修改:
提供者
改 POM 文件
在 pom 文件的基础上引入 eureka 的client包:
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改 yml 配置文件
添加如下配置:
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://localhost:7001/eureka/
修改主启动类
在主启动类上加上注解:@EnableEurekaClient
测试
先启动 EurekaServer
,然后启动服务提供者。
访问:http://localhost:7001/
二者保持一致!
消费者
改 POM 文件
加入 eureka-client 依赖
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改 yml 配置文件
加入以下内容
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/
修改主启动类
在主启动类上加上注解:@EnableEurekaClient
测试
先启动 Eureka Server ,然后启动提供者和消费者
Eureka 集群
概述
-
在上面的章节,实现了单节点的Eureka Server的服务注册和服务发现功能。Eureka Client会定时连接到Eureka Server,获取注册表中的信息并缓存到本地。微服务在消费远程API的时候总是使用本地缓存中的数据。因此一般来说,即使Eureka Server出现宕机,也不会影响到服务之间的调用。但是如果Eureka Server宕机的时候,某些微服务也出现了不可用的情况,Eureka SClient中的缓存如果不被刷新,就可能影响到微服务的调用,甚至影响到整个应用系统的高可用。因此,在生产环境中,通常会部署一个高可用的Eureka Server集群。
-
Eureka Server可用通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增量的同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册时Eureka Server的默认行为。
搭建Eureka Server高可用集群
Eureka 集群的原理,就是 相互注册,互相守望,这里我们就只模拟两台
修改本机的host文件
由于个人计算机中进行测试很难模拟多主机的情况,Eureka配置server集群的时候需要修改host文件。
在win10中host文件的路径是 C:\Windows\System32\drivers\etc\hosts
。
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
新建模块
现在创建 cloud-eureka-server7002 ,也就是第二个 Eureka 服务注册中心,pom 文件
和 主启动类
,与第一个Eureka类似,可以将其复制过来。
修改 yml 文件
7001 端口的 yml 文件:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka 服务器的实例名称
client:
# false 代表不向服务注册中心注册自己,因为它本身就是服务中心
register-with-eureka: false
# false 代表自己就是服务注册中心,自己的作用就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#单机版
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 一定要注意这里的地址,这是搭建集群的关键
defaultZone: http://eureka7002.com:7002/eureka/
7002 端口的 yml 文件:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com # eureka 服务器的实例名称
client:
# false 代表不向服务注册中心注册自己,因为它本身就是服务中心
register-with-eureka: false
# false 代表自己就是服务注册中心,自己的作用就是维护服务实例,并不需要去检索服务,是否从Eureka中获取服务列表
fetch-registry: false
service-url: # 配置暴露给Eureka Client的请求地址
# 设置与 Eureka Server 交互的地址,查询服务 和 注册服务都依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/
测试 Eureka 集群
运行这两个 Eureka 进行访问测试
http://eureka7001.com:7001/
http://eureka7002.com:7002/
eureka.instance.hostname 才是启动以后 本 Server 的注册地址,而 service-url 是 map 类型,只要保证 key:value 格式就行,它代表本 Server 指向了哪些其它Server 。利用这个,就可以实现 Eureka Server 相互之间的注册,从而实现集群的搭建。
修改 提供者 和 消费者 的 yml 配置
修改提供者和消费者,让其加入 Eureka 集群中。
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ # 集群版
测试集群中的服务注册
- 先启动 EurekaServer,7001/7002服务
- 再启动服务提供者,8001
- 再启动服务消费者,80
- 然后访问测试。
http://eureka7001.com:7001/
http://eureka7002.com:7002/
服务提供者集群
为提供者,即 cloud-provider-payment8001 模块创建集群,新建模块为 cloud-provider-payment8002
最终实现:
注意在 Controller 返回不同的消息,从而区分者两个提供者的工作状态。
新建模块
新建模块 cloud-provider-payment8002
改 POM
直接从 8001 复制
配置文件
配置区别:改变端口号,只要保证消费者项目对服务注册中心提供的名称一致,即完成集群。
# 端口号
server:
port: 8002 # 端口号不一样
spring:
application:
name: cloud-payment-service # 这次重点是这里,两个要写的一样,这是这个集群的关键
# 数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
# mybatis 相关
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.itjing.springcloud.entities # 所有Entity 别名类所在包
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
主启动
改变名称为 PaymentMain8002
业务类
直接从 8001 复制
改变服务提供者8001/8002中的Controller
例如在里面让输出信息加上端口号,以便于辨别是哪个提供者提供服务。
@RestController //必须是这个注解,因为是模拟前后端分离的 restful风格的请求,要求每个方法返回 json
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@PostMapping(value = "/payment/payments")
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("****插入结果:" + result);
if (result > 0) {
return new CommonResult(200, "插入数据库成功,serverPort:" + serverPort, result);
}
return new CommonResult(444, "插入数据库失败", null);
}
@GetMapping(value = "/payment/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
Payment result = paymentService.getPaymentById(id);
log.info("****查询结果:" + result);
if (result != null) {
return new CommonResult(200, "查询成功,serverPort:" + serverPort, result);
}
return new CommonResult(444, "没有对应id的记录", null);
}
}
测试
- 先启动 EurekaServer,7001/7002服务
- 再启动服务提供者,8001,8002
- 再启动服务消费者,80
- 然后访问测试。
服务提供者 8001 和 8002 全部注册进来了!
访问:http://localhost/consumer/payment/1
发现不管怎么访问,都是访问8001,这是因为我们在消费者中把地址写死了。
切记,这个不能写死。
修改消费者
消费者如何访问由这两个提供者组成的集群?
我们需要根据服务名称访问!
修改 Controller
@RestController
@Slf4j
public class OrderController {
//远程调用的 地址
// 重点是这里,改成提供者在Eureka上的名称,而且无需写端口号
// public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;
@PostMapping("consumer/payment/payments")
public CommonResult<Payment> create(Payment payment) {
/**
param1 请求地址,param2 请求参数, param3 返回类型
*/
return restTemplate.postForObject(PAYMENT_URL + "/payment/payments", payment, CommonResult.class);
}
@GetMapping("consumer/payment/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/" + id, CommonResult.class);
}
}
修改 Config 配置类
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //这个注解,就赋予了 RestTemplate 负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
测试
进行测试访问:http://localhost/consumer/payment/1,发现第一次访问了8001
刷新页面,又变成了访问8002
实现了负载均衡。
actuator信息配置
修改服务提供者在 Eureka 注册中心显示的主机名:
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001 # 8002 改成 payment8002
显示微服务所在的主机地址:
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001
prefer-ip-address: true # 访问路径可以显示ip地址======================
服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现
来获得该服务的信息
修改 提供者 8001
这里我们就直接通过 8001 去自测了,修改Controller,加入以下内容
@Resource
private DiscoveryClient discoveryClient;
@GetMapping("/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 serviceInstance : instances) {
log.info(serviceInstance.getServiceId() + "\t" + serviceInstance.getHost()
+ "\t" + serviceInstance.getPort() + "\t" + serviceInstance.getUri());
}
return this.discoveryClient;
}
主启动类
在主启动类上加上注解:@EnableDiscoveryClient
自测
- 先启动EurekaServer
- 再启动 8001 主启动类
- 访问测试:http://localhost:8001/payment/discovery
Eureka 自我保护机制
概述
-
微服务第一次注册成功后,每30秒会发送一次心跳将服务的实例信息注册到注册中心。通知Eureka Server该实例依然存在。如果超过90秒没有发送心跳,则服务器将从注册中心将此服务移除。
-
Eureka Server在运行期间,会统计心跳失败的比例在15分钟内是否低于85%,如果出现低于85%的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),那么Eureka就会认为客户端和注册中心出现了网络故障,此时会做如下的处理:
-
- Eureka不再从注册列表中删除因为长时间没有收到心跳而应该过期的服务。
-
- Eureka仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上(保证当前节点依然可用)。
-
- 当网络稳定的时候,当前实例新的注册信息会被同步到其他节点中。
-
验证自我保护机制开启,并不会马上呈现在web后台上,而是默认需要等待5分钟(可以通过eureka.server.wait-time-in-ms-when-sync-empty配置),即5分钟后你就会看到如下所示的提示信息:
讲解
禁止自我保护
通过设置eureka.enableSelfPreservation=false
来关闭自我保护机制
eureka:
instance:
hostname: eureka7001.com # eureka 服务器的实例名称
client:
# false 代表不向服务注册中心注册自己,因为它本身就是服务中心
register-with-eureka: false
# false 代表自己就是服务注册中心,自己的作用就是维护服务实例,并不需要去检索服务,是否从Eureka中获取服务列表
fetch-registry: false
service-url:
#单机版
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 一定要注意这里的地址,这是搭建集群的关键
defaultZone: http://eureka7002.com:7002/eureka/
server:
# 关闭自我保护机制,保证不可用服务被及时剔除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
修改 Eureka Client 模块的 心跳间隔时间
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001
prefer-ip-address: true # 访问路径可以显示ip地址
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 5
# Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认为90秒,超时将剔除服务)
lease-expiration-duration-in-seconds: 10
测试
- 7001 和 8001 都配置完成
- 先启动 7001 ,再启动 8001
- 关闭 8001,可以发现马上被删除了
Eureka中的常见问题
服务注册慢
-
默认情况下,服务注册到Eureka Server的过程较慢。
-
服务的注册设计到心跳,默认心跳的间隔为30s。在实例、服务器和客户端都在本地缓存中具有相同的元数据之前,服务不可用于客户端发现。可用通过配置
eureka.instance.lease-renewal-interval-in-seconds
(心跳续约间隔)来加快客户端连接到其他服务的过程。在生产中,最好使用默认值,因为在服务器内部有一些计算,它们对续约做出假设。
服务节点剔除问题
-
默认情况下,由于Eureka Server剔除失效服务间隔时间为90s且存在自我保护机制。所以不能有效而迅速的剔除失效节点,这对于开发或测试会造成困扰。解决方案如下:
-
对于Eureka Client:配置开启健康检查,并设置续约时间。
eureka:
client:
# 注册进 Eureka 的服务中心
register-with-eureka: true
# 是否从eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与 Eureka Server 交互的地址
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
healthcheck:
enabled: true # 开启健康检查(依赖spring-boot-actuator)
instance:
instance-id: payment8001
prefer-ip-address: true # 访问路径可以显示ip地址
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 5
# Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认为90秒,超时将剔除服务)
lease-expiration-duration-in-seconds: 10
Eureka的源码解析
参考这篇文章:https://www.yuque.com/sunxiaping/yg511q/gxd0r8#f2b7bb72