目录

[TOC]

服务发现组件是微服务架构中非常关键的一个组件。SpringCloud 提供的服务发现有多种,如Eureka,Consul和Zookeeper等。本篇介绍的是Eureka的使用。

服务发现简介

服务提供者,服务消费者,服务发现组件这三者之间的关系大致如下:

服务提供者与服务消费者都需要向服务发现组件进行注册,服务消费者从服务发现组件中获取服务提供者的信息(如名称、地址、端口等)。在服务发现组件注册的微服务需要通过心跳机制来保持连接状态并更新注册信息,当服务发现组件长时间无法与某个微服务实例进行通信时,会注销这个实例。这种机制使得即使服务提供者信息发生变化,服务消费者也无须修改配置文件。

由以上得知,服务发现组件应具备以下功能:

  • 服务注册表:用于记录各个微服务的注册信息,它还提供查询API与管理API。

  • 服务注册与服务发现:服务注册是指微服务在启动是将自己的信息注册到服务发现组件的过程。服务发现是指查询可用的微服务列表及其网络地址的机制。

  • 服务检查:服务发现组件应有一定的机制定时检测已注册的微服务,如长时间无法访问,就会从服务注册表中移除该实例。

Eureka原理

以下是Eureka官方的架构图,比较详细的描述了Erueka集群的工作原理:
Eureka官方架构图
我们可以把us-east-1c、us-east-1d与us-east-1e理解成独立的机房,而这整个图则是一个跨机房的Eureka集群。其中:

  • Application Service 相当于前面说的服务提供者

  • Application Client 相当于前面的服务消费者

  • Make Remote Call 可以理解成调用RESTful API的行为

由于图中所示,我们可以知道Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:

  • Eureka Server提供服务发现的能力

  • Eureka Client 是一个java客户端,用于简化与Eureka Server的交互

  • 微服务在启动后,会同期性(默认30s)地向Eureka Server 发送心跳来续约自己的“租期”

  • 如果Eureka Server 在一定时间内(默认90s)没有接收到某个微服务的实例心跳,Eureka Server将会注销该实例

  • 默认情况下,Eureka Server同时也是Eureka Client。多个Eureka Server 实例,互相之间通过复制的方式,来实现服务注册表中的数据同步

  • Eureka Client 会缓存服务注册表的信息,所以微服务无须每次请求都查询Eureka Server,从而降低了Eureka Server的压力。另外,即使Eureka Server 所以节点都挂了,服务消费者仍然可以使用缓存中的信息找到服务提供者并完成调用

Eureka Server实现

环境

  • Idea
  • Spring Boot :1.5.9.RELEASE
  • Spring Cloud:Edgware.RELEASE
  • JDK :1.8

引入依赖
在Idea新建 一个Spring Boot项目,添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<!--引入Eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

编写启动类
我们需要在启动类上加上 @EnableEurekaServer 注解,声明这是一个Eureka Server

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaServer
public class MicroserviceDiscoveryEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceDiscoveryEurekaApplication.class, args);
}
}

添加配置

1
2
3
4
5
6
7
8
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/

eureka.client.register-with-eureka 表示是否将自己注册到Eureka Server,默认为true,由于当前应用就是Eureka Server,所以设为false
eureka.client.fetch-registry 表示是否从Eureka Server获取注册信息,默认为true,因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,所以设定为false
eureka.client.serviceUrl.defaultZone 设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址,多个地址可以使用 , 分隔

启动项目,访问http://localhost:8761/ 即可看到Eureka Server的首页。可以看到界面展示了实例的状态,可用与不可用的Eureka节点,注册的服务实例列表,常用信息等,当然,目前还没有服务向这个Server注册过,下面记录实现服务注册。

微服务注册

Eureka Server建好后,我们的微服务就可以向这个Eureka Server进行注册了。
我们把之前文章(微服务简单实例–电影购票)中实现的服务提供者修改一下。
添加依赖

1
2
3
4
5
<!--注册Eureka Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

修改配置
在之前项目配置的基础上添加以下配置

1
2
3
4
5
6
7
8
9
spring:
application:
name: microservice-provider-user
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka/
instance:
prefer-ip-address: true

