Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件。
SpringCloud封装了Netflix公司开发的Eureka模块来实现服务注册和发现。Eureka采用了C-S的设计架构。EurekaServer作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心路连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行。SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑。
这里使用的均为 properties文件,如果你喜欢使用 yml 风格,推荐一个神器 在线yml和properties文件互转
新建一个 SpringCloud 项目,此项目基于 SpringBoot 2.2.2.RELEASE 版本
再创建3个子模块,一个生产者,一个消费者,一个注册中心
pom.xml
<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">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.fu.springcloudgroupId>
<artifactId>springcloudartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.RELEASEspring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
新建一个 porducer,端口设置为 8081
项目架构如图所示,简单 crud 代码不再展示,只展示核心配置代码
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
......
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
application.properties
server.port=8081
# 服务的名字,注册到注册中心的名字,消费者来根据名字调用服务 可以重复
spring.application.name=springcloud-producer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID 不可以重复 标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-producer-8181
启动类
@SpringBootApplication
@EnableEurekaClient // 需要此注解,启用Eureka客户端
public class SpringCloud01Producer {
public static void main(String[] args) {
System.out.println("生产者服务启动...8081");
SpringApplication.run(SpringCloud01Producer.class, args);
}
}
Controller
@RestController
@Scope("prototype")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("list")
public List<User> queryUserList() {
return userService.queryUserList();
}
}
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>3.9.0version>
dependency>
......
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
application.properties
server.port=8080
spring.application.name=springcloud-consumer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID 不可以重复 标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-consumer-8080
RestTemplateConfig.java
@SpringBootConfiguration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
}
启动类
@SpringBootApplication
@EnableEurekaClient
public class SpringCloud01Consumer {
public static void main(String[] args) {
SpringApplication.run(SpringCloud01Consumer.class, args);
}
}
Controller
@RestController
@Scope("prototype")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("queryUserList")
public List queryUserList() {
// 使用如下代码可以:不使用 Eureka,也能直接远程调用生产者
// String url = "http://127.0.0.1:8081/list";
// return restTemplate.getForObject(url, List.class);
// 获取所有远程服务的调用地址
List<String> services = discoveryClient.getServices();
System.out.println("注册中心上服务列表有: " + services);
// 通过服务名获取调用的服务信息
List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-producer");
for(ServiceInstance element: instances){
System.out.println(element.getServiceId()+"\t"+element.getHost()+"\t"+element.getPort()+"\t" +element.getUri());
}
if (instances.size() == 0) {
return null;
}
// 因为现在是单体服务,并不是集群模式,所以只可能有一个服务,直接调用第一个即可
ServiceInstance instance = instances.get(0);
String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/list";
return this.restTemplate.getForObject(url, List.class);
}
}
properties文件用于单个 注册中心,yml文件用于Eureka集群
这里使用的是properties文件
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
application.properties
server.port=8888
spring.application.name=eureka-server
# 注册中心主机名
eureka.instance.hostname=127.0.0.1
# 是否将自己注册到EurekaServer,默认是true
eureka.client.fetch-registry=false
# 是否进行检测,false: 因为自己是注册中心,不需要去检索服务信息
eureka.client.register-with-eureka=false
# 注册地址 多节点用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
启动类
@SpringBootApplication
@EnableEurekaServer // 表示这是一个 Eureka的服务端,即注册中心
public class SpringCloud01EurekaServer {
public static void main(String[] args) {
System.out.println("Eureka服务启动...8888");
SpringApplication.run(SpringCloud01EurekaServer.class, args);
}
}
先启动Eureka注册中心,再启动生产者和消费者。
查看eureka的控制面板,这两个服务都已经注册进去了
访问消费者的控制器,可以正常获取到数据
官方解释: 自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
默认情况下,Eureka Client
会定时的向 Eureka Server
端发送心跳包,默认是30s
发送一次,目的是告诉 Eureka Server
当前客户端实例还处于存活状态,如果Eureka server
在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90
秒),但是,如果短时间内丢失大量的实例心跳,便会触发Eureka server
的自我保护机制。
官方解释总是这么晦涩难懂,接下来使用一个实际场景,助你理解Eureka的自我保护机制。
启动项目完成,打开eureka的控制面板,可以看到如下内容
现在我们停止了 Consumer 服务,刷新页面
可以看到,出现了一个警告:紧急情况!Eureka可能不正确地声称实例在不在的情况下出现。续订小于阈值,因此不会为了安全而过期实例。
但是并没有把consumer服务剔除掉,而是使用了缓存,将consumer读取出来。
有可能这是因为网络波动的缘故,导致Eureka没有及时的收到 consumer 服务的心跳,这时候就需要Eureka的自我保护机制。如果没有自我保护,直接把consumer服务给剔除了,那网络连接稳定的时候,又需要重新将服务注册到Eureka上,这显然是非常消耗性能的。
为什么要关闭
这么好的功能为什么要关闭呢?
在生产环境下,肯定是要开启的。
但是在开发环境下,我们需要不停的测试各种代码,难免会一直重启服务,但是 Eureka 服务一般不会更改,也就不必要重启;我们在进行测试的时候,明明服务已经关闭了,但是Eureka上仍然显示有此服务,会给我们开发人员带来影响,不利于我们的开发。所以,在开发环境下,我们需要关闭Eureka的自我保护机制。
关闭方式
注册中心关闭自我保护机制,修改检查失效服务的时间,以确保注册中心将不可用的实例及时正确剔除
在eureka项目的 application.properties 文件中,添加以下配置
# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false
# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000
减短客户端服务发送服务心跳给服务端的时间, 在开发测试时,将值设置设置小些,保证服务关闭后注册中心能及时踢出服务。
在 consumer 项目的 application.properties 文件中,添加如下配置
# 租赁到期持续时间,即 如果6秒还没有收到信息,就在eureka中删除此服务
eureka.instance.lease-expiration-duration-in-seconds=6
# 租赁更新时间间隔,即 每隔2秒向注册中心发送一个确认信息
eureka.instance.lease-renewal-interval-in-seconds=2
测试关闭是否成功
重新启动3个项目
我们看到一个警告信息,是提示你Eureka的自我保护已经关闭了,忽略即可
自动保存模式关闭。这可能无法在出现网络或其他问题时保护实例。
然后关闭 consumer 服务,再次刷新页面(6秒后,会删除consumer服务)
因为我们只配置了 consumer 服务,所以当关闭 producer服务时,eureka并不能及时将 producer 服务剔除
我们手动关闭了 eureka 的自我保护机制,有点太暴力了,而且关闭自我保护机制之后,控制面板还会有一个红色警告,这让强迫症的开发者看起来很不爽。并且每个服务都需要配置那些信息,项目上线的时候,每个配置都需要修改,比较繁琐。
那么如何优雅的将 eureka中不可用的服务及时剔除呢?
需要在 client 客户端上配置:
pom.xml
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
application.properties
# 暴露已经停止的服务
management.endpoints.web.exposure.include=shutdown
# 关闭已经停止的服务
management.endpoint.shutdown.enabled=true
测试
启动3个项目,然后使用 post请求 访问如下地址,使用form,或者ajax都行,这里使用 PostMan 进行测试
http://127.0.0.1:8080/actuator/shutdown
然后刷新页面
可以看到,producer服务已经从eureka注册中心移除。而且也没有烦人的警告信息,项目上线的时候也不需要更改配置信息
# 注册中心主机名
eureka.instance.hostname=127.0.0.1
# 是否将自己注册到EurekaServer,默认是true
eureka.client.fetch-registry=false
# 是否进行检测,false: 因为自己是注册中心,不需要去检索服务信息
eureka.client.register-with-eureka=false
# 注册地址 多节点用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false
# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID 不可以重复 标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-producer-8181
# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false
# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000
# 表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
eureka.client.registry-fetch-interval-seconds=5
# 不启动此服务
eureka.client.enabled=false
详情参见:SpringCloud系列(三)Eureka搭建集群实现高可用(三种方式)