服务发现概述
-
服务发现机制是为了解决硬网络编码问题,服务消费者使用这种机制获取服务提供者网络信息,当微服务网络地址发生变更(例如IP或端口),会重新注册到服务发现组件,而服务消费者就无须人工修改网络地址了。
- 服务提供者、服务消费者、服务发现之间的关系如下:
- 微服务在启动时,将自己的网络地址等信息注入到服务发现组件中,服务发现组件会存储这些信息
- 服务消费者可以从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口
- 各个微服务与服务发现组件使用一定的机制(心跳)通信,服务组件如长时间无法与某微服务实例通信,就会注销该实例
- 服务发现组件功能:
-
服务注册表:是服务发现组件的核心,用来记录微服务信息,如名称、IP、端口等,提供查询和管理API,注册和注销
-
服务注册与服务发现:服务注册是指服务在启动时将自己信息发送到服务注册组件上,服务发现是指查询可用微服务列表及网络地址的机制
-
服务检查:服务发现组件检测已注册的服务,若某一个服务长时间无法访问则移除该实例
Eureka
- Eureka 是开源的服务发现组件,本身是一个基于 REST 的服务,包含 Server 和 Client 两部分
- Region:表示服务系统中的地理位置,Spring Cloud默认使用的是 us-east-1
- Availbility Zone:可理解为机房,Region理解为跨机房的 Eureka 集群
- Application Service:服务提供者
- Application Client:服务消费者
- Make Remote Call:如调用 RESTful API
- us-east-1c、us-east-1d、us-east-1e都是zone,都属于 us-east-1 这个region
- Eureka Server:提供服务发现能力,各个微服务启动时,会向 Eureka Server 注册自己的信息,Eureka Server 会保存这些信息
- Eureka Client:是一个 Java 客户端,用于简化与Eureka Server的交互
- 微服务启动后,会周期性(默认30秒)的与 Eureka Server 发送心跳信息
- 如果 Eureka Server 在一定时间内没接收到心跳信息则注销该实例(默认90秒)
- 多个 Eureka Server 之间通过复制方式,来实现服务注册表中数据的同步
- Eureka Client 会缓存注册表中信息,微服务无需每次请求都查询 Eureka Server,降低了压力
快速入门
编写 Eureka Server (Demo源码 eureka)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 添加 @EnableEurekaServer注解声明这是一个 Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
server:
port: 8761 #配置端口
eureka:
client:
register-with-eureka: false #是否向服务端注册自己,它本身就是Eureka Server,所以为false
fetch-registry: false #表示是否从Eureka Server获取信息,因为这是一个单节点,不需要同步其它Eureka Server的数据,所以为 false
service-url:
defaultZone: http://localhost:8761/eureka/ #设置 Eureka Client 与 Eureka Server 同步的地址,注册、查询服务都要使用该地址,多个地址可用逗号分隔
将微服务注册到 Eureka Server
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
- 添加 @EnableDiscoverClient 注解声明这是一个微服务
@SpringBootApplication
@EnableEurekaClient
public class FlimUserApplication {
public static void main(String[] args) {
SpringApplication.run(FlimUserApplication.class, args);
}
}
spring:
application:
name: flim-user #注册到Eureka的名字
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/ #设置EurekaServer的服务地址
instance:
prefer-ip-address: true #true代表将自己的IP注册到EurekaServer上,false代表注册主机名到EurekaServer
Eureka高可用
- 单节点 Eureka Server 并不适合生产环境,Eureka Client 会定时连接 Eureka Server,获取服务注册表中的信息并缓存在本地,微服务消费远程 API 时总是使用本地缓存中的数据。因此即使 Eureka Server 发生故障也不会影响到服务之间的调用,但如果 Eureka Server 故障时,某些微服务也出现了不可用的情况,Eureka Client 中的缓存若不被更新,则可能会影响到微服务之间的调用
- 因此在生产环境中,通常会部署一个高可用的 Eureka Server 集群,Eureka Server 可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server 之间也会彼此增量的同步信息,从而确保所有节点数据一致,节点之间相互注册也是 Eureka Server 的默认行为
构建双结点的 Eureka Server 集群
- 修改 Eureka Server 配置文件,添加两个 Eureka 环境
spring:
application:
name: eureka
---
spring:
profiles: eureka1
eureka:
instance:
hostname: eureka1
client:
service-url:
default-zone: http://localhost:8762/eureka
---
spring:
profiles: eureka2
eureka:
instance:
hostname: eureka2
client:
service-url:
default-zone: http://localhost:8761/eureka
- 执行以下命令启动两个 Eureka Server 实例,可看到两个实例之间已同步(注意:先后运行实例后,要等待一会才会同步成功)
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active = eureka1 --server.port=8761
java -jar eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active = eureka2 --server.port=8762
将应用注册到 Eureka Server 集群上
- 只需配置多个 Eureka Server 地址,就可以将其注册到Eureka Server集群了
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
- 即使只配置一个 Eureka Server 集群中的某个节点,也能注册到集群上,因为多个Eureka Server之间数据会同步
Eureka Server 安全
为 Eureka Server 添加用户认证
- 前面例子中 Eureka Server 允许匿名访问,可以构建一个需要登录才能访问的 Eureka Server,这需要添加 Spring Security 库
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
spring:
security:
user:
name: user
password: 123456
- 访问 http:localhost:8761/ 时会提示身份验证对话框
将微服务注册到需要认证的 Eureka Server
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka/
- 对于更复杂的需求可以创建一个类型为 DiscoveryClientOptionalArgs 的 @Bean,并向其中注入 ClientFilter
Eureka 元数据
- Eureka 元数据有两种,标准元数据和自定义元数据
-
标准元数据:指主机名、IP地址、端口号和健康检查信息等,这些信息都会被发现服务注册表中,用于服务之间调用
-
自定义元数据:可以用 eureka.instance.metadata-map 配置,这些元数据可以在远程客户端中访问,但一般不会改变远程客户端行为,除非客户端知道该元数据的含义
自定义元数据-服务提供者
- 修改配置文件,使用 eureka.instance.metadata-map 添加自定义元数据
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka/
instance:
prefer-ip-address: true
metadata-map:
my-metadata: 自定义的元数据
自定义元数据-服务消费者
- 修改电影微服务 MovieController 获取自定义元数据
@RestController
public class MovieController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/user-instance")
public List<ServiceInstance> showInfo(){
return this.discoveryClient.getInstances("flim-user"); //获取Flim-User微服务实例的信息
}
}
[
{
"host": "192.168.43.43",
"port": 8080,
"metadata": {
"management.port": "8080",
"my-metadata": "自定义的元数据"
},
"secure": false,
"serviceId": "FLIM-USER",
"uri": "http://192.168.43.43:8080",
"instanceInfo": {
"instanceId": "linyuandembp:flim-user",
"app": "FLIM-USER",
"appGroupName": null,
"ipAddr": "192.168.43.43",
"sid": "na",
"homePageUrl": "http://192.168.43.43:8080/",
"statusPageUrl": "http://192.168.43.43:8080/info",
"healthCheckUrl": "http://192.168.43.43:8080/health",
"secureHealthCheckUrl": null,
"vipAddress": "flim-user",
"secureVipAddress": "flim-user",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "192.168.43.43",
"status": "UP",
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1513418181530,
"lastRenewalTimestamp": 1513418181530,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1513418181530
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"management.port": "8080",
"my-metadata": "自定义的元数据"
},
"lastUpdatedTimestamp": 1513418181530,
"lastDirtyTimestamp": 1513418181485,
"actionType": "ADDED",
"asgName": null,
"overriddenStatus": "UNKNOWN"
}
}
]
Eureka Server 的 REST 端点
- Eureka Server 提供了一些 REST 端点,非 JVM 的微服务可以使用这些 REST 端点操作 Eureka,从而实现服务注册与发现,其中:
- appID:应用程序的名称
- instanceID:与实例相关联的唯一ID
- 使用 REST 请求向 Eureka Server 注册微服务时,需要 POST 一个请求体( JSON 或 XML 格式,请求题略长,此处不展示)
Eureka 自我保护模式
- Eureka Server 的自我保护模式,最直观的是首页输出的警告
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
- 默认情况下,如果 EurekaServer 在一定时间内没接收到某个微服务的心跳,则会注销该实例,但当网络发生故障,微服务无法与 EurekaServer 正常通信时,该行为就非常危险了,Eureka 通过“自我保护”来解决这个问题,当 Eureka Server 节点短时间内丢失过多客户端时,就会进入自我保护模式,一旦进入该模式,Eureka Server 就会保护服务注册表中的信息,不再删除服务注册表中的信息(不注销任何微服务),当网络恢复后,Eureka Server 退出自我保护模式
多网环境下的IP选择
- 对于多网卡用户,如某台服务器有三块网卡,但只有 eth1 可以被访问,可以通过配置文件来实现
- 使用 spring.cloud.inetutils.ignored-interfaces 属性忽略指定名称的网卡
spring:
cloud:
inetutils:
ignored-interfaces:
- docker0 #忽略docker0网卡
- veth.* #忽略所有veth开头的网卡
- 使用 spring.cloud.inetutils.preferredNetworks 属性指定使用的网络地址
spring:
cloud:
inetutils:
preferred-networks:
- 192.168
- 10.0
Eureka 健康检查
-
在 Status 栏有个 UP 表示应用程序状态正常,还有其它值如:DOWN、OUT_OF_SERVICE、UNKNOWN等,但只有 UP 状态的才会被服务请求,Eureka Server 与 Eureka Client 之间使用心跳机制确定 Eureka Client 状态,正常情况下为 UP
- 该机制不能反应应用程序状态,如微服务与 Eureka Server 心跳正常,可该服务数据源发生问题,无法正常工作
- Spring Boot Actuator 提供了 /health 端点,该端点可展示应用程序健康信息
Eureka 常见问题
Eureka 注册服务慢
- 默认情况下,服务注册到 Eureka Server 的过程较慢,在开发测试时往往希望能快速一点,从而提升工作效率
- 服务的注册涉及到周期性心跳,默认 30 秒一次,只有当实例、服务器端和客户端的本地缓存中的元数据都相同时,服务才能被发现(所以可能需要 3 次心跳)
- 可以使用参数 eureka.instance.leaseRenewalintervalInSeconds 修改时间间隔,单位为秒,从而加快客户端连接到服务的过程
-生产环境最好使用默认值,因为服务器内部有一些计算
已停止的微服务节点注销慢或不注销
- 开发环境下,我们希望能迅速有效的注销已停止的微服务实例,但 Eureka Server 清理无效节点周期(默认 90 秒),以及自我保护等原因,可能回遇到微服务注销慢甚至不注销
- 解决方法如下:
- Eureka Server 端:
eureka.server.enable-self-preservation = false #关闭自我保护
eureka.server.eviction-interval-timer-in-ms = 60000 #清理间隔,单位毫秒
- Eureka Client 端:
eureka.client.healthcheck.enabled = true #开启健康检查
eureka.instance.lease-renewal-interval-in-seconds = 30000 #更新时间,单位毫秒,默认30秒
eureka.instance.lease-expiration-duration-in-seconds = 90000 #到期时间,单位毫秒,默认90秒
如何自定义微服务的 InstanceID
- InstanceID 用于唯一标识到 Eureka Server 上的微服务实例,Eureka Server 首页可以直观的看到各个微服务的 InstanceID
- 在 Spring Cloud 中,服务的 InstanceID 默认值是
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
- 也可以通过下面配置修改 InstanceID 格式
eureka:
instance:
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
Eureka 的UNKNOWN 问题
- 注册信息 UNKNOWN 有两种情况,一种是应用名称 UNKNOWN,一种是应用状态 UNKNOWN
- 应用名称 UNKNOWN 可能是因为未配置 spring.application.name 或 eureka.instance.appname 属性
- 应用状态 UNKNOWN 是微服务不正常情况,该问题一般由健康检查导致,eureka.client.healthcheck.enabled=true 必须设置在 application.yml 中,而不是 bootstrap.yml 中