如果直接在服务消费者项目指定服务提供者地址的话,会带来很多问题。比如
- 服务提供者的网络地址(IP和端口)发生了变化,服务消费者无法自动感知,需要手动修改并重新部署。
- 无法动态增减每个微服务的集群节点。
- 服务之间调用混乱,难于管理。
服务发现组件提供了解决以上问题的能力。
一、服务发现简介
- 服务提供者、服务消费者、服务发现组件这三者的关系大致如下:
- 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息。
- 服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口。
- 各个微服务与服务发现组件使用一定机制(例如心跳)通信。服务发现组件如长时间无法与某微服务实例通信,就会注销该实例。
- 微服务网络地址发生变更(例如实例增减或者IP端口发生变化等)时,会重新注册到服务发现组件。使用这种方式,服务消费者就无需人工修改提供者的网络地址了。
综上,服务发现组件应该具备以下功能:
- 服务注册表:是服务发现组件的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销。
- 服务注册与服务发现:服务注册是指微服务在启动时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制。
- 服务检查:服务发现组件使用一定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表中移除该实例。
服务发现组件的其它叫法:服务注册、服务发现或注册中心。Spring Cloud提供了多种服务发现组件的支持,例如Eureka、Consul和Zookeeper等。
二、Eureka简介
Eureka是Netflix开源的服务发现组件,本身是一个基于REST的服务。它包含Server和Client两部分。
PS:Eureka 2.x已宣布闭源,不过可能是临时的。
三、Eureka原理
![](https://i-blog.csdnimg.cn/blog_migrate/759a54abea6944721ef5f61c92461c51.png)
- Application Service:相当于服务提供者
- Application Client:相当于服务消费者
- Make Remote Call:可理解成调用RESTful API的行为
Eureka包含两个组件:Eureka Server和Eureka Client,它们的作用如下:
- Eureka Server提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息(例如IP、端口、微服务名称等)。Eureka Server会存储这些信息。
- Eureka Client是一个Java客户端,用于简化与Eureka Server的交互。
- 微服务启动后,会周期性(默认30秒)地向Eureka Server发送心跳以续约自己的“租期”。
- 如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例。
- 默认情况下,Eureka Server同时也是Eureka Client。多个Eureka Server实例,互相之间通过复制的方式,来实现服务注册表中数据的同步。
- Eureka Client会缓存服务注册表中的信息。这种方式有一定的优势——首先,微服务无需每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次,即使Eureka Server所有节点都宕掉,服务消费者依然可以使用缓存的信息找到服务提供者并完成调用。
Eureka通过心跳检查、客户端缓存等机制,提供了系统的灵活性、可伸缩性和可用性。
四、Eureka的使用
1、编写Eureka Server
1)新建maven项目microservice-eureka,pom.xml文件引入spring boot、spring cloud和eureka-server的相关依赖。
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zhh</groupId>
<artifactId>microservice-eureka</artifactId>
<version>1.0</version>
<!-- Spring Boot依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- Spring Cloud管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)在application.yml加上以下配置。
## 服务端口
server:
port: 8080
eureka:
instance:
hostname: localhost
client:
## 因为该应用为注册中心,所以设置为false,代表不向注册中心注册自己
registerWithEureka: false
## 因为该应用为注册中心,职责就是维护服务实例信息,并不需要去检索服务,所以设置为false。
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3)启动类加上EnableEurekaServer注解。
@SpringBootApplication
@EnableEurekaServer
public class MicroserviceEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceEurekaApplication.class, args);
}
}
4)启动项目,浏览器访问http://localhost:8080/,看到以下页面说明Eureka服务端运行成功。
2、将微服务注册到Eureka Server
1)新建maven项目microservice-provider,pom.xml文件引入spring boot、spring cloud和eureka-server的相关依赖。
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zhh</groupId>
<artifactId>microservice-provider</artifactId>
<version>1.0</version>
<!-- Spring Boot依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- Spring Cloud管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring-boot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)在application.yml加上以下配置。主要是应用名(用于标记服务)、要注册到的Eureke注册中心地址。
server:
port: 8081
spring:
application:
name: provider-pay
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8080/eureka/
3)编写一个支付API的控制器。
@RestController
public class PayController {
@GetMapping("/pay")
public String pay() {
return "支付功能调用成功!";
}
}
4)在项目启动类加上EnableDiscoveryClient注解。
@SpringBootApplication
@EnableDiscoveryClient // 相当于服务发现组件的门面,也可以使用@EnableEurekaClient
public class MicroserviceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceProviderApplication.class, args);
}
}
5)启动应用,可以看到服务已经注册到Eureka Server上了。
五、Eureka Server的高可用
Eureka Client会定时连接Eureka Server,获取服务注册表中的信息并缓存在本地。微服务在消费远程API时总是使用本地缓存中的数据。因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。但如果Eureka Server宕机时,某些微服务也出现了不可用的情况,Eureka Client中的缓存若不被更新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用性。因此,通常会部署一个高可用的Eureka Server集群。
Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册时Eureka Server的默认行为。
下面来构造一个双节点Eureka Server集群(3个节点、4个节点...也是类似的,只要将自己的节点都注册到别的节点上就可以了)。
1)配置系统的hosts,Windows系统的hosts文件路径是C:\Windows\System32\drivers\etc\hosts,Linux及Mac OS等系统的文件路径是/etc/hosts。
127.0.0.1 ha1 ha2
2)复制项目microservice-eureka两份,将artifactId分别修改为microservice-eureka-ha1和microservice-eureka-ha2。
3)分别将application.yml修改如下,让两个节点的Eureka Server相互注册。
## 服务端口
server:
port: 9081
eureka:
instance:
hostname: ha1
client:
## 因为该应用为注册中心,所以设置为false,代表不向注册中心注册自己
registerWithEureka: false
## 因为该应用为注册中心,职责就是维护服务实例信息,并不需要去检索服务,所以设置为false。
fetchRegistry: false
serviceUrl:
defaultZone: http://ha2:9082/eureka/
## 服务端口
server:
port: 9082
eureka:
instance:
hostname: ha2
client:
## 因为该应用为注册中心,所以设置为false,代表不向注册中心注册自己
registerWithEureka: false
## 因为该应用为注册中心,职责就是维护服务实例信息,并不需要去检索服务,所以设置为false。
fetchRegistry: false
serviceUrl:
defaultZone: http://ha1:9081/eureka/
4)启动两个项目,分别访问http://localhost:9081/和http://localhost:9082/,都可以看到registered-replicas拥有对方节点。
5)将应用注册到Eureka Server集群上。
以microservice-provider项目为例, 只需修改eureka.client.serviceUrl.defaultZone,配置多个Eureka Server地址,就可以将其注册到Eureka Server集群了。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9081/eureka/,http://localhost:9082/eureka/
其实,微服务即使只配置Eureka Server集群中的某个节点,也能正常注册到Eureka Server集群,因为多个Eureka Server之间的数据会相互同步。不过,避免某些极端场景,还是建议在客户端配置多个Eureka Server节点。
六、Eureka的自我保护模式
默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka就会保护服务注册表中的信息,不会删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动推出自我保护模式。
可以将eureka.server.enable-self-preservation设置为false禁用自我保护模式(默认为true),一般在开发环境中使用。
七、多网卡环境下的IP选择
Spring Cloud提供了按需选择IP的能力。
1、忽略指定名称的网卡
spring:
cloud:
inetutils:
ignored-interfaces:
- docker0
- vetch.*
这样就可以忽略docker0网卡以及所有以vetch开头的网卡。
2、使用正则表达式,指定使用的网络地址
spring:
cloud:
inetutils:
preferred-networks:
- 192.168
- 10.0
3、只使用站点本地地址
spring:
cloud:
inetutils:
use-only-site-local-interfaces: true
这样就可以强制使用站点本地地址。
4、手动指定IP地址
eureka:
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
八、Eureka的健康检查
在Eureka Server首页,可以看到以下信息
在Status一栏有个UP,表示应用程序状态正常。应用状态还有其它取值,例如DOWN、OUT_OF_SERVICE、UNKNOWN等。只有标记为“UP”的微服务会被请求。