6.Eureka注册中心
6.1.认识Eureka
首先我们来解决第一问题,服务的管理。
问题分析
在刚才的案例中,student-service对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦。
网约车
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过“心跳”
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
6.3.入门案例
我们之前已经建立了一个parent项目,提供者项目,调用者项目,现在新增了一个注册中心项目,我们还是以多模块方式建立,如下:
spring-cloud-eureka-demo1 (parent项目)
-
spring-cloud-eureka-demo1-provide(提供者项目)
-
spring-cloud-eureka-demo1-web(调用者项目)
- spring-cloud-eureka-demo1-server(注册中心项目)
6.3.1.编写EurekaServer
接下来我们创建一个注册中心项目:spring-cloud-eureka-demo1-server
完整的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-eureka-demo1</artifactId>
<groupId>com.panshi.it</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-eureka-demo1-server</artifactId>
<properties>
<!-- 这个版本是稳定最终版本-->
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<!-- 2、引入这个版本spring-cloud-starter-netflix-eureka-server ,这个就是eureka注册中心依赖包 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 1、 加入这个springcloud 管理依赖包 -->
<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>
编写启动类:
@SpringBootApplication
@EnableEurekaServer // 声明这个应用是一个EurekaServer
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}
编写配置:
#端口
server.port=8761
# 应用名称,会在Eureka中显示
spring.application.name=eureka-server
#集群Eureka
#eureka.client.serviceUrl.defaultZone= http://localhost:8762/eureka/
# EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址
eureka.client.serviceUrl.defaultZone= http://localhost:8761/eureka
#是否注册自己的信息到EurekaServer,默认是true
eureka.client.register-with-eureka=false
# 是否拉取其它服务的信息,默认是true
eureka.client.fetch-registry=false
启动服务,并访问: http://127.0.0.1:8761/
6.3.2.将spring-cloud-eureka-demo1-provide注册到Eureka
注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。
我们在spring-cloud-eureka-demo1-provide中添加Eureka客户端依赖:
先添加SpringCloud依赖:
<properties>
<!-- 这个版本是稳定最终版本-->
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<!-- 1、用到注册中心或者springcloud技术,就要加入这个依赖管理包 -->
<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>
然后是Eureka客户端(提供者或者调用者):
<!-- 2、加入这个 Eureka客户端依赖包,意思就是把这个项目里面的ip:端口注册到Eureka注册中(提供注册地址到注册中心) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在启动类上开启Eureka客户端功能
通过添加@EnableDiscoveryClient
来开启Eureka客户端功能
@SpringBootApplication
@EnableDiscoveryClient // 开启EurekaClient功能
public class UserServiceDemoProvideApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceDemoProvideApplication.class, args);
}
}
编写配置
#===================配置数据库连接信息===========================
#警告红色:com.mysql.jdbc.Driver --> com.mysql.cj.jdbc.Driver
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
#The server time zone value '?й???????' 有这个异常,请加入这个参数serverTimezone=Asia/Shanghai
spring.datasource.url=jdbc:mysql://192.168.3.114:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
#===================配置mybatis信息============================
## 类型别名,多个使用(',' ';')分隔,对应数据库实体类包
mybatis.type-aliases-package=com.panshi.it.model
## 类型处理器,多个使用(',' ';')分隔,没有可以不用设置
# mybatis.type-handlers-package=com.panshi.it.xx
## 该配置项就是指将带有下划线的表字段映射为驼峰格式的实体类属性,true如数据库字段:update_time映射成updateTime字段,false就不支持
mybatis.configuration.map-underscore-to-camel-case=true
## mapper文件位置, dao或者mapper方式需要配置
mybatis.mapper-locations=classpath:mapper/*.xml
##打印mybatis sql 日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#============================SpringCloud配置注册中心=================
#定义当前项目服务名称
spring.application.name=eureka-service
#单个注册中心地址
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
#集群地址,多个地址
#eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka,http://localhost:8762/eureka
# 当其它服务获取地址时提供ip而不是hostname
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会系统在注册服务的会自己寻找本机ip地址,一般不指定
# eureka.instance.ip-address=192.168.9.12
注意:
- 这里我们添加了spring.application.name属性来指定应用名称,将来会作为应用的id使用。
- 不用指定register-with-eureka和fetch-registry,因为默认是true
重启项目,访问Eureka监控页面查看
我们发现user-service服务已经注册成功了
6.3.3.消费者从Eureka获取服务
接下来我们修改spring-cloud-eureka-demo1-web,尝试从EurekaServer获取服务。
方法与消费者类似,只需要在项目中添加EurekaClient依赖,就可以通过服务名称来获取信息了!
1)添加依赖:
先添加SpringCloud依赖:
<properties>
<!-- 这个版本是稳定最终版本-->
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<!-- 1、用到注册中心或者springcloud技术,就要加入这个依赖管理包 -->
<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>
然后是Eureka客户端:
<!-- 2、导入这个 Eureka客户端依赖,这个依赖就可以把自己地址注册到注册中心,或者从注册中心获取到提供者地址-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)在启动类开启Eureka客户端
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端
public class ConsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumeApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
// 默认的RestTemplate,底层是走JDK的URLConnection方式。
return new RestTemplate();
}
}
3)修改配置:
server.port=8011
spring.application.name=eureka-consumer
eureka.client.serviceUrl.defaultZone= http://localhost:8761/eureka
4)修改代码,用DiscoveryClient类的方法,根据服务名称,获取服务实例:
import org.springframework.cloud.client.discovery.DiscoveryClient;
@Service
public class StudentService implements IStudentService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
/*
调用提供者方式1,缺点就是无法解决多个服务中,有一个服务挂了,无法自动切换到下一个
*/
@Override
public List<Student> findStudent() {
//通过注册中心,获取提供者ip地址,根据服务id(EUREKA-SERVICE:就是提供者提供名称spring.application.name=eureka-service),获取服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("EUREKA-SERVICE");
log.info("获取到注册中心,获取提供者服务接口信息====>{}",instances);
// 因为只有一个Service,因此我们直接get(0)获取
ServiceInstance instance = instances.get(0);
// 获取ip和端口信息
String url = "http://"+instance.getHost()+":"+instance.getPort()+"/student-service";
log.info("获取到注册中心,请求服务接口地址====>{}",url);
return restTemplate.getForObject(url,List.class);
}
}
6.4.Eureka详解
6.4.1.基础架构
Eureka架构中的三个核心角色:
-
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-demo
-
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的provide-demo
-
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-web-demo
6.4.2.高可用的Eureka Server
Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
服务同步
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
动手搭建高可用的EurekaServer
我们假设要搭建两条EurekaServer的集群,端口分别为:8761和8762
1)我们修改原来的EurekaServer配置:
#端口
server.port=8761
# 应用名称,会在Eureka中显示
spring.application.name=eureka-server1
#集群Eureka
#eureka.client.serviceUrl.defaultZone= http://localhost:8762/eureka/
# EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址
eureka.client.serviceUrl.defaultZone= http://localhost:8762/eureka
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务进行注册,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:
- 删除了register-with-eureka=false和fetch-registry=false两个配置。因为默认值是true,这样就会把自己注册到注册中心了。
- 把service-url的值改成了另外一台EurekaServer的地址,而不是自己
2)另外一台配置恰好相反:
#端口
server.port=8762
# 应用名称,会在Eureka中显示
spring.application.name=eureka-server2
# 配置其他Eureka服务的地址,而不是自己
eureka.client.serviceUrl.defaultZone= http://localhost:8761/eureka
# 当其它服务获取地址时提供ip而不是hostname
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
3)启动测试:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ujYO2gAl-1640575368695)(assets\1594627805271.png)]
4)客户端注册服务到集群
也就是提供者,调用者都要配置两个注册中心地址即可
因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:
eureka:
client:
service-url: # EurekaServer地址,多个地址以','隔开
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
6.4.3.服务提供者
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-erueka=true
参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。第一层Map的Key就是服务名称,第二层Map的key是服务的实例id。
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);
有两个重要参数可以修改服务续约的行为:
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90
- lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
- lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。
但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。
provide或者调用项目
# 5秒一次心跳 ,结合注册中心项目设置有效时间
eureka.instance.lease-renewal-interval-in-seconds=5
# 10秒即过期 ,结合注册中心项目性设置有效时间
eureka.instance.lease-expiration-duration-in-seconds=10
提供者这两个参数要设置有效,必须要注册中心检测配置时间相结合
eureka项目
#扫描失效服务的间隔时间(缺省为60*1000ms),结合提供者设置有效时间
eureka.server.eviction-interval-timer-in-ms=2000
测试方法:eureka项目修改配置,provide修改配置
6.4.4.服务消费者
获取服务列表
当服务消费者启动时,会检测eureka.client.fetch-registry=true
参数的值,如果为true,则会从Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒
会重新获取并更新数据。我们可以通过下面的参数来修改:
#默认`每隔30秒`会重新获取并更新数据
eureka.client.registry-fetch-interval-seconds=5
生产环境中,我们不需要修改这个值。
但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。
测试方法:停止提供者项目,观察注册中心控制台,如果已消失了,再过5s后获取客户端列表,这个时候返回的数据应该就是0
6.4.5.失效剔除和自我保护
失效剔除
有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。
可以通过eureka.server.eviction-interval-timer-in-ms
参数对其进行修改,单位是毫秒,生成环境不要修改。
这个会对我们开发带来极大的不变,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如10S
自我保护
我们关停一个服务,就会在Eureka面板看到一条警告:必须满足Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。
但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:
#扫描失效服务的间隔时间(缺省为60*1000ms)
eureka.server.eviction-interval-timer-in-ms=5000
#关闭自我保护模式(缺省为打开)
eureka.server.enable-self-preservation=false