SpringCloud—Eureka
服务治理概念
Eureka服务端:
我们也称为服务治理中心。他同其他配置中心一样,支持高可用配置,它依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。如果Eureka以集群方式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许分片故障期间继续提供服务的发现与注册。当故障分片恢复运行时,集群中的其他分片会把他们的状态再次同步回来。
Eureka客户端:
主要处理服务的注册于发现。客户端服务通过注解和参数配置的方式嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka向注册中心注册自身提供的服务并周期性的发送心跳来更新它的服务租约,同时,他也能从服务端查询当前注册的信息并把他们缓存到本地并周期性的刷新服务状态。
搭建服务注册中心
第一步:在POM.xml中引入相关依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入Eureka 所需依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <!--这里全局定义Spring Cloud的版本等信息--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.SR7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
第二步:
通过@EnableEurekaServer注解启动一个服务注册中心,提供给其他应用进行对话,如下:
@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
在默认配置下,该服务注册中心会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需在application.properties中增加如下配置:
server.port=8888 #关闭注册中心的“自我保护”功能,以防止关闭的实例无法被服务注册中心剔除的问题。 eureka.server.enable-self-preservation=false eureka.instance.hostname=localhost #由于默认设置下,服务注册中心也会将自己作为客户端来注册自己,所以我们需要禁用它的客户端注册行为 eureka.client.register-with-eureka=false #由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置成false eureka.client.fetch-registry=false eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.register-with-eureka:由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己。
eureka.client.fetch-registry:由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
注册服务提供者
搭建了服务注册中心后,我们尝试将一个SpringBoot应用加入Eureka的服务治理体系中去。
第一步,引入服务提供者所需的POM:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--eureka 客户端需要引入这个依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Brixton.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
第二步,写一个Controller类,HelloController.java中的内容如下:
package com.example.client01; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.logging.Logger; /* * description: * author: W W * date:2018-03-12 19:06 * */ @RestController public class HelloController { private final org.slf4j.Logger logger= LoggerFactory.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "hello",method = RequestMethod.GET) public String index(){ ServiceInstance instance=client.getLocalServiceInstance(); logger.info("/hello ,host:"+instance.getHost()+", service_id:"+instance.getServiceId()); return "Hello Colud!"; } }
然后,在主类中通过加上@EnableDiscoveryClient注解,激活Eureka中的DiscoveryClient实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveryClient实例),才能实现上述Controller中对服务信息的输出。
package com.example.client01; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class Client01Application { public static void main(String[] args) { SpringApplication.run(Client01Application.class, args); } }
最后,我们需要在application.perperties中,通过spring.application.name来为服务命名,比如命名和hello-service,再通过eureka.client.serviceUrl.defaultZone 属性来指定服务注册中心的地址,这里我们指定为之前构建的服务注册中心地址,这里我们指定为之前构建的服务注册中心地址,万增配置如下:
spring.application.name=hello-service eureka.client.service-url.defaultZone=http://localhost:8888/eureka/
高可用注册中心
在微服务架构这样的分布式环境中,我们需要充分考虑发生故障的情况,所以在生产环境中必须对各个组件进行高可用部署,对于微服务如此,对于注册中心也一样。之间一直是单节点的服务注册中心,这在生产环境中显然并不合适,我们需要构建高可用的服务注册中心以增强系统的可用性。
Eureka Server的设计一开始就考虑了高可用的问题,在Eureka的服务治理设计中,所有节点既是服务提供方,也是服务消费方,服务注册中心也不例外。是否还记得在单节点的配置中,我们设置过下面这两个参数,让注册中心注册不了自己。
eureka.client.register-with-eureka=false eureka.client.fetch-registry=false
Eureka Server的高可用实际上就是将自己作为服务向其他服务中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的相互同步,达到高可用的效果。下面我们就来尝试搭建高可用服务注册中心的集群。下面我们构建一个双节点的服务注册中心集群。
创建application-peer1.properties,作为peer1服务中心的配置,并将service-url指向peer2
spring.application.name=eureka-server server.port=1111 #配置了host,peer1映射到127.0.0.1 eureka.instance.hostname=peer1 eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
创建application-peer2.properties,作为peer2服务中心的配置,并将service-url指向peer1:
spring.application.name=eureka-server server.port=1112 eureka.instance.hostname=peer2 eureka.client.service-url.defaultZone=http://peer1:1111/eureka/
在etc/hosts文件中添加对peer1和peer2的转换,让上面配置的host形式的service-url能在本地正确访问到;Windows系统路径为:C:\Windows\System32\drivers\etc\hosts。
127.0.0.1 peer1 127.0.0.1 peer2
通过spring.profiles.active属性来分别启动peer1和peer2:
java -jar eureka_server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1 java -jar eureka_server-0.0.1-SNAPSHOT.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的节点变为了不可用分片(unavaiable-replicas)
在设置了多节点服务注册中心后,服务提供方还需要做一些简单的配置才能将服务注册到Eureka Server集群中。我们以hello-service为例,修改application.properties配置文件,如下所示:
spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
上面的配置主要是对eureka.client.serviceUrl.defaultZone做了改动
下面我们启动该服务,通过访问http:// localhost:1111/和http://localhost:1112/,可以观察到hello-service服务同时被注册到了peer1和peer2上。若此时断开peer1,由于hello-service同时也向peer2注册,因此在peer2上的其他服务依然能访问到hello-service,从而实现服务注册的高可用。
如我们不想使用主机名来定义注册中心的地址,也可以使用IP地址的形式,但是需要在配置文件中增加配置参数eureka.instance.prefer-ip-address=true,该值默认为false。
服务发现与消费
上面,我们已经搭建起微服务架构中的核心组件——服务注册中心(包括单节点模式和高可用模式)。同时还将一个SpringBoot入门程序(hello-service),注册到Eureka注册中心上,成为该服务治理体系下的一个服务,并命名为hello-service。现在我们已经有了服务注册中心和服务提供者,下面就来尝试构建一个服务消费者,它主要完成两个目标,发现服务以及消费服务。其中发现服务由Eureka服务端完成,而服务消费的任务由Ribbon完成。RibbonServerList服务端列表去轮询访问以达到均衡负载的作用。当Ribbon和Eureka联合使用时,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心获取服务端列表。同时也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动,这里不对Ribbon做详细的介绍,只需要理解它在Eureka服务发现的基础上,实现了一套对服务实例的选择策略,从而实现对服务的消费。
下面构建一个简单的实例,看看Eureka的服务治理体系下如何实现服务的发现与消费。
- 首先,做一些准备工作。启动之前实现的服务注册中心eureka-server以及hello-service服务,为了试验Ribbon的客户端负载均衡功能,我们通过java -jar命令行的方式启动不同端口的hello-service,具体如下:
java -jar eureka_server-0.0.1-SNAPSHOT.jar --server.port=8081 java -jar eureka_server-0.0.1-SNAPSHOT.jar --server.port=8082
- 创建一个SpringBoot的基础工程来实现服务消费者,取名为ribbon-consumer,并在pom.xml中引入如下的依赖内容,较之前的hello-service我们新增了Ribbon模块的依赖spring-cloud-starter-ribbon。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.7.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> </dependencies>
- 创建应用主类ConsumerApplication,通过@EnableDiscoveryClient注解让该应用注册为Eureka客户端应用,以获得服务发现的能力。同时,在该主类中创建RestTemplate的SpringBean实例,并通过@LoadBalanced注解开启客户端负载均衡。
@SpringBootApplication @EnableDiscoveryClient public class ConsumerApplication { @Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
- 创建ConsumerController类并实现/ribbon-consumer接口。在该接口中,通过在上面创建的RestTemplate来实现对HELLO-SERVICE服务提供的/hello接口进行调用。可以看到这里访问的地址是服务名HELLO-SERVIE,而不是一个具体的地址,在服务治理框架中,这是一个非常重要的特性,也符合在本章一开始对服务治理的解释。
package com.example.consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /* * description: * author: W W * date:2018-03-13 11:52 * */ @RestController public class ConsumerController { @Autowired RestTemplate restTemplate; @RequestMapping(value = "ribbon-consumer",method = RequestMethod.GET) public String helloConsumer(){ return restTemplate.getForEntity("http://HELLO-SERVICE/hello",String.class ).getBody(); } }
- 在application.properties中配置Eureka服务注册中心的位置,需要与之前的HELLO-SERVICE一样,不然发现不了该服务的,同时设置该消费者的端口为9000,不能与之前启动的应用端口冲突。
server.port=9988 spring.application.name=ribbon-consumer eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
- 启动ribbon-consumer应用后,我们可以在Eureka信息面板中看到当前除了HELLO-SERVICE之外,还多了我们实现的RIBBON-CONSUMER服务。
通过向http://localhost:9988/ribbon-consumer发起GET请求,成功返回了“Hello World”。此时,我们可以在ribbon-consumer应用的控制台中看到如下信息,Ribbon输出了当前客户端维护的HELLO-SERVICE的服务列表情况,其中包含了各个实例的位置,Ribbon就是按照此信息进行轮询访问,以实现基于客户端的负载均衡。另外还输出了其他非常有用的信息,如对各个实例的请求总数量、第一次连接信息、上一次连接信息、总的请求失败数量。
再尝试发送几次请求,并观察启动的两个HELLO-SERVICE的控制台,可以看到两个控制台交替打印下面的日志,这是我们之前在HelloController中实现的对服务信息的输出,可以用来判断当前ribbon-consumer对HELLO-SERAVICE的调用是否是负载均衡的。
服务治理机制
构建Eureka服务治理体系中的三个核心角色:
服务注册中心、服务提供者以及服务消费者
上面,我们通过简单的注解配置就实现了强大的服务治理功能之后,进一步了解一下Eureka基础架构中各个元素的一些通信行为,以此来理解基于Eureka实现的服务治理体系是如何运作起来的,下图为例:
"服务注册中心-1"和“服务注册中心-2”,他们相互注册组成了高可用集群。
“服务提供者”启动了两个实例,一个注册到“服务注册中心-1”上,另一个注册到“服务注册中心-2”上。
还有两个"服务消费者",他们也都分别指向了一个注册中心
服务提供者
服务注册
“服务提供者”在启动的时候会通过发送REST请求的方式将自己注册到EurekaServer上,同时还带上了一些元数据信息。EurekaServer接收这个Rest请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名(我们回想一下之前在实现Ribbon负载均衡的例子中,Eureka信息面板中一个服务由多个实例的情况,这些内容就是以这样的双层Map形式存储的。)
在服务注册时,需要确认一下eureka.client.register-with-eureka=true参数是否正确,该值默认为true。若设置为false将不会启动注册操作。
服务同步
如架构图中所示,这里的两个服务提供者分别注册到了两个不同的注册中心上,也就是说,他们的信息分别被两个服务注册中心所服务,此时,由于服务注册中心之间相互注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它也会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步,通过服务同步,两个服务提供者的服务信息也就可以通过这两台服务注册中心中的任意一台获取到。
服务续约
在注册完服务之后,服务提供者会维护一个心跳来维持告诉EurekaServer:"我还活着",以防止Eureka Server的“剔除任务”将该服务实例从服务列表中排除出去,我们称该操作为服务续约(Rennew)
关于服务续约有两个重要属性,我们可以关注并根据需求来进行调整:
eureka.instance.lease-renewal-interval-in-seconds=30 eureka.instance.lease-expiration-duration-in-seconds=90
eureka.instance.lease-renewal-interval-in-seconds参数用于定义服务续约任务的调用间隔时间,默认为30秒。
eureka.instance.lease-expiration-duration-in-seconds参数用于定义服务失效的时间,默认为90秒。
服务调用
服务消费者在获取服务清单后,通过服务名可以获取到具体提供服务的实例名和该实例的元数据信息,因为有这些服务消息的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用关闭了的实例。所以在客户端程序中当服务实例进行正常的关闭操作时,他会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(DOWN)并把该下线事件传播出去。
服务注册中心
失效剔除
有些时候,我们的服务实例并不一定会正常下线,可能会由于内存溢出、网络故障灯原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。为了服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每间隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
自我保护
当我们在本地调试基于Eureka的程序时,基本都会碰到这样一个问题,在服务注册中心的信息面板中出现类似下面的红色警告信息:
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之后,会维护一个心跳连接,告诉EurekaServer自己还活着。EurekaServer在运行期间,会统计心跳失败的比例在15分钟以内是否低于85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),EurekaServer会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护期间内实例若出现问题。那么客户端很容易拿到实际已经不存在的服务实例,出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。
由于本地调试很容易触发注册中心的保护机制,这会使得注册中心维护的服务实例不那么准确。所以,我们在本地进行开发的时候,可以使用eureka.server.enable-self-preservation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。
配置详解
Eureka客户端的配置主要分为以下两个方面。
- 服务注册相关的配置信息,包括服务注册中心的地址、服务获取的间隔时间、可用区域等。
- 服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号、健康检查路径等。
而Eureka的服务端更多的类似一个现成的产品,大多数情况下,我们不需要修改它的配置信息,所以不做这方面的笔记。必要时可以去org.springframework.cloud.netflix.eureka.server.EurekaServerConfig-Bean类查看源码,进步一学习。这些参数均以eureka.server作为前缀。
下面主要记录一下Eureka客户端的一些配置信息。
服务注册类配置
关于服务注册类的配置信息,我们可以通过查看org.springframework.cloud.netflix.eureka.EurekaClientConfigBean的源码来获取比官方文档更为详尽的内容,这些配置信息都以eureka。client为前缀。下面针对一些常用的配置信息做进一步的介绍和说明。
前面,我们将一个SpringBoot应用纳入Eureka的服务治理体系,除了引入Eureka的依赖之外,就是在配置文件中指定注册中心,主要通过eureka.client.serviceUrl参数实现。该参数的定义如下所示,它的配置值存储在HashMap类型中,并且设置有一组默认值,默认值的key为defaultZone、value为http://localhost:8761/eureka/
由于之前实现的服务注册中心使用了1111端口,所以我们做了如下配置,来将应用注册到对应的Eureka服务端中。
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
当构建了高可用的服务注册中心集群时,我们可以为参数的value值配置多个注册中心的地址(通过逗号分隔)。比如下面的例子:
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
另外,为了服务注册中心的安全考虑,很多时候我们都会为服务注册中心加上安全校验信息,比如http://
其他配置
下面整理了org.springframework.cloud.netflix.euraka.EurekaClient-ConfigBean中定义的常用配置参数以及对应的说明和默认值,这些参数均以eureka.client为前缀。
参数名 | 说明 | 默认值 |
---|---|---|
enabled | 启用Eureka客户端 | true |
registryFetchIntervalSeconds | 从Eureka服务端获取注册信息的间隔时间,单位为秒 | 30 |
instanceInfoReplicationIntervalSeconds | 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒 | 30 |
initialInstanceInfoReplicationIntervalSeconds | 初始化实例信息到Eureka服务的间隔时间,单位为秒 | 40 |
eurekaServiceUrlPollIntervalSeconds | 轮询Eureka服务端地址更改的间隔时间,单位为秒。当我们与Spring Cloud Config配合,动态刷新Eureka的serviceURL地址时需要关闭该参数 | 300 |
eurekaServerReadTimeoutSeconds | 读取Eureka Server信息的超时时间,单位为秒 | 8 |
eurekaServerConnectTimeoutSeconds | 连接Eureka Server的超时时间,单位为秒 | 5 |
eurekaServerTotalConnections | 从Eureka客户端到所有Eureka服务端的连接总数 | 200 |
eurekaServerTotalConnectionsPerHost | 从Eureka客户端到每个Eureka服务端主机的连接总数 | 50 |
eurekaConnetionIdleTimeoutSeconds | Eureka服务端连接的空闲关闭时间,单位为秒 | 30 |
heartbeatExecutorThreadPoolSize | 心跳连接池的初始化线程数 | 2 |
heartbeatExecutorExponentialBackOffBound | 心跳超时重试延迟时间的最大乘数值 | 10 |
cacheRefreshExecutorThreadPoolSizi | 缓存刷新线程池的初始化线程数 | 2 |
cacheRefreshExecutorExponentialBackOffBound | 缓存刷新重试延迟时间的最大乘数值 | 10 |
useDnsForFetchingServiceUrls | 使用DNS来获取Eureka服务端的serviceUrl | false |
registerWithEureka | 是否要将自身的实例信息注册到Eureka服务端 | true |
preferSameZoneEureka | 是否偏好使用处于相同Zone的Eureka服务端 | true |
filterOnlyUpInstances | 获取实例时是否过滤,仅保留UP状态的实例 | true |
fetchRegistry | 是否从Eureka服务端获取注册信息 | true |
服务实例类配置
关于服务实例类的配置信息,我们可以通过org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean的源码来获取详细内容,这些配置信息都以eurekainstance为前缀,下面针对一些常用的配置信息做一些详细的说明。
元数据
元数据就是Euraka客户端在向服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含了一些标准化的元数据,比如服务名称、实例名称、实例IP、实例端口等用于服务治理的重要信息;以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。
在使用Spring Cloud Eureka的时候,所有的配置信息都通过org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean进行加载,但是真正进行服务注册的时候,还是会包装成com.netflix.appinfo.InstanceInfo 对象发送给Eureka服务端。我们可以通过eureka.instance.
eureka.instance.metadataMap.zone=shanghai
实例名配置
实例名,即InstanceInfo中的instanceId参数,它是区分同一个服务中不同实例的唯一标示。在Netflix Eureka的原生实现中,实例名采用主机名作为默认值,这样的设置使得在同一在同一主机上无法启动多个相同的服务实例,所以,在Spring Cloud Eureka的配置中,针对同一主机中启动多实例的情况,对实例名的默认命名做了更为合理的扩展,它采用了如下默认规则:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id}:${server.port}
对于实例名的命名规则,我们可以通过eureka.instance.instanceId参数来进行配置。比如,在本地进行客户端负载均衡调试时,需要启动同一服务的多个实例,如果我们直接启动同一个应用必然会产生端口冲突。虽然可以在命名行中指定不同的server.port来启动,但是这样还是略显麻烦。实际上,我们可以直接通过server.port=0或者使用随机数server.port=${random.int[10000,19999]}来让Tomcat启动的时候采用随机端口。但是这个时候我们会发现注册到Eureka Server的实例名是相同的,这会使得只有一个服务实例能够正常提供服务。对于这个问题,我们就可以通过设置实例名规则来轻松解决:
eureka.instance.instanceId=${spring.application.name}:${random.int}
通过上面的配置,利用应用名加随机数的方式来区分不同的实例,从而实现在同一主机上,不指定端口就能轻松启动多个实际的效果。
端点配置
在InstanceInfo中,我们可以看到一些URL的配置信息,比如homePageUrl、statusPageUrl、healthCheckUrl,他们分别代表了应用主页的URL、状态页的URL健康检查的URL。其中,状态页和健康检查的URL在Spring Cloud Eureka中默认使用了spring-boot-actuator模块提供的/info端点和/health端点。虽然我们在之前的示例中并没有对这些端点做具体的设置,但是实际上这些URL地址的配置非常重要。为了服务的正常运作,我们必须确保Eureka客户端的/health端点在发送元数据的时候,是一个能够被注册中心访问到的地址,否则服务注册中心不会根据应用的健康检查来更改状态(仅当开启了healthcheck功能时,以该端点信息作为健康检查标准)。而/info端点如果不正确的话,会导致在Eureka面板中单击服务实例时,无法访问到服务实例提供的信息接口。
大多数情况下,我们并不需要修改这几个URL的配置,但是在一些特殊情况下,比如,为应用设置了context-path,这时,所有的spring-boot-actuator模块的监控端点都会增加一个前缀。所以,我们就需要做类似如下的配置,为/info和health端点也加上类似的前缀信息:
management.context-path=/hello eureka.instance.statusPageUrlPath=${management.context-path}/info eureka.instance.healthCheckUrlPath=${management.context-path}/health
另外,有时候为了安全考虑,也有可能会修改/info 和 /health端点的原始路径。这个时候,我们也需要做一些特殊的配置,比如像下面这样:
endpoints.info.path=/appInfo endpoints.health.path=/checkHealth eureka.instance.statusPageUrlPath=/${endpoints.info.path} eureka.instance.healthCheckUrlPath=/${endpoints.health.poth}
在上面所举例的两个实例中,我们使用了eureka.instance.statusPageUrlPath和eureka.instance.healthCheckUrlPath参数,这两个配置值有一个共同特点,他们都使用相对路径来进行配置。由于Eureka的服务注册中心默认会以HTTP的方式来访问和暴露这些端点,因此当客户端应用以HTTPS的方式来暴露服务和监控端点时,相对路径的配置方式就无法满足需求了。所以,Spring Cloud Eureka还提供了绝对路径的配置参数,具体示例如下所示:
eureka.instance.statusPageUrl=https://${eureka.instance.hostname}/info eureka.instance.healthCheckUrl=https://${eureka.instance.hostname}/health eureka.instance.homePageUrl=https://${eureka.instance.hostname}/
健康监测
默认情况下,Eureka中各个服务实例的健康监测并不是通过spring-boot-actuator模块的/health端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。在Eureka的服务续约与剔除机制下,客户端的健康状态从注册到注册中心开始都会处于UP状态,除非心跳终止一段时间之后,服务注册中心将其剔除。默认的心跳实现方式可以有效检查客户端进程是否正常运作,但却无法保证客户端应用能够正常提供服务。由于大多数的微服务应用都会有一些其他的外部资源依赖,比如数据库、缓存、消息代理等,如果我们的应用于这些外部资源无法联通的时候,实际上已经不能提供正常的对外服务了,但是因为客户端心跳依然在运行,所以它还是会被服务消费者调用,而这样的调用实际上并不能获得预期的结果。
在Spring Cloud Eureka中,我们可以通过简单的配置,把Eureka客户端的健康检测交给spring-boot-actuator模块的/health端点,以实现更加全面的健康状态维护。详细的配置步骤如下所示:
在pom.xml中引入spring-boot-starter-actuator模块的依赖。
在application.properties中增加参数配置eureka.client.healthcheck.enabled=true。
如果客户端的/health端点路径做了一些特殊处理,请参考前文介绍端点配置时的方法进行配置,让服务注册中心可以正确访问到健康检测端点。
其他配置
除了上面介绍的配置参数外,下面整理了一些org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean中定义的配置参数以及对应的说明和默认值,这些参数均以eureka.instance为前缀。
参数名 | 说明 | 默认值 |
---|---|---|
preferIpAddress | 是否优先使用IP地址作为主机名的标识 | false |
leaseRenewalIntervalInSeconds | Eureka客户端向服务端发送心跳的时间间隔,单位为秒 | 30 |
leaseExpirationDurationInSeconds | Eureka 服务端在收到最后一次心跳之后等待的时间上限,单位为秒。超过该时间之后服务端会将该服务实例从服务清单中剔除,从而禁止服务调用请求被发送到该实例上 | 90 |
nonSecurePort | 非安全的通信端口号 | 80 |
securePort | 安全的通信端口号 | 443 |
nonSecurePortEnabled | 是否启用非安全的通信端口号 | true |
securePortEnabled | 服务名,默认取spring.application.name的配置值,如果没有则为unknown | |
appname | 服务名,默认取spring.application.name的配置值,如果没有则为unknown | |
hostname | 主机名,不配置的时候将根据操作系统的主机名来获取 |
在上面的这些配置中,除了前三个配置参数在需要的时候可以做一些调整,其他的参数配置大多数不需要进行配置,使用默认值即可。