深度剖析服务发现组件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

深度剖析服务发现组件Eureka


深度剖析服务发现组件Eureka

图1

上图简要描述了Eureka的基本架构,由3个角色组成:

Eureka Server:提供服务注册和发现

Service Provider:服务提供方,将自身服务注册到Eureka,从而使服务消费方能够找到

Service Consumer:服务消费方,从Eureka获取注册服务列表,从而能够消费服务。

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

3.1.2 More in depth

深度剖析服务发现组件Eureka

图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的依赖。

org.springfr amework.cloud

spring-cloud-starter-eureka-server

1.2.0.RELEASE

3.2.3.2 启用Eureka Server

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

@EnableEurekaServer

@SpringBootApplication

publicclassEurekaServiceApplication{

publicstaticvoidmain(String[] args) {

SpringApplication.run(EurekaServiceApplication.class, args);

}

}

做完以上配置后,启动应用,Eureka Server就开始工作了!

启动完,打开http://localhost:8761,就能看到启动成功的画面了。

深度剖析服务发现组件Eureka

图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的依赖。

org.springfr amework.cloud

spring-cloud-starter-eureka

1.2.0.RELEASE

3.2.4.2 启动Service Provider

启用Service Provider非常简单,只需要加上@EnableDiscoveryClient即可。

@EnableDiscoveryClient

@SpringBootApplication

publicclassReservationServiceApplication{

publicstaticvoidmain(String[] args) {

newSpringApplicationBuilder(ReservationServiceApplication.class)

.run(args);

}

}

做完以上配置后,启动应用,Server Provider就开始工作了!

启动完,打开http://localhost:8761,就能看到服务已经注册到Eureka Server了。

深度剖析服务发现组件Eureka

图4

3.2.4.3 启动Service Consumer

启动Service Consumer其实和Service Provider一样,因为本质上Eureka提供的客户端是不区分Provider和Consumer的,一般情况下,Provider同时也会是Consumer。

@EnableDiscoveryClient

@SpringBootApplication

publicclassReservationClientApplication{

@Bean

CommandLineRunner runner(DiscoveryClient dc) {

returnargs -> {

dc.getInstances("reservation-service")

.forEach(si -> System.out.println(String.format(

"Found %s %s:%s", si.getServiceId(), si.getHost(), si.getPort())));

};

}

publicstaticvoidmain(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)做状态同步(异步操作)

深度剖析服务发现组件Eureka

图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定义如下:

privatefinal ConcurrentHashMap>>registry=

newConcurrentHashMap>>();

3.3.2 Renew

Renew(服务续约)操作由Service Provider定期调用,类似于heartbeat。主要是用来告诉Eureka Server Service Provider还活着,避免服务被剔除掉。接口实现如下图所示。

可以看到,接口实现方式和register基本一致:首先更新自身状态,再同步到其它Peer。

深度剖析服务发现组件Eureka

图6

3.3.3 Cancel

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

深度剖析服务发现组件Eureka

图7

3.3.4 Fetch Registries

Fetch Registries由Service Consumer调用,用来获取Eureka Server上注册的服务。

为了提高性能,服务列表在Eureka Server会缓存一份,同时每30秒更新一次。

深度剖析服务发现组件Eureka

图8

3.3.5 Eviction

Eviction(失效服务剔除)用来定期(默认为每60秒)在Eureka Server检测失效的服务,检测标准就是超过一定时间没有Renew的服务。

默认失效时间为90秒,也就是如果有服务超过90秒没有向Eureka Server发起Renew请求的话,就会被当做失效服务剔除掉。

失效时间可以通过eureka.instance.leaseExpirationDurationInSeconds进行配置,定期扫描时间可以通过eureka.server.evictionIntervalTimerInMs进行配置。

接口实现逻辑见下图:

深度剖析服务发现组件Eureka

图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。

Eureka的优势

1、在Eureka平台中,如果某台服务器宕机,Eureka不会有类似于ZooKeeper的选举leader的过程;客户端请求会自动切换到新的Eureka节点;当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理之中;而对于它来说,所有要做的无非是同步一些新的服务注册信息而已。所以,再也不用担心有“掉队”的服务器恢复以后,会从Eureka服务器集群中剔除出去的风险了。Eureka甚至被设计用来应付范围更广的网络分割故障,并实现“0”宕机维护需求。(多个zookeeper之间网络出现问题,造成出现多个leader,发生脑裂)当网络分割故障发生时,每个Eureka节点,会持续的对外提供服务(注:ZooKeeper不会):接收新的服务注册同时将它们提供给下游的服务发现请求。这样一来,就可以实现在同一个子网中(same side of partition),新发布的服务仍然可以被发现与访问。

2、正常配置下,Eureka内置了心跳服务,用于淘汰一些“濒死”的服务器;如果在Eureka中注册的服务,它的“心跳”变得迟缓时,Eureka会将其整个剔除出管理范围(这点有点像ZooKeeper的做法)。这是个很好的功能,但是当网络分割故障发生时,这也是非常危险的;因为,那些因为网络问题(注:心跳慢被剔除了)而被剔除出去的服务器本身是很”健康“的,只是因为网络分割故障把Eureka集群分割成了独立的子网而不能互访而已。

幸运的是,Netflix考虑到了这个缺陷。如果Eureka服务节点在短时间里丢失了大量的心跳连接(注:可能发生了网络故障),那么这个Eureka节点会进入”自我保护模式“,同时保留那些“心跳死亡“的服务注册信息不过期。此时,这个Eureka节点对于新的服务还能提供注册服务,对于”死亡“的仍然保留,以防还有客户端向其发起请求。当网络故障恢复后,这个Eureka节点会退出”自我保护模式“。所以Eureka的哲学是,同时保留”好数据“与”坏数据“总比丢掉任何”好数据“要更好,所以这种模式在实践中非常有效。

3、Eureka还有客户端缓存功能(注:Eureka分为客户端程序与服务器端程序两个部分,客户端程序负责向外提供注册与发现服务接口)。所以即便Eureka集群中所有节点都失效,或者发生网络分割故障导致客户端不能访问任何一台Eureka服务器;Eureka服务的消费者仍然可以通过Eureka客户端缓存来获取现有的服务注册信息。甚至最极端的环境下,所有正常的Eureka节点都不对请求产生相应,也没有更好的服务器解决方案来解决这种问题

时;得益于Eureka的客户端缓存技术,消费者服务仍然可以通过Eureka客户端查询与获取注册服务信息,这点很重要。

4、Eureka的构架保证了它能够成为Service发现服务。它相对与ZooKeeper来说剔除了Leader节点的选取或者事务日志机制,这样做有利于减少使用者维护的难度也保证了Eureka的在运行时的健壮性。而且Eureka就是为发现服务所设计的,它有独立的客户端程序库,同时提供心跳服务、服务健康监测、自动发布服务与自动刷新缓存的功能。但是,如果使用ZooKeeper你必须自己来实现这些功能。Eureka的所有库都是开源的,所有人都能看到与使用这些源代码,这比那些只有一两个人能看或者维护的客户端库要好。

5、维护Eureka服务器也非常的简单,比如,切换一个节点只需要在现有EIP下移除一个现有的节点然后添加一个新的就行。Eureka提供了一个web-based的图形化的运维界面,在这个界面中可以查看Eureka所管理的注册服务的运行状态信息:是否健康,运行日志等。Eureka甚至提供了Restful-API接口,方便第三方程序集成Eureka的功能。

来源参考:baike.ren*.com/2016-12/18938.html转载请注明出处。