eureka.client.instance.prefer-ip-address表示将自己的IP注册到Eureka Server
spring.application.name 是指定一个应用名称
eureka.client.serviceUrl.defaultZone 设置Eureka Server的地址,多个Eureka地址使用,分隔

修改启动类
同样的,在微服务的启动类上加上一个注解 @EnableDiscoveryClient 表示这是一个Eureka Client。上面也说到Eureka 包含Server 与 Client两个组件,Client本身是一个JAVA客户端,把它集成到微服务中,简化了微服务与Eureka Server 的交互。

1
2
3
4
5
6
7
8
@EnableDiscoveryClient
@SpringBootApplication
public class MicroserviceSimpleProviderUserApplication {

public static void main(String[] args) {
SpringApplication.run(MicroserviceSimpleProviderUserApplication.class, args);
}
}

完成以上修改后,启动服务提供者项目。刷新Eureka Server可以看到在 Instances currently registered with Eureka 栏目下出现了服务提供者的名称,地址,状态等信息。
注册成功
这样,一个微服务就注册到Eureka Server上了。另外如果是非JAVA服务注册到Eureka Server,可以使用Eureka的api进行注册。

Erueka高可用部署

前面我们写了一个Eureka Server,并将一个微服务注册到了Eureka Server,在实际环境中,Eureka 需要是一个高可用的集群环境。这样Eureka Server 宕机时,其它的Eureka节点还是能够继续提供服务,虽然Eureka Client有缓存注册表信息,也可以提供服务查询,但缓存不及时更新也会影响之后的服务调用。
前面在编写Eureka Server时配置了 eureka.client.register-with-eureka=falseeureka.client.fetch-registry=false,现在在多节点的环境中,要实现Eureka实例之间相互注册彼此增量地同步信息,才能确保各节点数据一致,实现Eureka的高可用部署。所以下面的集群环境中,这两个配置应该为 true 或不配置默认为 true
修改hosts
因为是在本机实现多节点的Eureka Server集群,需要修改一下系统的hosts文件,添加以下配置:

1
127.0.0.1  peer1 peer2 peer3

如修改hosts无法保存,需要添加用户修改权限。
添加成功后可以ping peer1试试是否配置生效。

修改配置
将上面编写的Eureka Server 项目的application.yml修改如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
spring:
application:
name: microservice-discovery-eureka
---
spring:
profiles: peer1 # 指定profile=peer1
server:
port: 8761
eureka:
instance:
hostname: peer1 # 指定当profile=peer1时,主机名是peer1
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/,http://peer3:8763/eureka/ # 将自己注册到peer2和peer3这两个Eureka上面去
register-with-eureka: true
fetch-registry: true
---
spring:
profiles: peer2
server:
port: 8762
eureka:
instance:
hostname: peer2
prefer-ip-address: false
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer3:8763/eureka/
register-with-eureka: true
fetch-registry: true

---
spring:
profiles: peer3
server:
port: 8763
eureka:
instance:
hostname: peer3
prefer-ip-address: false
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/
register-with-eureka: true
fetch-registry: true

YAML 文件可以由一或多个文档组成(即相对独立的组织结构组成),文档间使用---(三个横线)在每文档开始作为分隔符。同时,文档也可以使用...(三个点号)作为结束符(可选)。
所以这里的配置文件实际上是三个配置文件组成,---分隔符不可少,否则编译出错。也可以把这几段配置写在三个YAML文件中。

启动
启动项目时通过给spring.profiles.active 传递不同的参数,以使用不同的配置。
在IDEA中我们可以在启动前配置参数:
这里写图片描述
添加三个Spring Boot Application,并选择启动类,在Active Profiles中分别配置peer1,peer2,peer3参数后,分别启动。
在启动时,先启动成功的Eureka 可能会报以下错误:

​ 2018-07-05 14:32:10.983 ERROR 13768 — [-target_peer2-8] c.n.e.cluster.ReplicationTaskProcessor : Network level connection to peer peer2; retrying after delay
​ com.sun.jersey.api.client.ClientHandlerException: java.net.SocketTimeoutException: Read timed out

