6. Eureka的服务注册与发现
6.1 Eureka基础介绍
1. 什么是服务治理
Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
2.什么是服务注册与发现
Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址)) 下左图是Eureka系统架构,右图是Dubbo的架构,请对比

3.Eureka包含两个组件
Eureka Server和Eureka Client
Eureka Server提供服务注册服务 各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
EurekaClient通过注册中心进行访问 是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
6.2 Eureka常用属性配置
Spring Cloud Eureka 属性作用
| 配置参数 | 默认值 | 说明 |
|---|---|---|
| 服务注册中心配置 | Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean | |
| eureka.server.enable-self-preservation | false | 关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除 |
| 服务实例类配置 | Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean | |
| eureka.instance.prefer-ip-address | false | 不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了 eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址 |
| eureka.instance.ip-address | IP地址 | |
| eureka.instance.hostname | 设置当前实例的主机名称 | |
| eureka.instance.appname | 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown | |
| eureka.instance.lease-renewal-interval-in-seconds | 30 | 定义服务续约任务(心跳)的调用间隔,单位:秒 |
| eureka.instance.lease-expiration-duration-in-seconds | 90 | 定义服务失效的时间,单位:秒 |
| eureka.instance.status-page-url-path | /info | 状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.status-page-url | 状态页面的URL,绝对路径 | |
| eureka.instance.health-check-url-path | /health | 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.health-check-url | 健康检查页面的URL,绝对路径 | |
| 服务注册类配置 | Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean | |
| eureka.client.service-url. | 指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。 如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码 | |
| eureka.client.fetch-registery | true | 检索服务;false不会向Eureka Server注册中心获取注册信息 |
| eureka.client.registery-fetch-interval-seconds | 30 | 从Eureka服务器端获取注册信息的间隔时间,单位:秒 |
| eureka.client.register-with-eureka | true | 启动服务注册;false不会向Eureka Server注册中心注册自己的信息 |
| eureka.client.eureka-server-connect-timeout-seconds | 5 | 连接 Eureka Server 的超时时间,单位:秒 |
| eureka.client.eureka-server-read-timeout-seconds | 8 | 读取 Eureka Server 信息的超时时间,单位:秒 |
| eureka.client.filter-only-up-instances | true | 获取实例时是否过滤,只保留UP状态的实例 |
| eureka.client.eureka-connection-idle-timeout-seconds | 30 | Eureka 服务端连接空闲关闭时间,单位:秒 |
| eureka.client.eureka-server-total-connections | 200 | 从Eureka 客户端到所有Eureka服务端的连接总数 |
| eureka.client.eureka-server-total-connections-per-host | 50 | 从Eureka客户端到每个Eureka服务主机的连接总数 |
6.3 单机搭建Eureka
1.搭建Eureka
- 建Module
- 改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-java1</artifactId>
<groupId>com.neuedu.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>server-eureka-7001</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>
<!--一般通用配置-->
<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>
</dependencies>
</project>关于Eureka的依赖
<!--以前的老版本(当前使用2018) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!--现在新版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>- 写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/- 主启动
- @EnableEurekaServer
package org.jshand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp7001 {
private static final Logger log = LoggerFactory.getLogger(EurekaServerApp7001.class);
public static void main(String[] args) {
SpringApplication.run(EurekaServerApp7001.class, args);
log.info("Eureka Server Started (EurekaServerApp7001服务端启动成功)");
}
}- 测试
http://localhost:7001/结果页面
如果Idea中没有出现Service视图,找到项目目录.idea>workspace.xml
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
</component>Spring Cloud Eureka 属性作用
| 配置参数 | 默认值 | 说明 |
|---|---|---|
| 服务注册中心配置 | Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean | |
| eureka.server.enable-self-preservation | false | 关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除 |
| 服务实例类配置 | Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean | |
| eureka.instance.prefer-ip-address | false | 不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了 eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址 |
| eureka.instance.ip-address | IP地址 | |
| eureka.instance.hostname | 设置当前实例的主机名称 | |
| eureka.instance.appname | 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown | |
| eureka.instance.lease-renewal-interval-in-seconds | 30 | 定义服务续约任务(心跳)的调用间隔,单位:秒 |
| eureka.instance.lease-expiration-duration-in-seconds | 90 | 定义服务失效的时间,单位:秒 |
| eureka.instance.status-page-url-path | /info | 状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.status-page-url | 状态页面的URL,绝对路径 | |
| eureka.instance.health-check-url-path | /health | 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置 |
| eureka.instance.health-check-url | 健康检查页面的URL,绝对路径 | |
| 服务注册类配置 | Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean | |
| eureka.client.service-url. | 指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。 如果服务注册中心加入了安全验证,这里配置的地址格式为: http://<username>:<password>@localhost:8761/eureka 其中 <username> 为安全校验的用户名;<password> 为该用户的密码 | |
| eureka.client.fetch-registery | true | 检索服务;false不会向Eureka Server注册中心获取注册信息 |
| eureka.client.registery-fetch-interval-seconds | 30 | 从Eureka服务器端获取注册信息的间隔时间,单位:秒 |
| eureka.client.register-with-eureka | true | 启动服务注册;false不会向Eureka Server注册中心注册自己的信息 |
| eureka.client.eureka-server-connect-timeout-seconds | 5 | 连接 Eureka Server 的超时时间,单位:秒 |
| eureka.client.eureka-server-read-timeout-seconds | 8 | 读取 Eureka Server 信息的超时时间,单位:秒 |
| eureka.client.filter-only-up-instances | true | 获取实例时是否过滤,只保留UP状态的实例 |
| eureka.client.eureka-connection-idle-timeout-seconds | 30 | Eureka 服务端连接空闲关闭时间,单位:秒 |
| eureka.client.eureka-server-total-connections | 200 | 从Eureka 客户端到所有Eureka服务端的连接总数 |
| eureka.client.eureka-server-total-connections-per-host | 50 | 从Eureka客户端到每个Eureka服务主机的连接总数 |
2. 将提供者注册到Eureka
- 改POM
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>- 写YML
server:
port: 8081
spring:
application:
name: payment
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
#在注册中心,控制台,访问的时候 地址IP地址(超链接的地址)
prefer-ip-address: true
#主机名字,在列表中使用
hostname: 192.168.77.11
#在eureka列表中查看微服务时使用如下格式
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}- 主启动 @EnableEurekaClient
- 测试 先要启动EurekaServer
http://localhost:7001/微服务注册名配置说明 - 自我保护机制
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用eureka.server.enable-self-preservation=false来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除。
3. 在Eureka中注册消费者
- POM
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>- YML
server:
port: 80
spring:
application:
name: order
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
instance:
#在注册中心,控制台,访问的时候 地址IP地址(超链接的地址)
prefer-ip-address: true
#主机名字,在列表中使用
hostname: 192.168.77.11
#在eureka列表中查看微服务时使用如下格式
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}- 主启动 @EnableEurekaClient
- 测试 先要启动EurekaServer,7001服务 再要启动服务提供者provider,8001服务 eureka服务器
4. 使用Eureka+RestTemplate做服务调用
声明RestTemplate对象
在注入的RestTemplate中声明@LoadBanlance
@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力(默认是ribbon)
@Configuration
public class Appconfig {
@Bean
@LoadBalanced //负载均衡,封装了 负载的策略,
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}服务调用
在调用的时候将ip:port替换成 application.name
@GetMapping("/order/{id}")
CommonResult<Payment> order(@PathVariable("id") String orderId){
//调用支付的接口
String url = OrderConstants.SERVICE_PAYMENT+"/pay/"+orderId;
CommonResult<Payment> result = restTemplate.getForObject(url, CommonResult.class);
return result;
}5.模拟服务集群
上面模拟的是一个服务提供者,为了负载提高系统可用性,此时可以将服务提供者多部署几台,分别上线。
将cloud-provider-payment-8081代码复制一份,修改端口为8082,并添加相同的方法
/**
* http://127.0.0.1:8081/provider/echo/clouds
*/
@Value("${server.port}")
private String port;
@GetMapping("/echo/{param}")
String echo(@PathVariable String param){
return param+"-provider:"+port;
}在服务消费方添加方法调用echo方法
@GetMapping("/echo/{param}")
String echo(@PathVariable String param){
String url = OrderConstants.SERVICE_PAYMENT+"/echo/"+param;
String result = restTemplate.getForObject(url, String.class);
return result;
}通过浏览器 直接访问服务消费者,http://192.168.77.11/consumer/echo/aaa,结果会在8081、和8082之间切换
aaa-provider:8082、aaa-provider:8081
6.4 手动的服务发现Discovery
在App类上添加@EnableDiscoveryClient注解
使用DiscoveryClient手动访问服务;
package com.neuedu.cloud.controller;
import com.netflix.discovery.converters.Auto;
import com.neuedu.cloud.common.OrderConstants;
import com.neueud.cloud.common.CommonResult;
import com.neueud.cloud.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* 项目 spring-cloud-java1
*
* @author 张金山
* @version 1.0
* 说明 订单接口
* @date 2021/6/23 13:36
*/
@RestController
@Slf4j
public class OrderControlelr {
@Autowired
RestTemplate restTemplate; //才有能力将应用的 名字 转换成 实际 ip地址
/**
* http://192.168.77.11/order/506
* @param orderId
* @return
*/
@GetMapping("/order/{id}")
CommonResult<Payment> order(@PathVariable("id") String orderId){
//调用支付的接口
String url = OrderConstants.SERVICE_PAYMENT+"/pay/"+orderId;
CommonResult<Payment> result = restTemplate.getForObject(url, CommonResult.class);
return result;
}
@GetMapping("/echo/{param}")
String echo(@PathVariable String param){
String url = OrderConstants.SERVICE_PAYMENT+"/echo/"+param;
String result = restTemplate.getForObject(url, String.class);
return result;
}
/**
* 手动发现服务
*/
@Autowired
DiscoveryClient discoveryClient; //httpclient
/**
* 手动发现服务
* @return
*/
@GetMapping("/discoveryClient")
String discoveryClient(){
List<String> services = discoveryClient.getServices();
StringBuffer stringBuffer = new StringBuffer();
for (String service : services) {
log.info("服务名称:{}",service);
stringBuffer.append("服务名称:"+service);
List<ServiceInstance> instance = discoveryClient.getInstances(service);
for (ServiceInstance serviceInstance : instance) {
/***
* http://192.168.77.11:80/order/506
*/
log.info("ServiceId:{}, getHost:{},Port :{}",
serviceInstance.getServiceId(),
serviceInstance.getHost(),
serviceInstance.getPort()
);
}
}
return stringBuffer.toString();
}
}6.5 集群搭建Eureka
为了实现Eureka的高可用,搭建Eureka注册中心集群 ,实现负载均衡+故障容错
准备工作
找到C:\Windows\System32\drivers\etc路径下的hosts文件,或者使用swichhosts软件 修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.comC:\Users\Administrator>ping eureka7001.com
正在 Ping eureka7001.com [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
127.0.0.1 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms
C:\Users\Administrator>新建server-eureka-7002
改POM ,跟7001相同
写YML(以前单机)
- 端口
- hostname
- 7001 需要修改defaultZone
- 7002 需要修改defaultZone
在微服务中配置注册到集群中(yml)
ymldefaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版测试,先启动两个Eureka服务
查看两个Eureka控制台,是否存在服务
http://eureka7001.com:7001/、http://eureka7002.com:7002/在启动order、payment三个微服务。
再测试将注册中心的某一个停止,测试远程服务调用是否可用
6.6 自我保护模式
什么会产生Eureka自我保护机制? 为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
什么是自我保护模式? 默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。 它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
客户端
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 30服务端
\# 关闭自我保护机制,保证不可用的服务被及时剔除
enable-self-preservation: false
\# 如果2秒内没有收到某个微服务的心跳,那就剔除该微服务,单位为毫秒
eviction-interval-timer-in-ms: 2000
