Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于 Netflix Eureka 做了二次封装,主要负责完成微服务架构中的服务治理功能。主要包括:
- Eureka 服务端
1)服务注册中心 - Eureka 客户端
1)服务提供者
2)服务消费者
本文主要内容包括:
服务治理机制
1)服务提供者
- 服务的注册 - 服务的同步 - 服务的续约
2)服务消费者
- 服务的获取 - 服务的调用 - 服务的下线
3)服务注册中心
- 失效剔除 - 自我保护
配置详解
1)Eureka Server 的配置2)Eureka Client 的配置
- 服务注册类配置(均以 eureka.client 为前缀) - 指定注册中心 - 其他配置 - 服务实例类配置(均以 eureka.instance 为前缀) - 元数据 - 实例名 - 端点 - 健康检测 - 其他配置
服务治理机制
服务提供者
- 服务注册
“服务提供者”在启动的时候会通过 Rest 请求的方式将自己注册到 Eureka Server 上,同时带上自身服务的一些元源数据。注册中心接收到请求后,将元数据信息存储在一个双层结构的 Map 中,第一层的 key 是服务名,第二层的 key 是具体服务的实例名。
服务注册在单机的服务注册中心配置:
spring.application.name=hello-service
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
服务注册在集群的服务注册中心配置:
spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
在注册服务时,要确保参数:eureka.client.register-with-eureka=true,是否正确,如果设置为false,则不会启动注册操作。
- 服务同步
如果服务注册中心是集群的话,当服务提供者通过 http 协议发送 rest 请求到其中的一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。
- 服务续约
在注册完服务之后,服务提供者会维护一个心跳,用来通知注册中心“我还活着”,以防被注册中心的“剔除任务”将服务实例从服务列表中排除出去,这个行为称之为服务续约。
关于续约的两个属性:
# 定义服务续约任务的调用间隔时间
eureka.instance.lease-renewal-interval-in-seconds=30
# 定义服务失效的时间
eureka.instance.lease-expiration-duration-in-seconds=90
服务消费者
Ribbon 是基于 HTTP 和 TCP 的客户端负责均衡器,它是通过在客户端中配置的 ribbonServerList 服务端列表去轮询访问服务实例,以达到负载均衡的作用。当 Ribbon 和 Eureka 联合使用时,Ribbon 的服务实例清单 RibbonServerList 会被 DiscoveryEnabledNIWSServerList 重写,扩展成从 Eureka 注册中心中获取服务端列表,同时它也会用 NIWSDiscoveryPing 取代 IPing 。
- 获取服务
当启动消费者时,它会发送一个rest请求给服务注册中心,注册中心返回给客户端一份只读的服务清单,同时消费者会每隔30秒重新请求一份清单。
# 确保该参数为true,开启请求服务清单列表
eureka.client.fetch-registry=true
# 缓存清单的更新时间,默认为30s
eureka.client.registry-fetch-interval-seconds=30
- 服务调用
消费者获取服务清单后,通过服务名可以获得具体提供服务的实例名和实例的元数据。通过这些信息,客户端会根据自己的需要决定调用哪个实例。在 Ribbon 中默认采用轮询的方式调用,从而实现客户端的负载均衡。
- 服务下线
当服务实例进行正常的关闭操作时,它会触发一个服务下线的 rest 请求给注册中心,注册中心接收到请求后,将服务状态置为下线(DOWN),并把下线事件传播到其他注册中心。
服务注册中心
- 失效剔除
有时候,服务实例并不一定会正常下线(内存溢出、网络故障等),这样也就没有 rest 请求发送给注册中心,那么注册中心就检测不到服务是否存活。但是,注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(60s)将当前清单中超时(90s)没有续约的服务剔除出去。
- 自我保护
我们在本地调试基于 eureka 的程序时,基本上都会在服务注册中心的信息面板上看到一个红色的警告信息,这个警告实际上是触发了 eureka server 的自我保护机制。
服务注册到 eureka server 之后,会维护一个心跳连接,在 eureka server 运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现,eureka server 会将当前的实例信息保护起来,不让这些实例过期,尽管在这段保护期间内,实例可能会出现问题,eureka server 仍然会保护,这样,客户端很容易拿到已经挂掉的实例,导致调用失败的情况,因此客户端必须要有容错机制,比如请求重试、断路器等机制。
禁止注册中心的保护机制:
# 关闭保护机制,确保 eureka server 可以将不可用的实例正确剔除
eureka.server.enable-self-preservation=false
不可用服务实例被剔除的时间间隔:
# 过期服务剔除的默认时间为1分钟
eureka.server.eviction-interval-timer-in-ms=60000
配置详解
Eureka Server 服务端参数均以 eureka.server 作为前缀,大多数情况下,除了自我保护机制外,不需要修改其他配置。它的所有配置参数均可在 org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean 中找到。
Eureka Client 客户端的主要配置分为:
1)服务注册相关:服务注册中心地址、服务列表获取的间隔时间、可用区域等
2)服务实例相关:服务实例的名称、ip地址、端口号、健康检查路径等
服务注册类配置(均以 eureka.client 为前缀)
- 指定注册中心
# 此参数的配置值存储在 HashMap 中,并有一组默认值
# 默认 key:defaultZone,默认 value:http://localhost:8761/eureka/
# 单机配置
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
# 集群配置
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
# 附带安全校验
eureka.client.serviceUrl.defaultZone=http://<username>:<password>@localhost:1111/eureka/
- 其他配置
下面是整理了 org.springframework.cloud.netflix.eureka.EurekaClientConfigBean 中定义的常用配置参数以及对应的说明和默认值,这些参数均以 eureka.client 为前缀。
参数名 | 说明 | 默认值 |
---|---|---|
enabled | 启用 Eureka 客户端 | true |
registryFetchIntervalSeconds | 从 Eureka 服务端获取注册信息的间隔时间,单位为秒 | 30 |
instanceInfoReplicationIntervalSeconds | 更新实例信息的变化到 Eureka Server 的间隔时间,单位为秒 | 30 |
initialInstanceInfoReplicationIntervalSeconds | 初始化实例信息到 Eureka Server 的间隔时间,单位为秒 | 40 |
eurekaServiceUrlPollIntervalSeconds | 轮询 Eureka Server 地址更改的时间间隔,单位为秒 。当与 Spring Cloud Config 配合,动态刷新 Eureka 的 serviceURL 地址时需要关注该参数 | 300 |
eurekaServerReadTimeoutSeconds | 读取 Eureka Server 信息的超时时间,单位为秒 | 8 |
eurekaServerConnectTimeoutSeconds | 连接 Eureka Server 的超时时间,单位为秒 | 5 |
eurekaServerTotalConnections | 从 Eureka Client 到所有 Eureka Server 的连接总数 | 200 |
eurekaServerTotalConnectionsPerHost | 从 Eureka Client 到每个 Eureka Server 主机的连接总数 | 50 |
eurekaConnectionIdleTimeoutSeconds | Eureka Server 连接的空闲关闭时间,单位为秒 | 30 |
heartbeatExecutorThreadPoolSize | 心跳连接池的初始化线程数 | 2 |
heartbeatExecutorExponentialBackOffBound | 心跳超时重试延迟时间的最大乘数值 | 10 |
cacheRefreshExecutorThreadPoolSize | 缓存刷新线程池的初始化线程数 | 2 |
cacheRefreshExecutorExponentialBackOffBound | 缓存刷新重试延迟时间的最大乘数值 | 10 |
useDnsForFetchingServiceUrls | 使用 DNS 来获取 Eureka Server 的serviceUrl | false |
registerWithEureka | 是否要将自身的实例信息注册到 Eureka Server | true |
preferSameZoneEureka | 是否偏好使用处于相同 Zone 的 Eureka Server | true |
filterOnlyUpInstances | 获取实例时是否过滤,仅保留 UP 状态的实例 | true |
fetchRegistry | 是否从 Eureka Server 获取注册信息 | true |
服务实例类配置(均以 eureka.instance 为前缀)
- 元数据
在使用spring cloud eureka的时候,所有的配置信息都通过 org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 进行加载,服务注册的时候,会被包装成 com.netflix.appinfo.InstanceInfo 对象发送给 Eureka 服务端。
我们可以通过 eureka.instance.properties=value 的格式对标准化元数据直接配置,properties 就是 EurekaInstanceConfigBean 对象中的成员变量名。
也可以自定义元数据,通过 eureka.instance.metadataMap.key=value 的格式进行配置,比如:eureka.instance.metadataMap.zone=henan,metadataMap 是 InstanceInfo 中的 ConcurrentHashMap 。
- 实例名
实例名,即InstanceInfo中的instanceId参数,它是区分同一服务中不同实例的唯一标识。针对同一主机启动多个相同的服务实例的情况,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.instance.instanceId=${spring.application.name}:${random.int}
- 端点
在InstanceInfo中,可以看到一些 URL 的配置信息,比如 homePageUrl(主页)、statusPageUrl(状态页)、healthCheckUrl(健康检查页)。其中,状态页和健康检查的 URL 默认使用了 spring-boot-actuator 模块提供的 /info 端点和 /health 端点。我们必须确保 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.path}
绝对路径配置方式:
# 上面使用的 statusPageUrlPath 和 healthCheckUrlPath 都使用相对路径进行配置,由于注册中心默认会以 HTTP 的方式来访问和暴露这些端点,因此当客户端应用以HTTPS的方式时,相对路径的配置方式就不满足需求了。
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 端点实现的,而是依靠客户端心跳的方式来保持服务实例的存活。但是,由于大多数微服务应用都会有一些其他的外部资源依赖,比如数据库、缓存、消息代理等,如果我们的应用无法与这些资源联调,实际上已经不能正常的对外服务了,但是心跳检测依然在运行,所以还是会被服务消费者调用。在 spring cloud eureka 中,可以通过简单的配置,把 eureka 客户端的健康检查交给 spring-boot-actuator 模块的 /health 端点,以实现更加全面的健康状态维护。
1)在 pom.xml 中引入 spring-boot-starter-actuator 模块
2)在 application.properties 中增加参数配置 eureka.client.healthcheck.enabled=true
3)如果客户端路径做了特殊处理,参考端点配置时的方法进行配置
- 其他配置
除了上面介绍的配置参数外,下面整理了一些 org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean 中定义的配置参数以及对应的说明和默认值,这些参数均以 eureka.instance 为前缀。
参数名 | 说明 | 默认值 |
---|---|---|
preferIpAddress | 是否优先使用 IP 地址作为主机名的标识 | false |
leaseRenewalIntervalInSeconds | Eureka Client 向 Eureka Server 发送心跳的时间间隔,单位为秒 | 30 |
leaseExpirationDurationInSeconds | Eureka Server 在收到最后一次心跳之后等待的时间上线,单位为秒。超过该时间之后,Eureka Server 会将该服务实例从服务清单中剔除 | 90 |
nonSecurePort | 非安全的通信端口号 | 80 |
securePort | 安全的通信端口号 | 443 |
nonSecurePortEnabled | 是否启用非安全的通信端口号 | true |
securePortEnabled | 是否启用安全的通信端口号 | |
appname | 服务名,默认取 spring.application.name 的配置值,如果没有则为 unknown | |
hostname | 主机名,不配置的时候将根据操作系统的主机名来获取 |
以上这些配置中,除了前三个配置参数在需要的时候可以做一些调整,其他的参数配置大多数情况下使用默认值即可。
Eureka 的跨平台支持
Eureka 的通信机制使用了 HTTP 的 REST 接口实现,由于 HTTP 的平台无关性,虽然 Eureka Server 通过 Java 实现,但是在其下的微服务应用并不限于使用 Java 来进行开发。
默认情况下,Eureka 使用 jersey 和 XStream 配合 JSON 作为 Server 与 Client 之间的通信协议,当然也可以实现自己的协议来代替。