SpringCloud的Eureka微服务的注册和发现
SpringCloud Eureka微服务的注册和发现本文将介绍Eureka的原理和作用:1.服务发现简介服务发展组件的结构图服务提供者、服务消费者、服务发现组件这三者之间的关系:Created with Raphaël 2.1.2服务消费者服务消费者服务提供者服务提供者服务发现组件服务发现组件调用注册注册发送心跳各微服务在启动的时,将自己的网络地址等信息注册...
#SpringCloud Eureka微服务的注册和发现
本文将介绍Eureka的原理和作用:
1.服务发现简介
服务发展组件的结构图
服务提供者、服务消费者、服务发现组件这三者之间的关系:
- 各微服务在启动的时,将自己的网络地址等信息注册到服务发现组件中,服务发展组件会存储这些信息
- 服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口
- 各个微服务与服务发现组件使用一定的机制(例如心跳)通信。服务发现组件如长时间无法与某微服务实例通信,就会注销该实例。
- 微服务网络地址发生变更(例如实例增减或者IP端口发生变化等)时,会重新注册到服务发现组件。使用这种方式,服务消费者就无须人工修改提供者的网络地址了。
综上,服务发现组件应具备以下功能
- 服务注册表:是服务发现组件的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册和注销。
- 服务住蹙额和服务发现:服务注册是指微服务在启动的时,将自己的信息注册到服务发现组件上的过程。服务发现是指查询可用微服务列表及其网络地址的机制。
- 服务检查:服务发现组件使用一定机制定时检测已注册的服务,如发现某实例长时间无法访问,就会在服务注册表中移除该实例。
综上,使用服务发现的好处是显而易见的。Spring Cloud 提供了多种服务发现组件的支持,例如Eureka、Consul和Zookeeper等。
服务发现的方式可以细分为服务器端发现和客户端发现,原理基本相通。
2.Eureka原理
Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:
- Eureka Server 提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息(例如IP、端口、微服务名称等),Eureka Server会存储这些信息。
- Eureka Client是一个Java客户端,用于简化与Eureka Server的交互
- 微服务启动后会周期性(默认30秒)地向Eureka Server发送心跳以续约自己的“租期”。
- 如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)
- 默认情况下,Eureka Server同时也是Eureka Client。多个Eureka Server实例,相互之间通过复制的方式,来实现服务注册表中的数据的同步。
- Eureka Client 会缓存服务注册表中的信息。这种方式有一定的优势——首先,微服务无须每次请求都查询Eureka Server 的压力;其次即使Eureka Server所有节点都宕掉,服务消费者依然可以使用缓存中过的信息找到服务提供者并完成调用。
综上,Eureka通过心跳检查、客户端缓存等机制,提高系统的灵活性、可伸缩性和可用性。
2.Eureka Server
1.pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<!--<artifactId>spring-cloud-starter-eureka-server</artifactId>-->
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</dependency>
2.启动类,在启动类上添加@EnableEurekaServer注解,声明这是一个Eureka Server。
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
// new SpringApplicationBuilder(EurekaServerApplication.class).web(true).run(args);
}
}
3.在配置文件application.yml中添加以下内容
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
application.yml中的配置属性:
- eureka.client.registerWithEureka:表示十分将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server,故而设为false。
- eureka.client.fetchRegistry:表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他 的Eureka Server节点的数据,故而设为false。
- eureka.client.serverUrl.defaultZone:设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址,默认是http://localhost:8761/eureka;多个地址可用,分隔。
访问http://localhost:8761/eureka可用看到界面可知,Eureka Server在首页展示了很多信息,例如当前实例的系统状态、注册到Eureka Server上的服务实例、常用信息、实例信息等。
2.Eureka Client
1.pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<!--<artifactId>spring-cloud-starter-netflix-eureka</artifactId>-->
</dependency>
2.在配置文件application.yml中添加以下配置。
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address:true
server:
port: 8762
spring:
application:
name: service-hi
其中spring.application.name用于指定注册到Eureka Server上的应用名称;eureka.instance.prefer-ip-address = true 表示自己的IP注册到Eureka Server。如果不配置该属性或者设置为false,则表示注册微服务在所有操作系统的hostname到Eureka Server。
3.编写启动类,在启动类上添加@EnableDiscoveryClient注解,声明这是一个Eureka Client
@SpringBootApplication
@EnableEurekaClient //@EnableDiscoveryClient
@RestController
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
@Value("${server.port}")
String port;
@RequestMapping("/hi")
public String home(@RequestParam String name){
return "hi "+name+",I'm from port:" +port;
}
}
也可以使用@EnableDiscoveryClient 注解替代@EnableEurekaClient。在Spring Cloud中,服务发现组件有多种选择,例如Zookeeper、Consul等,@EnableDiscoveryClient为各种服务组件提供了支持,该注解是spring-cloud-commons项目的注解,是一个高度的抽象;而@EnableEurekaClient表明是Eureka的Client,该注解是spring-cloud-netflix项目中的注解,只能与Eureka一起工作。当前Eureka在项目的classpath中时,两个注解没有区别。
访问http://localhost:8761/eureka看到一个微服务已经注册到了Eureka Server
2.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,额外配置了eureka.client.registerWithEureka=false、eureka.client.fetchRegistry=false。
构建双节点的Eureka Server集群
1.修改系统hosts,Windows系统的hosts文件路径C:\Windows\System32\driver\etc\hosts;Linux及Mac OS 等系统的文件路径是/etc/hosts。
127.0.0.1 peer1 peer2
2.将配置文件application.yml修改如下:让两个节点的Eureka Server相互注册。
spring:
application:
name: peers-eureka-server
---
spring:
#指定profile=peer1
profile: peer1
server:
port: 8761
eureka:
#指定当profile=peer1时,主机名是peer1
instance:
hostname: peer1
client:
service-url:
defaultZone: http://peer2:8762/eureka
---
spring:
#指定profile=peer2
profile: peer2
server:
port: 8762
eureka:
#指定当profile=peer1时,主机名是peer1
instance:
hostname: peer2
client:
service-url:
defaultZone: http://peer1:8761/eureka
如上,使用连字符(—)将该application.yml文件分为三段。第二段和第三段分别为spring.properties指定了一个值,该值表示它所在的那段内容应用在哪个Profile里。第一段由于并未指定spring.profile,因为这段内容会对所有的Profile生效。
经过以上分析,第一了peer1和peer2这两个Profile。当应用以peer1这个Profile启动时,配置改Eureka Server的主机名为peer1,并将其注册到http://peer2:8762/eureka/;反之,当应用以profile=peer2时,Eureka Server会注册到peer1节点的Eureka Server。
测试
1.打包项目 并使用以下命令启动两个Eureka Server节点。
java -jar ****-0.0.1-SNAPHSHOT.jar --spring.profiles.active=peer1
java -jar ****-0.0.1-SNAPHSHOT.jar --spring.profiles.active=peer2
2.访问http://peer1:8761,会发现“registered-replicas”中已有peer2节点;同理访问http://peer2:8762,也能发现其中“registered-replicas”中已有peer1节点
将应用注册Eureka Server集群上
以前面的Eureka Client 项目为例,只需修改eureka.client.serviceUrl.default-Zone,配置多个Eureka Server地址就可以将其注册到Eureka Server集群了。
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/
instance:
prefer-ip-address:true
server:
port: 8762
spring:
application:
name: service-hi
当然微服务即使只配置,Eureka Server集群中某个节点,也能正常注册到Eureka Server集群,因为多个Eureka Server之间的数据会相互同步。
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka/
instance:
prefer-ip-address:true
server:
port: 8762
spring:
application:
name: service-hi
正常情况下,这种方式与配置多个Server节点的效果一样的。不过为适应某些极端场景,建议在客户端配置多个Eureka Server节点
3.为Eureka Server 添加用户认证
前面例子中,Eureka Server是允许匿名访问的,本节来构建一个需要登录才能访问Eureka Server。
1.pom.xml中添加spring-boot-start-security依赖,该依赖为Eureka Server提供用户认证的能力。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-start-security</artifactId>
</dependency>
2.在application.yml中添加以下内容:
security:
basic:
enabled:true # 开启基于HTTP basic的认证
user:
name:user # 配置登录的账号是user
password:password123 # 配置登录的密码是password123
这样就为Eureka Server 添加了一个基于 HTTP basic的认证。如果不设置这段内容,账号默认是为user,密码是一个随机值,该值会在启动时打印出来。
将服务注册到需要认证的Eureka Server
将eureka.client.serviceUrl.defaultZone配置
为http://user:password@EUREKA_HOST:EUREKA_PORT/eureka/这种形式,就可以将HTTP basic 认证添加到Eureka Client了,因此,主席稍微修改既可以将应用注册到本例的Eureka Server了:
eureka:
client:
service-url:
defaultZone: http://user:password123@localhost:8761/eureka/
instance:
prefer-ip-address:true
server:
port: 8762
spring:
application:
name: service-hi
对与更复杂的需求,可以创建一个类型为DiscoveryClientOptionalArgs的@Bean,并向其中注入ClientFilter。
踩坑记录
因为官方spring cloud Finchley.M7 以上 BUG https://github.com/spring-cloud/spring-cloud-netflix/issues/2754
spring cloud Finchley.M6 和 spring boot 2.0.0.RC1是可用的
而 spring cloud Finchley.M9 和 spring boot 2.0.1.Release是官方仍在解决
至于其他版本
spring cloud Finchley.M7 8 和 spring boot 2.0.0.Release 也有该问题。
4.Eureka 的元数据
Eureka的元数据有两种,分别是标准元数据和自定义元数据。
标准元数据指的是主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发现服务注册表中,用于服务之间的调用。自定义元数据可以使用eureka.instance.metadata-map配置,这些元数据可以在远程客户端中访问,但一般不会改变客户端的行为,除非客户端知道该元数据的含义。
4.1改造user微服务
准备:负责项目microservice-provide-user,将ArtifactId修改为microservice-provide-user-my-metadata
1.修改application.yml,使用eureka.instance.metadata-map属性为改微服务添加应自定义的元数据:
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address:true
metadata-map:
# 自定义的元数据,key/value都可以随便写
my-metadata:我自定义的元数据
server:
port: 8762
spring:
application:
name: service-user
#####5.改造movie微服务
准备:负责项目microservice-consumer-movie,将ArtifactId修改为microservice-consumer-movie-understanding-metadata
1.修改application.yml,使用eureka.instance.metadata-map属性为改微服务添加应自定义的元数据:
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return this.restTemplate.getForObject("http://localhost:8000/" + id, User.class);
}
/**
* 查询microservice-provider-user服务的信息并返回
* @return microservice-provider-user服务的信息
*/
@GetMapping("/user-instance")
public List<ServiceInstance> showInfo() {
return this.discoveryClient.getInstances("microservice-provider-user");
}
}
使用DiscoveryClient.getInstances(serviceId),可以查询指定服务在Eureka上的实力列表
测试
1.启动microservice-discovery-eureka。
2.启动microservice-provide-user-my-metadata
3.启动microservice-consumer-movie-understabding-metadata
4.访问http://localhost:8761/eureka/apps可查看Eureka的metadata。
5.访问http://localhost:8010/user-instance,可以返回类似如下内容。
...略
可以看到使用DiscoveryClient的API获得了用户微服务的各种信息,其中包括了标准元数据和字段元数据。例如IP、端口等信息都是标准元数据,用于服务之间的调用;同时自定义元数据my-metadata,也可以通过客户端查询到但是并不会改变客户端的行为。
Eureka Server的REST端点
Eureka Server 提供了一些REST的端点。非JVM的微服务可以使用这些REST端点操作Eureka,从而实现注册于发现。前面讲的Eureka Client 就是一个使用Java编写的、操作这些REST端点的类库。也可以分析这些REST端点,编写其他语言的Eureka Client。
下表中Eureka 提供的REST端点,可以使用的XML或者JSON与这些端点通信,默认是XML
Operation | HTTP action | Description |
---|---|---|
Register new application instance | POST /eureka/v2/apps/appID | Input: JSON/XML payload HTTP Code: 204 on success |
De-register application instance | DELETE /eureka/v2/apps/appID/instanceID | HTTP Code: 200 on success |
Send application instance heartbeat | PUT /eureka/v2/apps/appID/instanceID | HTTP Code: * 200 on success * 404 if instanceID doesn’t exist |
Query for all instances | GET /eureka/v2/apps | HTTP Code: 200 on success Output: JSON/XML |
Query for all appID instances | GET /eureka/v2/apps/appID | HTTP Code: 200 on success Output: JSON/XML |
Query for a specific appID/instanceID | GET /eureka/v2/apps/appID/instanceID | HTTP Code: 200 on success Output: JSON/XML |
Query for a specific instanceID | GET /eureka/v2/instances/instanceID | HTTP Code: 200 on success Output: JSON/XML |
Take instance out of service | PUT /eureka/v2/apps/appID/instanceID/status?value=OUT_OF_SERVICE | HTTP Code: * 200 on success * 500 on failure |
Move instance back into service (remove override) | DELETE /eureka/v2/apps/appID/instanceID/status?value=UP (The value=UP is optional, it is used as a suggestion for the fallback status due to removal of the override) | HTTP Code: * 200 on success * 500 on failure |
Update metadata | PUT /eureka/v2/apps/appID/instanceID/metadata?key=value | HTTP Code: * 200 on success * 500 on failure |
Query for all instances under a particular vip address | GET /eureka/v2/vips/vipAddress | * HTTP Code: 200 on success Output: JSON/XML * 404 if the vipAddress does not exist. |
Query for all instances under a particular secure vip address | GET /eureka/v2/svips/svipAddress | * HTTP Code: 200 on success Output: JSON/XML * 404 if the svipAddress does not exist. |
表中的APPID是用程序的名称,instanceID是与实例相关关联的唯一ID。在AWS环境中,instanceID表示微服务实例的实例ID在非AWS环境则表示实例的主机名。
REST 端点注册微服务
1.启动miroservice-discovery-eureka.
2.编写一个符合上面XSD的XML,命名为rest-api-test.xml
略
3.使用curl命令测试
cat ./rest-api-test.xml | curl -v -X POST -H "Content-type: application/xml" -d @- http://localhost:8761/eureka/apps/rest-api-test
终端将会输出类似以下内容:
* upload completely sent off: 644 out of 644 bytes
< HTTP/1.1 204
< Content-Type:application/xml
< Date: Mon, 19 Dec 2016 05:12:21 GMT
此时,查看Eureka Server首页,会发现微服务已经成功注册。
查看某微服务的所有实例
在浏览器上输入地址:http://localhost:8761/eureka/apps/rest-api-test,将会看到类似于如下的内容
...略
从中可以看到此微服务的所有实例,以及实例的详细信息
注销微服务实例
将上面注册的微服务实例注销
curl -v -X DELETE http://localhost:8761/eureka/apps/rest-api-test/itmuch:rest-api-test:9000
< HTTP/1.1 200
< Content-Type:application/xml
< Content-Length
< Date: Mon, 19 Dec 2016 02:27:21 GMT
6.Eureka的自我保护模式
进入自我保护模式最直观的体现,是Eureka Server 首页输出的警告
默认情况下,如果Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。
Eureka通过“自我保护模式”来解决这个问题——当Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表数据(也就不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同步保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加健壮、稳定。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式
- **Eureka 官方关于自我保护模式的介绍:http://github.com/Netflix/eureka/wiki/Understanding-Eureka-Peer-Communication **
- Eureka的FAQ,其中讲到了自我保护模式:http://github.com/Netflix/eureka/wiki/FAQ
- Eureka与Zookeeper做服务发现的对比:http://dockone.io/article/78
- Eureka不注销任何服务的讨论:http://stackoverflow.com/questions/32616329/eureka-never-unregister-a-service
6.多网卡环境下的IP选择
指定IP在某些场景下很有用。例如某台服务器有eth0、eth1和eth2三块网卡,但是只有eth1可以被其他的服务器访问;如果Eureka Client 将eth0或者eth2注册到Eureka Server上,其他微服务就无法通过这个IP调用该微服务的接口。
Spring Cloud 提供了按需选择IP的能力,从而避免以上的问题。请看下面:
1.忽略指定名称的网卡
例如:
spring:
cloud:
inetutils:
ignored-interfaces:
- docker0
- veth.*
eureka:
instance:
prefer-ip-address:true
这样就可以忽略docker0网卡以及所有的veth开头的网卡。
2.使用正则表达式,指定使用的网络地址
示例:
spring:
cloud:
inetutils:
preferredNetworks:
- 192.168
- 10.0
eureka:
instance:
prefer-ip-address:true
3.只使用站点本地地址
示例:
spring:
cloud:
inetutils:
useOnlySiteLocalInterfaces:true
eureka:
instance:
prefer-ip-address:true
4.手动指定IP地址
在某些极端场景下,可以手动指定注册到Eureka Server的微服务IP。
示例:
eureka:
instance:
prefer-ip-address:true
ip-address:127.0.0.1
站点本地地址和链路本地地址:https://4sysops.com/archives/ipv6-tutorial-part-6-site-local-address/。
源码分析:http://www.itmuch.com/spring-cloud-code-read/spring-cloud-code-read-eureka-registry-ip/。
7.Eureka的健康检查
在Eureka首页,Status一栏有个UP,表示应用程序的状态正常。应用状态还有其他取值,例如DOWN、OUT_OF_SERVICE、UNKNOWN等。只有标记为“UP”的微服务会被请求。
Eureka Server与Eureka Client之间使用心跳机制来确定Eureka Client 的状态,默认情况下服务器端与客户端的心跳保存正常,应用程序就会始终保持“UP”状态。这种机制并不能完全反应应用程序的状态。举例,微服务与Eureka Server之间的心跳正常,Eureka Server认为该微服务“UP”;然而,该微服务的数据源发生了问题(例如因为网络抖动,连不上数据源),根本无法正常工作。
Spring Boot Actuator 提供了/health端点,该端点可展示应用程序的健康信息。要实现这一点只须启用Eureka的健康检查,这样应用程序就会将自己的健康状态传播到Eureka Server。开启的方法很简单,只须为微服务配置如下内容:
eureka:
client:
healthchek:
enabled: true
某些场景下,可能希望更细颗粒度的控制健康检查,此时可实现
com.netflix.application.HealthCheckHander接口。
eureka.client.healthcheck.ebable=true只能配置在application.yml中如果配置在bootrap.yml中可能会导致一些不良的副作用,例如应用注册到Eureka Server上的状态是UNKNOWN
当eureka.client.healthcheck.enabled=true时,/pause端点(该端点由Spring Boot Actuator提供,用于暂停应用)无法正常工作,详见:https://github.com/spring-cloud/spring-cloud-netflix/issues/1571/,经过测试发现当eureka.client.healthcheck.enable=true时,请求、pause端点无法将应用在Eureka Server上标记为DOWN。由于该BUG尚未修改,建议读者留意。
Eureka健康检查相关博客:https://jmnarloch.wordpress.com/2015/09/02/spring-cloud-fixing-eureka-application-status/
本文多数摘自《Spring Cloud与Docker微服务架构实战》作者使用版本为Camden SR4版本 spring boot 1.4.3.RELEASE版本 如果测试Finchley.M9版本请自行测试
再次推荐两本书:
周立老师的《Spring Cloud与Docker微服务架构实战》和方志朋老师的《深入理解Spring Cloud与微服务构建》
更多推荐
所有评论(0)