3.1 服务注册于发现
由于Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、Zookeeper。在Spring Cloud服务治理抽象层的作用下,我们可以无缝地切换服务治理实现,并且不影响任何其他的服务注册、服务发现、服务调用等逻辑。
所以,下面我们通过介绍两种服务治理的实现来体会Spring Cloud这一层抽象所带来的好处。
Spring Cloud Eureka
Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
创建“服务注册中心”
创建一个spring cloud 子模块 Spring Boot工程,命名为eureka-server,并在build.gradle中引入需要的依赖内容:
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties配置文件中增加如下信息:
# 指定服务名称
spring.application.name=eureka-server
# 服务端口
server.port=8001
#当前实例的主机名称
# 服务环境
# spring.profiles.active=peer1
#当前实例的主机名称
eureka.instance.hostname=peer1
#false代表不向注册中心注册自己(因为本身就是注册中心),默认是true,如果不设为false,启动会报找不到注册中心的错误
eureka.client.register-with-eureka=false
#注册中心用于维护服务实例,无需检索服务,故设为false
eureka.client.fetch-registry=false
#指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为defaultZone;默认的Value为http://localhos:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
eureka.client.service-url.defaultZone=http://peer2:8002/eureka/
# 读取每个注册中心节点超时时间
eureka.server.peer-node-read-timeout-ms=2000
# 本地调试关闭自我保护机制
eureka.server.enable-self-preservation=false
为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1001。启动工程后,访问:http://localhost:8001/ 可以看到下面的页面,其中还没有发现任何服务。
创建“服务提供方”
下面我们创建提供服务的客户端,并向服务注册中心注册自己。本文我们主要介绍服务的注册与发现,所以我们不妨在服务提供方中尝试着提供一个接口来获取当前所有的服务信息。
首先,创建一个基本的Spring Boot应用。命名为eureka-client. 在 build.gradle 中添加
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
}
其次,实现/dc请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。
@RestController
public class DcController {
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
}
最后在应用主类中通过加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现,这样才能实现Controller中对服务信息的输出。
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:
spring.application.name=eureka-client
server.port=2001
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。
启动该工程后,再次访问:http://localhost:1001/ 可以如下图内容,我们定义的服务被成功注册了。
当然,我们也可以通过直接访问eureka-client服务提供的/dc接口来获取当前的服务清单,只需要访问:http://localhost:2001/dc 我们可以得到如下输出返回:
3.2 服务消费者
服务发现与消费
使用LoadBalancerClient
在Spring Cloud Commons中提供了大量的与服务治理相关的抽象接口,包括DiscoveryClient、这里我们即将介绍的LoadBalancerClient等。对于这些接口的定义我们在上一篇介绍服务注册与发现时已经说过,Spring Cloud做这一层抽象,很好的解耦了服务治理体系,使得我们可以轻易的替换不同的服务治理设施。
从LoadBalancerClient接口的命名中,我们就知道这是一个负载均衡客户端的抽象定义,下面我们就看看如何使用Spring Cloud提供的负载均衡器客户端接口来实现服务的消费。
下面的例子,我们将利用上一篇中构建的eureka-server作为服务注册中心、eureka-client作为服务提供者作为基础。
- 我们先来创建一个服务消费者工程,命名为:eureka-consumer。并在pom.xml中引入依赖(这里省略了parent和dependencyManagement的配置):
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <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> </dependencies>
- 配置application.properties,指定eureka注册中心的地址:
spring.application.name=eureka-consumer server.port=2101 eureka.client.serviceUrl.defaultZone=http://localhost:1001/eureka/
- 创建应用主类。初始化RestTemplate,用来真正发起REST请求。@EnableDiscoveryClient注解用来将当前应用加入到服务治理体系中。
@EnableDiscoveryClient @SpringBootApplication public class Application { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }
- 创建一个接口用来消费eureka-client提供的接口:
@RestController public class DcController { @Autowired LoadBalancerClient loadBalancerClient; @Autowired RestTemplate restTemplate; @GetMapping("/consumer") public String dc() { ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/dc"; System.out.println(url); return restTemplate.getForObject(url, String.class); } }
可以看到这里,我们注入了LoadBalancerClient和RestTemplate,并在/consumer接口的实现中,先通过loadBalancerClient的choose函数来负载均衡的选出一个eureka-client的服务实例,这个服务实例的基本信息存储在ServiceInstance中,然后通过这些对象中的信息拼接出访问/dc接口的详细地址,最后再利用RestTemplate对象实现对服务提供者接口的调用。
在完成了上面你的代码编写之后,读者可以将eureka-server、eureka-client、eureka-consumer都启动起来,然后访问http://localhost:2101/consumer ,来跟踪观察eureka-consumer服务是如何消费eureka-client服务的/dc接口的。
3.3 构建高可用服务注册中心
构建高可用注册中心
Eureka Server除了单点运行之外,还可以通过运行多个实例,并进行互相注册的方式来实现高可用的部署,所以我们只需要将Eureke Server配置其他可用的serviceUrl就能实现高可用部署。
下面以Chapter1-1-1中的eureka-server为基础,对其改造,构建双节点的服务注册中心。
- 创建application-peer1.properties,作为peer1服务中心的配置,并将serviceUrl指向peer2
spring.application.name=eureka-server server.port=1111 eureka.instance.hostname=peer1 eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
- 创建application-peer2.properties,作为peer2服务中心的配置,并将serviceUrl指向peer1
spring.application.name=eureka-server server.port=1112 eureka.instance.hostname=peer2 eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
- 在/etc/hosts文件中添加对peer1和peer2的转换
127.0.0.1 peer1 127.0.0.1 peer2
- 通过spring.profiles.active属性来分别启动peer1和peer2
java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer1 java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer2
- 此时访问peer1的注册中心:http://localhost:1111/ 下图所示,我们可以看到registered-replicas中已经有peer2节点的eureka-server了。同样地,访问peer2的注册中心:http://localhost:1112/ 看到registered-replicas中已经有peer1节点,并且这些节点在可用分片(available-replicase)之中。我们也可以尝试关闭peer1,刷新http://localhost:1112/ 以看到peer1的节点变为了不可用分片(unavailable-replicas)。
深入理解
虽然上面我们以双节点作为例子,但是实际上因负载等原因,我们往往可能需要在生产环境构建多于两个的Eureka Server节点。那么对于如何配置serviceUrl来让集群中的服务进行同步,需要我们更深入的理解节点间的同步机制来做出决策。
Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。什么意思呢?不妨我们通过下面的实验来看看会发生什么。
- 场景一:假设我们有3个注册中心,我们将peer1、peer2、peer3各自都将serviceUrl指向另外两个节点。换言之,peer1、peer2、peer3是两两互相注册的。启动三个服务注册中心,并将compute-service的serviceUrl指向peer1并启动,可以获得如下图所示的集群效果。
访问http://localhost:1112/,可以看到3个注册中心组成了集群,compute-service服务通过peer1同步给了与之互相注册的peer2和peer3。
通过上面的实验,我们可以得出下面的结论来指导我们搭建服务注册中心的高可用集群:
- 两两注册的方式可以实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现
服务注册与发现
在设置了多节点的服务注册中心之后,我们主需要简单需求服务配置,就能将服务注册到Eureka Server集群中。我们以Chapter1-1-1中的compute-service为基础,修改application.properties配置文件:
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
上面的配置主要对eureka.client.serviceUrl.defaultZone属性做了改动,将注册中心指向了之前我们搭建的peer1与peer2。
下面,我们启动该服务,通过访问http://localhost:1111/和http://localhost:1112/ 以观察到compute-service同时被注册到了peer1和peer2上。若此时断开peer1,由于compute-service同时也向peer2注册,因此在peer2上其他服务依然能访问到compute-service,从而实现了高可用的服务注册中心。
3.4 eureka 详解
基础架构
eureka 服务治理的三个核心要素:
- 服务注册中心: eureka 提供的服务端,提供服务注册与发现的功能.
- 服务提供者: 提供服务的应用,可以是 springboot 应用,也可以是其他技术平台且遵循 eureka 通信机制的应用. 它将自己提供的服务注册到 eureka 上,以供其他平台发现.
- 服务消费者: 消费者应用从服务注册中心获取服务列表,从而使消费者知道可以去何处调用其所需要的服务.
很多时候,客户端既是服务提供者也是服务消费者.
服务治理机制
如上图所示,有几个重要的元素:
- "服务注册中心-1"和"服务注册中心-2", 它们相互注册组成了高可用集群.
- "服务提供者"启动了两个实例,一个注册到了"服务注册中心-1"上,另一个注册到"服务注册中心-2"上,.
- 还有两个服务消费者,它们分别指向了一个注册中心.
服务提供者
服务注册
"服务提供者"在启动时会通过发送 REST 请求的方式将自己注册到服务注册中心,同时带上自己的一些元数据信息. 服务注册中心收到这个 REST 请求后,将元数据信息存储到一个双层结构的 MAP 中,其第一层的 key 是服务名,第二层的 key 是具体服务的实例名.
服务同步
这里的了两个服务提供者分别注册到两个不同的服务注册中上,也就是说它们的信息分别被两个服务注册中心所维护. 此时由于服务注册中心之间因为相互注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将请求转发给集群中相连的其他注册中心,从而实现了注册中心之间的服务同步.
服务续约
在注册完服务之后.服务提供会维护一个心跳来持续告诉 eureka server 它还活着,防止 eureka server 的"剔除任务"将服务实例从服务类别中移除.我们称该操作为"服务续约".
有两个重要的属性,一个是服务续约任务的调用时间间隔,默认为 30 秒. 另一个是用于定义服务时效的时间,默认为 90 秒.
服务消费者
获取服务
当我们启动消费者时,它会发送一个 REST 请求给服务注册中心,来获取上面注册的服务清单.为了性能考虑,eureka server 会维护一份只读的服务清单返回给客户端,同时该缓存清单会每隔 30 秒更新一次.
服务调用
服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息.有这些实例的详细信息,客户端就可以根据自己实际的需求决定具体调用哪个实例,在 Ribbon 中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡.
对于实例的选择,eureka 中有 region 和 zone 的概念, 一个 region 可以包含多个 zone, 每个服务客户端需要被注册到一个 zone 中,所以每个客户端对应一个 region 和一个 zone.在进行服务调用时,优先访问同一个 zone 中的服务提供方,若访问不到,就访问其他的zone.
服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务实例进行正常的关闭操作时,它会触发一个服务下线的 REST 请求给 eureka server,告诉服务注册中心它要下线了.服务端收到请求后,将该服务状态置为下线(DOWN),并把该下线事件传播出去.
服务注册中心
失效剔除
有些时候,服务实例并不一定会正常的下线,可能由于内存溢出,网络故障等原因使得服务不能正常的工作,二服务注册中心并未收到"服务下线"的请求. 为了从服务列表将这些无法提供服务的实例剔除, eureka server 在启动时会创建一个定时任务,默认每隔一段时间(默认 60 秒)将当前清单中超时(默认 90 秒)没有续约的服务剔除.
自我保护
当我们在本地调试基于 eureka 程序时,基本都会出现一个问题,在服务注册中心信息面板上出现红色警告. 该警告就是触发了 eureka server 的自我保护机制. 服务注册到 eureka server 后,会维护一个心跳连接,告诉 eureka server 自己还活着. eureka server 在运行期间,会统计心跳失败的比例在 15 分钟之内是否低于 85%,如果出现低于的情况(在单机调试时很容易满足,实际在生产环境通常是由于网络不稳定造成), eureka server 会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能的保护这些注册信息. 但是这段时间内若实例出现问题,那么客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重复,熔断机制.
我们在本地开发时可以使用 eureka.server.enable-self-preservation=false 参数来关闭自我保护机制,以确保注册中心可以将不用的实例正确剔除.
3.5 配置详解
在 eureka 治理的体系当中,主要分为服务端和客户端两个不同的角色,服务端为服务注册中心,而客户端为各个提供接口的微服务应用. 当我们构建了高可用的注册中心后, 该集群中的所有的微服务应用和后续将要介绍的基础应用(如配置中心,API 网关等)都可以视作该体系下的一个微服务(eureka 客户端). 服务注册中心也一样,只是高可用环境下的服务注册中心除了作为客户端之外,还为集群中的其他客户端提供了服务注册的特殊功能. 所以 eureka 客户端的配置对象存在与所有 eureka 服务治理体系下的应用实例中. 在实际使用 spring cloud eureka 的过程中,我们所做的配置内容几乎都是对 eureka 客户端配置进行的操作.
eureka 客户端的配置主要分为:
- 服务注册相关的配置信息,包括服务注册中心的地址,服务获取的时间间隔,可用区域等.
- 服务实例相关的配置信息,包括服务实例的名称,IP 地址,端口号,健康检查路径等.
而 eureka 服务端更多的类似于一个现成产品,一般情况下我们需要修改它的配置信息.
服务注册类配置
这些配置信息一般是以 eureka.client 为前缀.
指定注册中心
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/,http://localhost:8002/eureka/
服务实例类配置
这类配置都是以 eureka.instance 为前缀.
元数据
服务实例的元数据是 eureka 客户端在向服务注册中心发送注册请求,用来描述自身服务信息的对象,其中包含了一些标准化的元数据,比如服务名称,实例名称,实例IP,实例端口等用于服务治理的重要信息;以及一些用于负载均衡策略或者其他特殊用途他的自定义元数据信息.
实例名配置
实例名 instanceId, 它是区分同一服务中不同实例的唯一标识. 默认使用主机名作为标识
端点配置
健康检查
来源:oschina
链接:https://my.oschina.net/u/4150612/blog/3179448