2016年 06月 25日node
因为项目中使用了Eureka作服务注册和发现,因此最近花了一些时间比较深刻的研究了一下Eureka,今天就来介绍一下它的实现细节。git
其官方文档中对本身的定义是:github
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers. We call this service, the Eureka Server. Eureka also comes with a Java-based client component,the Eureka Client, which makes interactions with the service much easier. The client also has a built-in load balancer that does basic round-robin load balancing.spring
简单来讲Eureka就是Netflix开源的一款提供服务注册和发现的产品,而且提供了相应的Java客户端。数据库
那么为何咱们在项目中使用了Eureka呢?我大体总结了一下,有如下几方面的缘由:缓存
相信你们看到这里,已经对Eureka有了一个初步的认识,接下来咱们就来深刻了解下它吧~架构
上图简要描述了Eureka的基本架构,由3个角色组成:app
须要注意的是,上图中的3个角色都是逻辑角色。在实际运行中,这几个角色甚至能够是同一个实例,好比在咱们项目中,Eureka Server和Service Provider就是同一个JVM进程。ide
上图更进一步的展现了3个角色之间的交互。性能
为了给你们一个更直观的印象,咱们能够经过一个简单的demo来实际运行一下,从而对Eureka有更好的了解。
Git仓库:git@github.com:nobodyiam/spring-cloud-in-action.git
这个项目使用了Spring Cloud相关类库,包括:
Demo项目使用了Spring Cloud Config作配置,因此第一步先在本地启动Config Server。
因为项目基于Spring Boot开发,因此直接运行com.nobodyiam.spring.cloud.in.action.config.ConfigServerApplication
便可。
Eureka Server的Demo模块名是:eureka-server
。
eureka-server
是一个基于Spring Boot的Web应用,咱们首先须要作的就是在pom中引入Spring Cloud Eureka Server的依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.1.0.RC2</version> </dependency>
启用Eureka Server很是简单,只须要加上@EnableEurekaServer
便可。
@EnableEurekaServer @SpringBootApplication public class EurekaServiceApplication { public static void main(String[] args) { SpringApplication.run(EurekaServiceApplication.class, args); } }
作完以上配置后,启动应用,Eureka Server就开始工做了!
启动完,打开http://localhost:8761就能看到启动成功的画面了。
Service Provider的Demo模块名是:reservation-service
。
Service Consumer的Demo模块名是:reservation-client
。
reservation-service
和reservation-client
都是基于Spring Boot的Web应用,咱们首先须要作的就是在pom中引入Spring Cloud Eureka的依赖。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.1.0.RC2</version> </dependency>
启用Service Provider很是简单,只须要加上@EnableDiscoveryClient
便可。
@EnableDiscoveryClient @SpringBootApplication public class ReservationServiceApplication { public static void main(String[] args) { new SpringApplicationBuilder(ReservationServiceApplication.class) .run(args); } }
作完以上配置后,启动应用,Server Provider就开始工做了!
启动完,打开http://localhost:8761就能看到服务已经注册到Eureka Server了。
启动Service Consumer其实和Service Provider同样,由于本质上Eureka提供的客户端是不区分Provider和Consumer的,通常状况下,Provider同时也会是Consumer。
@EnableDiscoveryClient @SpringBootApplication public class ReservationClientApplication { @Bean CommandLineRunner runner(DiscoveryClient dc) { return args -> { dc.getInstances("reservation-service") .forEach(si -> System.out.println(String.format( "Found %s %s:%s", si.getServiceId(), si.getHost(), si.getPort()))); }; } public static void main(String[] args) { SpringApplication.run(ReservationClientApplication.class, args); } }
上述代码中经过dc.getInstances("reservation-service")
就能获取到当前全部注册的reservation-service
服务。
看了前面的demo,咱们已经初步领略到了Spring Cloud和Eureka的强大之处,经过短短几行配置就实现了服务注册和发现!
相信你们必定想了解Eureka是如何实现的吧,因此接下来咱们继续Dive!首先来看下Eureka Server的几个对外接口实现。
首先来看Register(服务注册),这个接口会在Service Provider启动时被调用来实现服务注册。同时,当Service Provider的服务状态发生变化时(如自身检测认为Down的时候),也会调用来更新服务状态。
接口实现比较简单,以下图所示。
ApplicationResource
类接收Http服务请求,调用PeerAwareInstanceRegistryImpl
的register
方法PeerAwareInstanceRegistryImpl
完成服务注册后,调用replicateToPeers
向其它Eureka Server节点(Peer)作状态同步注册的服务列表保存在一个嵌套的hash map中:
以3.2.4.2中的截图为例,RESERVATION-SERVICE
就是app name,jason-mbp.lan:reservation-service:8000
就是instance name。
Hash map定义以下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
Renew(服务续约)操做由Service Provider按期调用,相似于heartbeat。主要是用来告诉Eureka Server Service Provider还活着,避免服务被剔除掉。接口实现以下图所示。
能够看到,接口实现方式和register
基本一致:首先更新自身状态,再同步到其它Peer。
Cancel(服务下线)通常在Service Provider shut down的时候调用,用来把自身的服务从Eureka Server中删除,以防客户端调用不存在的服务。接口实现以下图所示。
Fetch Registries由Service Consumer调用,用来获取Eureka Server上注册的服务。
为了提升性能,服务列表在Eureka Server会缓存一份,同时每30秒更新一次。
Eviction(失效服务剔除)用来按期在Eureka Server检测失效的服务,检测标准就是超过必定时间没有Renew的服务。
默认失效时间为90秒,也就是若是有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当作失效服务剔除掉。
失效时间能够经过eureka.instance.leaseExpirationDurationInSeconds
进行配置,按期扫描时间能够经过eureka.server.evictionIntervalTimerInMs
进行配置。
接口实现逻辑见下图:
在前面的Register、Renew、Cancel接口实现中,咱们看到了都会有replicateToPeers操做,这个就是用来作Peer之间的状态同步。
经过这种方式,Service Provider只须要通知到任意一个Eureka Server后就能保证状态会在全部的Eureka Server中获得更新。
具体实现方式其实很简单,就是接收到Service Provider请求的Eureka Server,把请求再次转发到其它的Eureka Server,调用一样的接口,传入一样的参数,除了会在header中标记isReplication=true,从而避免重复的replicate。
那你们可能会有疑问,Eureka Server是怎么知道有多少Peer的呢?
Eureka Server在启动后会调用EurekaClientConfig.getEurekaServerServiceUrls
来获取全部的Peer节点,而且会按期更新。按期更新频率能够经过eureka.server.peerEurekaNodesUpdateIntervalMs
配置。
这个方法的默认实现是从配置文件读取,因此若是Eureka Server节点相对固定的话,能够经过在配置文件中配置来实现。
若是但愿能更灵活的控制Eureka Server节点,好比动态扩容/缩容,那么能够override getEurekaServerServiceUrls
方法,提供本身的实现,好比咱们的项目中会经过数据库读取Eureka Server列表。
具体实现以下图所示:
最后再来看一下一个新的Eureka Server节点加进来,或者Eureka Server重启后,如何来作初始化,从而可以正常提供服务。
具体实现以下图所示,简而言之就是启动时把本身当作是Service Consumer从其它Peer Eureka获取全部服务的注册信息。而后对每一个服务,在本身这里执行Register,isReplication=true,从而完成初始化。
如今来看下Service Provider的实现细节,主要就是Register、Renew、Cancel这3个操做。
Service Provider要对外提供服务,一个很重要的步骤就是把本身注册到Eureka Server上。
这部分的实现比较简单,只须要在启动时和实例状态变化时调用Eureka Server的接口注册便可。须要注意的是,须要确保配置eureka.client.registerWithEureka
=true。
Renew操做会在Service Provider端按期发起,用来通知Eureka Server本身还活着。 这里有两个比较重要的配置须要注意一下:
eureka.instance.leaseRenewalIntervalInSeconds
Renew频率。默认是30秒,也就是每30秒会向Eureka Server发起Renew操做。
eureka.instance.leaseExpirationDurationInSeconds
服务失效时间。默认是90秒,也就是若是Eureka Server在90秒内没有接收到来自Service Provider的Renew操做,就会把Service Provider剔除。
具体实现以下:
在Service Provider服务shut down的时候,须要及时通知Eureka Server把本身剔除,从而避免客户端调用已经下线的服务。
逻辑自己比较简单,经过对方法标记@PreDestroy
,从而在服务shut down的时候会被触发。
这里你们疑问又来了,Service Provider是怎么知道Eureka Server的地址呢?
其实这部分的主体逻辑和3.3.7 How Peer Nodes are Discovered几乎是同样的。
也是默认从配置文件读取,若是须要更灵活的控制,能够经过override getEurekaServerServiceUrls
方法来提供本身的实现。按期更新频率能够经过eureka.client.eurekaServiceUrlPollIntervalSeconds
配置。
Service Consumer这块的实现相对就简单一些,由于它只涉及到从Eureka Server获取服务列表和更新服务列表。
Service Consumer在启动时会从Eureka Server获取全部服务列表,并在本地缓存。须要注意的是,须要确保配置eureka.client.shouldFetchRegistry
=true。
因为在本地有一份缓存,因此须要按期更新,按期更新频率能够经过eureka.client.registryFetchIntervalSeconds
配置。
Service Consumer和Service Provider同样,也有一个如何知道Eureka Server地址的问题。
其实因为Service Consumer和Service Provider本质上使用的是同一个Eureka客户端,因此这部分逻辑是同样的,这里就再也不赘述了。详细信息见3.4.4节。
本文主要介绍了Eureka的实现思路,经过深刻了解Eureka Server、Service Provider、Service Consumer的实现,咱们清晰地看到了服务注册、发现的一系列过程和实现方式。
相信对正在使用Eureka的同窗会有一些帮助,同时但愿对暂不使用Eureka的同窗也能有必定的启发,毕竟服务注册、发现仍是比较基础和通用的,了解了实现方式后,在使用上应该能更驾轻就熟一些吧~