这是因为先启动成功的peer1根据配置会向peer2和peer3进行注册请求,而此时这两个应用可能还没启动好,所以出现上面的错误,三个应用启动成功后就正常了。
正常情况下如下图:
Eureka集群
显示了注册到该Eureka的实例信息,Eureka各节点地址。
在下面可以看到显示了两个注册的节点,同时这两个节点是可用的。
我之前测试时出现不可用(unavailable-replicas)的节点,如下:
不可用Eureka
解决情况是将 eureka.instance.prefer-ip-address 设置为 false ,因为是在本地配置集群环境,IP是相同的,因此使用IP注册可能会导致节点不可识别,另外,三个应用的spring.application.name不一致也会出现 unavailable-replicas 的情况

服务注册到Erueka 集群
前面我们将服务提供者注册到了一个单节点的Eureka Server ,在Eureka的集群环境中,只需要稍加修改即可。把服务提供者的 eureka.client.serviceUrl.defaultZone配置修改如下:

1
2
3
4
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/,http://peer3:8763/eureka/

其实可以继续使用localhost,因为是在本地测试。
另外,我们可以只写一个Eureka Server的地址,因为各Eureka Server节点之间会自动同步注册的实例信息,正常情况下这两种方式是一样。建议全部写上。

启动服务提供者,刷新三个Eureka Server可以看到服务已经注册了:
集群注册

Eureka用户认证

在生产环境中Eureka Server需要登录才能访问,Eureka的用户认证可以使用Security实现。下面稍微修改一下Eureka Server
添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加配置
添加以下配置在第一段中,作为三个Eureka Server的公共配置。

1
2
3
4
5
6
security:
basic:
enabled: true #开启HTTP basic的认证
user:
name: user #配置登录账号证
password: admin #配置登录密码

最后把Eureka以及服务提供者的配置文件中的 defaultZone 地址修改成如下格式:

http://user:admin@peer1:8761/eureka/

这样才能注册到需要认证的Eureka Server。

重新启动Eureka Server和服务提供者后,再次访问三个Eureka可以看到弹出了登录框,输入上面的账号与密码后登录后即可进行Eureka Server首页,服务已正常注册成功。

Eureka自我保护模式

在实现上面的练习过程中,打开Eureka可能会遇到如下情况:
Eureka自我保护模式
这是Eureka开启自我保护模式时的警告。
前面说过Eureka Server在一定时间内没有接收到某个微服务实例心跳,Eureka Server将会注销该实例。但如果出现网络分区故障,微服务与Eureka Server之间无法正常通信,微服务其实是正常的,但由于无法接收到该微服务的通信,Eureka Server则会注销该服务。
为了避免这种情况的发生,Eureka Server 通过 自我保护模式 来解决这个问题。当Eureka Server在短时间内丢失过多客户端时,那么这个Eureka节点会进行自我保护模式。Eureka 会保护服务注册表中的信息,不再删除服务注册表中的数据,当网络故障恢复后,该Eureka节点会自动退出自我保护模式。
这种模式使得Eureka 集群更加健壮,稳定。
另外也可以选择禁用该模式,配置如下:

​ eureka.server.enable-self-preservation = false

Eureka健康检查

登录Eureka首页可以看到注册的微服务状态是 UP
微服务状态
表示应用程序状态正常,应用的状态还有其它取值,如DOWN,OUT_OF_SERVICE,UNKNOWN等。只有标记为 UP 的微服务会被请求。
默认情况下,服务器端与客户端的心跳保持正常,应用程序状态就会显示UP 状态。但这个机制并不能完全反应微服务的状态,举个栗子,微服务与Eureka Server心跳正常,但微服务的数据源出现了问题(如数据库被关闭)。这个微服务其实无法正常工作,但Eureka Server认为该微服务为UP状态。
为了解决这个问题,我们可以把微服务的健康状态传播到Eureka Server,只需要在微服务中作如下配置:

1
2
3
4
eureka:
client:
healthcheck:
enabled: true

这样,当微服务健康状态出问题时,Eureka Server能够实时反应其它真实状态。

参考:官方API文档

以上,为《Spring Cloud与Docker微服务架构实战》第4章学习笔记。