序言
前文说过,若一个项目里有过多的微服务,人为管理起来非常麻烦,因此迫切需要能够自动管理微服务的东东,这个东东叫做服务治理框架,它提供了 2 个功能:
- 服务注册:在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
- 服务发现:由于在服务治理框架下运作,服务间的调用不再通过指定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口的时候,并不知道具体的服务实例位置。因此,调用方需要向服务注册中心咨询服务,并获取所有服务的实例清单,以实现对具体服务实例的访问。
服务注册的例子
假设我们有两个提供服务 A 的进程分别运行于192.168.0.100:8000
和192.168.0.101:8000
2 个服务器上,另外还有三个提供服务 B 的进程分别运行于192.168.0.100:9000
、192.168.0.101:9000
、192.168.0.102:9000
3 个服务器上。
当这些服务器的进程均启动,并向注册中心注册自己的服务之后,注册中心就会维护类似下面的一个服务清单。另外,服务注册中心还需要以心跳的方式去监测清单中的服务是否可用, 若不可用则会从服务清单中剔除,达到排除故障服务的效果。
服务名 | 相关实例 |
---|---|
服务A | 192.168.0.100:8000、92.168.0.101:8000 |
服务B | 192.168.0.100:9000、192.168.0.101:9000、192.168.0.102:9000 |
服务发现的例子
假设现有服务 C 希望调用服务 A ,服务 C 就需要向注册中心发起咨询服务请求, 服务注册中心就会将服务 A 的位置清单返回给服务 C , 如按上例服务 A 的情况,C 便获得了服务 A 的两个可用实例192.168.0.100:8000
和192.168.0.101:8000
。
当服务 C 要发起调用的时候,便从该清单中以某种轮询策略取出一个实例来进行服务调用,这就是后续我们将会介绍的客户端负载均衡。
当然,以上仅仅只是一种简单的服务治理逻辑,以方便我们理解服务治理框架的基本运行思路。实际的框架为了性能等因素,不会采用每次都向服务注册中心获取服务的方式, 且不同的应用场景在缓存和服务剔除等机制上也会有一些不同的实现策略。
知道这些概念后,现在来解释下什么是 Eureka。
认识 Eureka
Spring Coud Eureka,它使用 Netflix Eureka 来实现服务的注册与发现,即包含了客户端组件,也包含了服务端组件。服务端与客户端均采用 Java 编写,主要适用于通过 Java 实现的分布式系统。
基础架构
Eureka 基础架构包含三个核心角色:
- 服务注册中心:Eureka 服务端,亦称服务注册中心,同其他服务框架的注册中心一样,支持高可用配置以应对多种不同的故障场景。Netflix 推荐每个可用的区域运行一个 Eureka 服务端凭此形成集群。不同可用区域的服务注册中心通过异步模式互相复制各自的状态, 这意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。
- 服务提供者:Eureka 客户端,提供服务的应用,可以是 Spring Boot 应用,也可以是其他技术平台且遵循 Eureka 通信机制的应用。它将自己提供的服务注册到 Eureka 服务端, 以供其他应用发现;
- 服务消费者:Eureka 客户端,消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务。
注意哦
:很多时候服务提供者同时也是服务消费者。
高可用的 Eureka 服务注册中心(集群)
在微服务架构这样的分布式环境中, 我们需要充分考虑发生故障的情况, 所以在生产环境中必须对各个组件进行高可用部署, 对于微服务如此, 而对于服务注册中心也一样。
EurekaServer 的高可用实际上就是将自己作为服务向其他服务注册中心注册自己, 凭此形成一组互相注册的服务注册中心(集群), 以实现服务清单的互相同步, 达到高可用的效果。
服务治理机制
如上图:
- “服务注册中心 - 1 “ 和 “ 服务注册中心 - 2 “, 它们互相注册组成了高可用集群。
- “服务提供者” 启动了两个实例, 一个注册到 “ 服务注册中心 - 1 “ 上, 另外一个注册到 “服务注册中心 - 2 “ 上。
- 还有两个“服务消费者“,它们也都分别只指向了一个注册中心。
根据上面的结构, 下面详细了解从服务注册开始到服务调用及各个元素所涉及的一 些重要通信行为。
服务提供者
服务提供者有以下行为:
- 服务注册:服务提供者在启动时,会检测属性配置的:
eureka.clicen.register-with-eureka=true
参数是否正确,其默认为true
。若值确实为true
,则会携带着自己的元数据信息(如主机,端口,运行状况指示器 URL ,主页和其他详细信息)向注册中心发起一个 REST 请求,注册中心会将信息保存到一个双层的 Map 结构(Map<String,Map<String,ServiceInstance>>
)中进行维护:- 第一层 Map 的 Key 就是服务 ID,一般是配置的
spring.application.name
属性 - 第二层 Map 的 Key 是服务的实例 ID,一般是
host + serviceId + port
,值则为服务的实例对象,这代表一个服务可以同时启动多个不同实例以形成集群。
- 第一层 Map 的 Key 就是服务 ID,一般是配置的
- 服务同步:如前面图示,两个服务提供者分别注册到了两个不同的服务注册中心上,也就是说, 它们的信息分别被两个服务注册中心所维护。 此时, 由于服务注册中心之间因互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时, 它会将该请求转发给集群中相连的其他注册中心, 从而实现注册中心之间的服务同步 。通过服务同步,两个服务提供者的服务信息就可以通过这两台服务注册中心中的任意一台被服务消费(调用)者获取到。
- 服务续约(
renew
):在注册服务完成以后,服务提供者会维持一个心跳定时向注册中心发起 REST 请求,告诉他:我还活着,防止注册中心的剔除任务将该服务实例从服务列表中排除出去。下面为相关参数:eureka.instance.lease-renewal-interval-in-seconds=30
:服务续约任务的调用间隔时间,默认 30S;eureka.ins七ance.lease-expiration-duration-in-seconds=90
:服务失效的时间,默认 90S
服务消费(调用)者
服务消费者有以下行为:
- 获取服务:当我们启动服务消费者的时候,它会发送一个 REST 请求给服务注册中心,来获取上面注册的服务清单。获取服务是服务消费者的基础,所以必须确保默认的
eureka.client.fetch-registry= true
参数没有被修改成false
。若希望修改缓存清单的更新时间,可以通过eureka.client.registry-fetch-interval-seconds=30
参数进行修改,该参数默认值为 30 ,单位为秒。 - 服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。
- 服务下线:当服务器进行正常关闭操作时,会触发一个服务下线的 REST 请求告诉服务注册中心:我要下线了。服务注册中心收到请求之后,会将该服务置为下线状态(
DOWN
),并把该下线事件传播出去。
服务注册中心
服务注册中心有以下行为:
- 失效剔除:有时候服务可能由于内存溢出或网络故障等原因不能正常工作,而服务注册中心并未收到服务下线的 REST 请求。相对于服务提供者的服务续约操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认 60 秒)将当前清单中超时(默认为 90 秒)没有续约的服务剔除,可以自定义时间,一般默认就好。
- 自我保护:当一个服务停止,Eureka 面板会有一条警告信息触发 Eureka 的自我保护机制。当服务未按时进行心跳续约时,注册中心会统计服务实例最近 15 分钟心跳续约的比例是否低于 85% ,确定是否进行失败剔除操作。可自定义参数
如何使用 Eureka ?
下面根据集成的通用步骤及快速入门来学习如何使用 Eureka。
集成说明
要想使用 Eureka,需要分别配置服务端和客户端。
Eureka 服务注册中心配置
① 创建 Eureka 服务端的项目需要在pom.xml
添加:1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
注意哦
:需要在pom.xml
先引入前置依赖才能使用,多模块项目则可以在父模块添加以下依赖:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
<dependencyManagement>
<dependencies>
<!--springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
② 创建启动类并添加 Eureka 的注解@EnableEurekaServer
,其可以启动一个服务注册中心提供给其他应用进行对话,示例代码如下:1
2
3
4
5
6
7
public class EurekaServer {
public static void main(String[] args) {
SpringApplication.run(EurekaServer.class);
}
}
③ 对application.yml
文件进行配置:1
2
3
4
5
6
7
8
9
10
11
12server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
部分重要参数说明一下:
spring.application.name
:代表 Eureka 服务的名字,我们知道若想在某个网站进行注册,则需要提供用户名,所以向 Eureka 注册中心注册也需要提供客户端的名字,因此对相关应用也需取名;eureka.client.service-url
:Eureka 一般都会以集群方式运行,一个 Eureka 既是服务器也是客户端,因为它们需要相互检测以防止故障问题。因此,在默认设置下,Eureka 服务注册中心也会将自己作为客户端来尝试注册它自己,源码中默认的注册地址defaultZone
为http://localhost:8761/eureka
,这里我们可以重写为自己设置的端口10086
。eureka.client.instance.
:prefer-ip-address
:指定 Eureka 实例是否使用指定 IP,默认false
ip-address
:指定 Eureka 实例设置的具体 IP。
为什么要设置 Eureka 实例的 IP 呢?
这是因为:默认联网情况下, Eureka 启动时使用当前网络分配的 IP,而我们测试的 IP 应为当前主机。不指定也行,但需断网测试。
现在尝试访问一下http://127.0.0.1:10086/
,效果如下图:
注意哦
:启动过程中,日志存在报错,这代表 Eureka 客户端正在注册 Eureka 服务端,而服务端才刚刚启动,需要心跳检测 30 S 后才注册成功,因此很正常。
客户(应用)注册 Eureka 配置
当 Eureka 客户端向 Eureka 注册中心注册时,它会提供有关自身的元数据信息给注册中心,Eureka 接收信息后维持心跳。心跳故障若超过配置时间,注册中心表中该实例将被剔除。
① 对一个客户(应用)可以加入下面的依赖来使其成为一个 Eureka 客户端(需前置依赖详见上文):1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
② 在启动程序中声明该应用是 Eureka 客户端,加入@EnableEurekaClient
注解可以表示使用 Eureka 客户端,但我们一般会使用更为强大@EnableDiscoveryClient
注解,毕竟服务框架不止 Eureka 一种,而该注解对其他服务框架也兼容,示例代码如下:1
2
3
4
5
6
7
8
"cn.wk.mapper") (
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
③ 和服务端一样,对application.yml
文件的配置差不多,让其注册到注册中心就好了:1
2
3
4
5
6
7
8
9server:
port: 8080
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
应用到底是服务提供者还是消费者取决于你如何调用。
快速入门(原有项目改造)
现在我们改造原有 demo,来学习一下 Eureka.
① 创建子模块并添加依赖
首先,我们创建一个子模块eureka
,加入以下依赖:1
2
3
4
5
6<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
② 创建启动类
1 |
|
③ 修改配置文件
1 | server: |
④ 修改另外的两个子项目
首先在user-service
服务提供者和consumer
服务消费者模块的pom.xml
文件都添加以下依赖:1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
第二步:在相关启动类上添加@EnableDiscoveryClient
注解;
第三步:修改application.yml
文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# user-service 模块添加
spring:
application:
name: user-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
# consumer 模块添加
spring:
application:
name: consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
第四步:修改consumer
模块的controller
层,将原先的硬代码修改:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"consumer") (
public class ConsumerController {
private RestTemplate restTemplate;
private DiscoveryClient discoveryClient;
"{id}") (
public User queryById(@PathVariable("id") Integer id) {
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = instances.get(0);
return restTemplate.getForObject("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id, User.class);
}
}
注入的DiscoveryClient
可以获取服务注册中心的服务实例的信息。
⑤ 启动项目并测试
当我们将 3 个模块都启动后,通过http://localhost:8080/consumer/1
可以获取到下面的 JSON 数据:1
{"id":1,"username":"lucy","password":"123","name":"章总","telephone":0}
这个数据调用过程中发生了什么呢?
首先,consumer
服务消费者模块现在不是直接访问user-service
服务提供者模块,而是先去访问eukera-server
服务注册中心,要求相应的服务。
当服务注册中心收到consumer
服务消费者模块的要求:user-service
在家吗?我想找他拿点东西。
服务注册中心一看,自己维护的列表中确实存在user-service
模块,因此告诉consumer
模块:user-service
在家呢!进来找他吧!
最后,consumer
通过user-service
得到了自己想要的东西。
参考
- Spring Cloud 官方文档
- 翟永超. Spring Cloud 微服务实战 [M]. 电子工业出版社,2017