服务治理是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。Spring Cloud Eureka是Spring Cloud Netflix微服务套件的一部分,基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能,包括服务注册和服务发现等。
Eureka是Netflix贡献的服务发现模块,本质来说是一个Restful服务,用于定位运行在AWS Region中的中间层服务,跟AWS紧密联系,由两个组件组成:Eureka Server和Eureka Client。Eureka Server是服务注册服务器,Eureka Client是一个java客户端,用来简化与服务器的交互,作为轮询负载均衡器,并提供服务的故障切换支持。
Netflix在其生产环境中使用的是另外的客户端,它基于流量、资源利用率以及出错状态的加权负载均衡。
默认情况下,Eureka使用Jersey和XStream配合JSON作为Server与Client之间的通讯协议。
1.Eureka通信交互
Eureka基本通信交互如下图所示:
上图包含以下几个操作:
Registry:每个微服务启动时,会通过Eureka client向Eureka server注册自己,server会保存该服务的信息。
Replicate:每个 Eureka Server 同时也是 Eureka Client(逻辑上的),多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用。
Remote Call:Eureka Client 会缓存 Eureka Server 中的信息,即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者。
Renew:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)。
Cancel:服务下线,Eureka客户端在程序关闭时向Eureka服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用DiscoveryManager.getInstance(). shutdownComponent();
Eviction:服务剔除,在默认的情况下,当Eureka客户端连续90秒没有向Eureka服务器发送服务续约,即心跳,Eureka服务器会将该服务实例从服务注册列表删除,即服务剔除。
2.Eureka Server
Eureka Server的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。可以采用两两注册的方式实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现。
Eureka Server在设计的时候就考虑了高可用设计,在Eureka服务治理设计中,所有节点既是服务的提供方,也是服务的消费方,服务注册中心也不例外。Eureka Server的高可用实际上就是将自己做为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
Eureka Server与Spring Cloud集成时,主要需要添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
2.1.安全认证
启动了Eureka Server,可以直接进入了spring cloud的服务治理页面,这么做在生产环境是极不安全的,我们需要给Eureka Server加上安全的用户认证,通过spring-security来实现。
加上以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
同时在配置中为serviceurl中加入安全校验信息,如下所示:
eureka.client.serviceUrl.defaultZone=http://<username>:<password>@${eureka.instance.hostname}:${server.port}/eureka/
2.2.失效剔除
有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障等原因使服务不能正常运作。而服务注册中心并未收到“服务下线”的请求,为了从服务列表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
2.3.自我保护
服务注册到Eureka Server后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间会统计心跳失败的比例在15分钟以之内是否低于85%,如果出现低于的情况,Eureka Server会将当前实例注册信息保护起来,让这些实例不会过期。这样做会使客户端很容易拿到实际已经不存在的服务实例,会出现调用失败的情况。因此客户端要有容错机制,比如请求重试、断路器。
这是自我保护相关的属性:eureka.server.enableSelfPreservation=true. 可以设置改参数值为false,以确保注册中心将不可用的实例删除
3.Eureka Client——服务提供者
Eureka Client与Spring Cloud集成时,主要需要添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
Eureka Client主要涉及以下流程。
3.1.服务注册
Eureka Server在启动的时候会通过REST请求的方式将自己注册到其他Eureka Server上,同时带上自身服务的一些元数据信息。Eureka Server接收到这个Rest请求之后,将元数据信息存储在一个双层结构的Map中,其中第一层的key是服务名。第二层的key 是具体服务的实例名。
在服务注册时,需要确认一下eureka.client.register-with-eureka=true参数是否正确,该值默认为true。若设置为fasle将不会启动注册操作。
3.2.服务同步
从eureka服务治理体系架构图中可以看到,不同的服务提供者可以注册在不同的服务注册中心上,它们的信息被不同的服务注册中心维护。
此时,由于多个服务注册中心互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他注册中心,从而实现服务注册中心之间的服务同步。通过服务同步,提供者的服务信息就可以通过集群中的任意一个服务注册中心获得。
3.3.服务续约
在注册服务之后,服务提供者会维护一个心跳用来持续高速Eureka Server,“我还在持续提供服务”,否则Eureka Server的剔除任务会将该服务实例从服务列表中排除出去。我们称之为服务续约。
下面是服务续约的两个重要属性:
(1)eureka.instance.lease-expiration-duration-in-seconds:leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。默认为90秒;如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了;如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉;该值至少应该大于leaseRenewalIntervalInSeconds。
(2)eureka.instance.lease-renewal-interval-in-seconds:leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。默认30秒。
4.Eureka Client——服务消费者
在整个服务治理体系中,作为Eureka Client出现,主要涉及以下流程:
4.1.获取服务
消费者服务启动时,会发送一个Rest请求给服务注册中心,来获取上面注册的服务清单。为了性能考虑,Eureka Server会维护一份只读的服务注册清单来返回给客户端,同时该缓存清单默认会每隔30秒更新一次。
下面是获取服务的两个重要的属性:
(1)eureka.client.fetch-registry:是否需要去检索寻找服务,默认是true
(2)eureka.client.registry-fetch-interval-seconds:表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
4.2.服务调用
服务消费者在获取服务清单后,通过服务名可以获取具体提供服务的实例名和该实例的元数据信息。因为有这些服务实例的详细信息,所以客户端可以根据自己的需要决定具体调用哪个实例,在Ribbon中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
4.3.服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭操作时,会触发一个服务下线的Rest服务请求给Eureka Server,告诉服务注册中心:“我要下线了。”服务端在接收到该请求后,将该服务状态置位下线(DOWN),并把该下线事件传播出去。