深度剖析服务发现组件Netflix Eureka

一、背景介绍

Eureka是Netflix开源的一款提供服务注册和发现的产品。
其官方文档中对自己的定义是:
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.

我们在研发Apollo配置中心时(https://github.com/ctripcorp/apollo),考虑到配置中心是基础服务,有非常高的可用性要求,为了更好地支持服务动态扩容、缩容、失效剔除等特性,所以就选择了使用Eureka来提供服务注册和发现功能。
  本着“当你选择一款开源产品后,你就应当对它负责,既要信任它又要挑战它”的原则,我花了一些时间比较深入地研究了Eureka的实现细节(好在Eureka的实现短小精悍,通读源码也没花太多时间),今天就来详细介绍一下。

二、Why Eureka?

那么为什么我们在项目中使用了Eureka呢?我大致总结了一下,有以下几方面的原因:
  1. 它提供了完整的Service Registry和Service Discovery实现
  首先是提供了完整的实现,并且也经受住了Netflix自己的生产环境考验,相对使用起来会比较省心。
  2. 和Spring Cloud无缝集成
  我们的项目本身就使用了Spring Cloud和Spring Boot,同时Spring Cloud还有一套非常完善的开源代码来整合Eureka,所以使用起来非常方便。
  另外,Eureka还支持在我们应用自身的容器中启动,也就是说我们的应用启动完之后,既充当了Eureka的角色,同时也是服务的提供者。这样就极大地提高了服务的可用性。
这一点是我们选择Eureka而不是zk、etcd等的主要原因,为了提高配置中心的可用性和降低部署复杂度,我们需要尽可能地减少外部依赖。

3. Open Source
  最后一点是开源,由于代码是开源的,所以非常便于我们了解它的实现原理和排查问题。
  三、Dive into Eureka
  相信大家看到这里,已经对Eureka有了一个初步的认识,接下来我们就来深入了解下它吧:
  3.1 Overview
  3.1.1 Basic Architecture


图片描述

图1

上图简要描述了Eureka的基本架构,由3个角色组成:
Eureka Server:提供服务注册和发现
Service Provider:服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到
Service Consumer:服务消费方,从Eureka获取注册服务列表,从而能够消费服务。

需要注意的是,上图中的3个角色都是逻辑角色。在实际运行中,这几个角色甚至可以是同一个实例,比如在我们项目中,Eureka Server和Service Provider就是同一个JVM进程。

3.1.2 More in depth


图片描述

图2

上图更进一步的展示了3个角色之间的交互。
Service Provider会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作;
Eureka Server之间会做注册服务的同步,从而保证状态一致;
Service Consumer会向Eureka Server获取注册服务列表,并消费服务。

3.2 Demo
  为了给大家一个更直观的印象,我们可以通过一个简单的demo来实际运行一下,从而对Eureka有更好的了解。
  3.2.1 Git Repository
  Git仓库:https://github.com/nobodyiam/spring-cloud-in-action
  这个项目使用了Spring Cloud相关类库,包括:
Spring Cloud Config
Spring Cloud Eureka (Netflix)

3.2.2 准备工作
  Demo项目使用了Spring Cloud Config做配置,所以第一步先在本地启动Config Server。
  由于项目基于Spring Boot开发,所以直接运行com.nobodyiam.spring.cloud.in.action.config.ConfigServerApplication
即可。
  3.2.3 Eureka Server Demo
  Eureka Server的Demo模块名是:eureka-server

  3.2.3.1 Maven依赖
  eureka-server
是一个基于Spring Boot的Web应用,我们首先需要做的就是在pom中引入Spring Cloud Eureka Server的依赖。

<dependency>
<groupId>org.springfr amework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>

3.2.3.2 启用Eureka Server
  启用Eureka Server非常简单,只需要加上@EnableEurekaServer
即可。

@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}}

做完以上配置后,启动应用,Eureka Server就开始工作了!
  启动完,打开http://localhost:8761,就能看到启动成功的画面了。

图片描述

图3

3.2.4 Service Provider and Service Consumer Demo

Service Provider的Demo模块名是:reservation-service

  Service Consumer的Demo模块名是:reservation-client

3.2.4.1 Maven依赖
  reservation-service
和reservation-client
都是基于Spring Boot的Web应用,我们首先需要做的就是在pom中引入Spring Cloud Eureka的依赖。

<dependency>
<groupId>org.springfr amework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>

3.2.4.2 启动Service Provider
  启用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了。

图片描述

图4

3.2.4.3 启动Service Consumer
  启动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
服务。
  3.3 Eureka Server实现细节
  看了前面的demo,我们已经初步领略到了Spring Cloud和Eureka的强大之处,通过短短几行配置就实现了服务注册和发现!
  相信大家一定想了解Eureka是如何实现的吧,所以接下来我们继续Dive!首先来看下Eureka Server的几个对外接口实现。
  3.3.1 Register
  首先来看Register(服务注册),这个接口会在Service Provider启动时被调用来实现服务注册。同时,当Service Provider的服务状态发生变化时(如自身检测认为Down的时候),也会调用来更新服务状态。
  接口实现比较简单,如下图所示。
ApplicationResource
类接收Http服务请求,调用PeerAwareInstanceRegistryImpl
的register
方法
PeerAwareInstanceRegistryImpl
完成服务注册后,调用replicateToPeers
向其它Eureka Server节点(Peer)做状态同步(异步操作)

图片描述

图5

注册的服务列表保存在一个嵌套的hash map中:
第一层hash map的key是app name,也就是应用名字
第二层hash map的key是instance name,也就是实例名字

以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>>>();

3.3.2 Renew
  Renew(服务续约)操作由Service Provider定期调用,类似于heartbeat。主要是用来告诉Eureka Server Service Provider还活着,避免服务被剔除掉。接口实现如下图所示。
  可以看到,接口实现方式和register基本一致:首先更新自身状态,再同步到其它Peer。


图片描述

图6

3.3.3 Cancel
  Cancel(服务下线)一般在Service Provider shut down的时候调用,用来把自身的服务从Eureka Server中删除,以防客户端调用不存在的服务。接口实现如下图所示。


图片描述

图7

3.3.4 Fetch Registries
  Fetch Registries由Service Consumer调用,用来获取Eureka Server上注册的服务。
  为了提高性能,服务列表在Eureka Server会缓存一份,同时每30秒更新一次。


图片描述

图8

3.3.5 Eviction
  Eviction(失效服务剔除)用来定期(默认为每60秒)在Eureka Server检测失效的服务,检测标准就是超过一定时间没有Renew的服务。
  默认失效时间为90秒,也就是如果有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当做失效服务剔除掉。
  失效时间可以通过eureka.instance.leaseExpirationDurationInSeconds
进行配置,定期扫描时间可以通过eureka.server.evictionIntervalTimerInMs
进行配置。
  接口实现逻辑见下图:


图片描述

图9

3.3.6 How Peer Replicates
  在前面的Register、Renew、Cancel接口实现中,我们看到了都会有replicateToPeers操作,这个就是用来做Peer之间的状态同步。
  通过这种方式,Service Provider只需要通知到任意一个Eureka Server后就能保证状态会在所有的Eureka Server中得到更新。
  具体实现方式其实很简单,就是接收到Service Provider请求的Eureka Server,把请求再次转发到其它的Eureka Server,调用同样的接口,传入同样的参数,除了会在header中标记isReplication=true,从而避免重复的replicate。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容