Eureka简介
Spring Cloud Eureka基于Netflix Eureka开发,负责整个Spring Cloud微服务架构中的服务治理,具体来讲就是两个功能:
- 服务注册:每个服务在启动时都需要连接到Eureka,并向Eureka提供本服务的名称、主机名、端口、通信协议等信息,Eureka则根据这些信息,按照服务名称组织好服务清单,例如:
服务名 服务提供者 CRM-SERVICE
10.0.0.1:8080
、10.0.0.2:8080
NEWS-SERVICE
10.0.0.5:8080
、10.0.0.6:8080
、10.0.0.7:8080
- 服务发现:服务间的调用无需在业务逻辑或者配置文件显式指定具体的服务实例所在的地址,而是向Eureka发起一个服务发现请求,并提供目标服务的名称,Eureka根据这个名称返回这个服务的实例地址。例如
NEWS-SERVICE
需要调用CRM-SERVICE
服务,NEWS-SERVICE
会向Eureka请求服务名称为CRM-SERVICE
的实例地址,Eureka会返回该服务所有可用地址:10.0.0.1:8080
、10.0.0.2:8080
,NEWS-SERVICE
则会根据这个地址发起请求。当然,每个服务都需要指定Eureka服务器的地址。
Eureka包含服务器和客户端两个部分,上述是Eureka服务器的具体功能。Eureka客户端简单来说就是用来与Eureka服务器进行通信交互,例如:
- 处理Eureka服务器提供的服务地址
- 向Eureka服务器发起请求,注册自身(服务提供者)。
- 发送心跳包,进行续租
- 实时更新客户端缓存的服务清单
Eureka高可用
考虑到服务器集群可能会出现各种各样的网络、硬件故障,所以需要多个Eureka服务器实例来组成一个服务治理集群,即使其中一个Eureka服务器宕机,其它Eureka服务器依然可以继续进行服务治理,从而达到Eureka的高可用。
Eureka在集群化时,每个Eureka服务器实例会将自己作为服务,向其它Eureka服务器实例注册自己,从而各个Eureka都两两互相注册,在服务清单发生变化时,它们会互相之间进行同步。
例如有两台主机,一台主机名为eureka0
,另外一台为eureka1
,如果需要开启两个Eureka服务器实例并组成Eureka集群,需要进行以下配置:
eureka0
配置文件application.yml
:
spring:
application:
name: eureka-server
server:
port: 8080
eureka:
client:
service-url:
default-zone: http://eureka1:8080
eureka1
配置文件application.yml
:
# eureka1配置
spring:
application:
name: eureka-server
server:
port: 8080
eureka:
client:
service-url:
default-zone: http://eureka0:8080
启动上述两个实例。并使用浏览器访问http://eureka0:8080
或http://eureka1:8080
,可以发现Instances currently registered with Eureka
栏有1个Eureka服务器实例,代表这两个Eureka服务器已经成功组成Eureka服务器集群。
对于Spring Cloud微服务集群的Eureka客户端,可以在配置项eureka.client.serviceUrl.defaultZone
指定多个Eureka服务器实例的地址,这样在其中一个地址对应的服务器宕机时,Eureka客户端还可以尝试连接另外一个Eureka服务器实例。
Spring Cloud微服务集群结构
Spring Cloud微服务集群有三个基本角色:服务注册中心、服务提供者、服务消费者,组成以下架构:
服务提供者
服务提供者与Eureka的交互主要体现在以下三个方面:
-
服务注册
服务提供者在启动时,会根据配置文件中配置的Eureka服务器地址列表,随机选取一个地址并连接到Eureka服务器。然后,通过发送REST请求的方式(包含自身数据)将自身注册到该Eureka服务器上。Eureka服务器收到该请求后,会将请求附带的服务节点数据存储到一个两层Map中,第一层的key为服务名,也就是上述例子中的CRM-SERVICE
或者NEWS-SERVICE
,第二层key为服务的实例名称appName
,在源码中,这个Map定义在com.netflix.eureka.registry.AbstractInstanceRegistry
中:private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
其中,
InstanceInfo
包含了一个服务实例的元数据信息,含有30余个实例变量,记录了服务实例ID、服务实例名、所在IP地址、端口、主页URL、健康检查URL、状态检查URL、最近更新时间等数据字段。 -
服务同步
对于Eureka服务器集群,一个Eureka服务器在收到服务提供者的服务注册请求后,除了会将其生成InstanceInfo
保存在本地以外,还会将其转发给其它Eureka服务器实例,从而使各个Eureka服务器的服务注册清单达到同步。 -
续租
在默认情况下,Eureka客户端每隔20秒就会向Eureka服务器发送一个心跳包,来方便Eureka服务器判断这个服务有没有失效。如果超出90秒(默认情况)还没有收到这个服务发送的心跳包,Eureka服务器就会将服务从服务注册清单中删除。
服务消费者
服务消费者需要调用服务提供者的服务,所以与Eureka的交互大多体现在服务清单的获取和更新上:
- 服务获取
当服务消费者启动的时候,同样会根据配置文件中配置的Eureka服务器地址列表,随机选取一个Eureka服务器。然后,通过发送REST请求给Eureka服务器,来获取服务清单。并且在此之后,每隔30秒服务消费者都会向Eureka服务器主动请求服务清单。服务清单一般包含了服务提供者的元数据信息。 - 服务调用
服务消费者获取到服务清单后,就会在需要调用其它服务时根据这个清单来与服务提供者建立连接,互相通信。如果服务消费者有负载均衡的功能,并且同一个服务有多个相同的服务提供者,那么会轮询调用这些服务提供者。
服务注册中心
-
失效服务自动剔除
之前提到过,如果服务提供者由于种种原因宕机后,Eureka服务器就无法收到该服务发送的心跳包。Eureka默认每60秒检查一次服务提供者,对超过90秒没有收到心跳包的服务提供者会被服务注册中心剔除。 -
自我保护
在Eureka服务器运行期间,如果心跳包接收失败的比例在15分钟之内低于85%,Eureka服务器就会进入保护模式,不会删除服务清单中任何一个服务。这种机制也对服务消费者提了个要求:如果目标服务中的某个服务实例的确是失效了,那么需要采取“熔断”,即在这个服务实例恢复正常之前,不再调用这个服务实例,而是调用该服务的其它实例(如果有的话),如果该服务仅有一个服务实例并且失效,那么只能抛出一个异常。
当Eureka服务器进入自我保护模式之后,会在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.
可以将参数
eureka.server.enable-self-preservation
设为false
停用保护机制。
Eureka Region / Zone 概念
对于存在多个机房的Spring Cloud微服务集群,我们总是希望一个服务消费者能够调用当前机房的服务提供者,而不是调用其它机房的服务提供者,因为这样可能会造成比较的大的延时,影响系统的吞吐量和客户端的体验。所以,Eureka引入了Region/Zone的概念来区分不同机房的服务提供者。
Region可以理解为地理上的分区,比如北京、上海或者扩大到亚洲、北美都是可以的,没有大小的限制,可以自己根据整个Spring Cloud的微服务集群架构或者业务结构特点来划分不同的Region。
Zone可以理解为具体的机房。例如上海有多个机房,可以将不同的机房定义为不同的ID。可以看出Region和Zone是一对多的关系:
服务消费者B总是会尝试调用本机房的服务提供者,如果当前机房某一服务的全部服务提供者实例失效,那么就会尝试调用另外一个机房的服务。
如果需要使用Region/Zone策略,那么需要保证每个机房至少有一个Eureka服务器实例。
我们假设上述例子中,虹口区机房Eureka服务器实例主机名为hk.eureka
、浦东区机房Eureka服务器实例主机名为pd.eureka
。那么hk.eureka
的application.yml
配置为:
spring:
application:
name: shanghai-hk-eureka
server:
port: 8080
eureka:
instance:
hostname: hk.eureka #当前实例主机名
client:
register-with-eureka: true #将自己作为Eureka客户端来尝试注册自己,Eureka高可用必须
prefer-same-zone-eureka: true #是否采用优先选取同一Zone的服务的策略
fetch-registry: true # 从其它Eureka服务器实例获取服务,从而达到同步
region: shanghai # 当前Eureka的Region
availability-zones:
shanghai: hongkou, pudong #第一个为当前Eureka服务器的Zone
service-url: #上述Zone所对应的Eureka服务器实例地址
hongkou: http://${eureka.instance.hostname}:${server.port}/eureka
pudong: http://pd.eureka:8080/eureka
pd.eureka
的application.yml
配置为:
spring:
application:
name: shanghai-pd-eureka
server:
port: 8080
eureka:
instance:
hostname: pd.eureka
client:
register-with-eureka: true
prefer-same-zone-eureka: true
fetch-registry: true
region: shanghai
availability-zones:
shanghai: pudong, hongkou #注意这里的顺序
service-url:
hongkou: http://hk.eureka:8080/eureka
pudong: http://${eureka.instance.hostname}:${server.port}/eureka
虹口区机房CRM-SERVICE
(假定主机名为hk.crm.service
)配置:
spring:
application:
name: crm-service
server:
port: 8080
eureka:
instance:
hostname: hk.crm.service
metadata-map:
zone: hongkou #指定当前Zone
client:
register-with-eureka: true
prefer-same-zone-eureka: true
fetch-registry: true
region: shanghai
availability-zones:
shanghai: hongkou, pudong
service-url:
hongkou: http://hk.eureka:8080/eureka
pudong: http://pd.eureka:8080/eureka
按照上述规则配置好其它服务提供者,然后配置虹口区消费者B:
spring:
application:
name: feign
server:
port: 8080
eureka:
instance:
hostname: hk.feign
metadata-map:
zone: hongkou
client:
register-with-eureka: true
prefer-same-zone-eureka: true
fetch-registry: true
region: shanghai
availability-zones:
shanghai: hongkou, pudong
service-url:
hongkou: http://hk.eureka:8080/eureka
pudong: http://pd.eureka:8080/eureka
这样,虹口区机房消费者在通过访问http://CRM-SERVICE/
时是始终会优先选择当前机房的服务提供者。