Spring Cloud Eureka

一、Eureka简介:

从现在微服务框架的使用来看,毫无疑问,Spring Cloud 是目前微服务架构领域的翘楚。那么,对于他,你了解多少呢?

实际上,Spring Cloud 是一个全家桶式的技术栈,基于Spring Boot进行开发,它包含了很多组件。其中有5个最核心的组件 Eureka、Ribbon、Feign、Hystrix、Zuul 。本文主讲Eureka。

 

Eureka是Netflix 开源的服务发现组件,Spring Cloud 将其集成在 Spring Cloud Netflix中,主要负责完成微服务架构中的服务治理功能。Spring Cloud通过为Eureka增减了Spring Boot风格的自动化配置,只需要通过简单的引入依赖和注解配置,就能让Spring Boot构建的微服务应用与Eureka服务治理体系进行整合。

它主要包括两个组件:Eureka Server(注册中心服务端) 和 Eureka Client(注册中心客户端)

 

二、Eureka详细介绍

 

1、思考:什么是服务治理?

服务治理:

服务治理 是服务架构中最为核心的和基础的模块。服务治理主要用来实现各个服务实例的自动化注册和发现

多个服务进程在服务注册中心注册了自己的服务,服务注册中心需要维护一个服务清单,服务注册中心还需要以心跳的方式去监测清单中服务的可用性,如果不可用,将从服务清单中移出。

 

2、Eureka服务治理基础架构的三大要素

  • Eureka Server(服务注册中心)

Eureka Server是Eureka提供的服务端,提供服务注册与发现的功能。服务注册中心可以为集群,(集群就是两个或以上),形成高可用的eureka注册中心

Eureka的服务治理设计中,所有的节点既是服务的提供方,又是服务的消费方,服务注册中心也不例外。Eureka Server的高可用实际上是将自己作为服务向其他服务注册中心注册自己,

这样形成一组相互注册的服务注册中心

  • Service Provider(服务提供者)

Service Provider是提供服务的应用,可以是Spring Boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现。

 

  • Service Consumer(服务消费者)

Service Consumer是服务的消费者,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。通过客户端负载均衡某种算法轮询服务列表,然后调用其所需要的服务,也即

调用对应的服务提供者。为了性能的考虑,Eureka Server会维护一个只读的服务清单来返回给客户端,同时该缓存清单会默认每隔30s更新一次。

 

注意:在实际运行时,上述三大核心要素可能是两个,甚至一个实例。Eureka客户端通常包括 Service Provider(服务提供者) 和  Service Consumer(服务消费者)

 

思考:在实际运行时,这三者之间是如何交互的呢?接着看下面

 

3、Eureka服务调用

Spring Cloud Eureka_第1张图片

如上图所示,解释每个过程的含义:

1、服务注册:

Provider 在启动的时候会通过REST请求的方式将自己注册到Eureka Server(服务注册中心)上,同时带上自身服务的一些元数据信息。Eureka Server在接受到这个REST请求后,将元数据存储在一个双层结构的MAP中。其中第一层的key是服务名,第二层的key是具体服务的实例名。(Eureka信息面板中一个服务有多个实例的情况,这些内容就是以双层Map的形式存储)

注意:在服务注册时,需要确认一下eureka.client.register-with-eureka=true是否正确,如果为false是禁止向服务注册中心注册的。

2、服务同步:

服务提供者注册到了一个服务注册中心后,由于 多个服务注册中心相互注册为服务(高可用服务注册中心),当服务提供者发送注册请求到一个服务注册中心后,它会将该请求转发到其他相连的注册中心,从而实现注册中心之间的服务同步。这样就可以通过任何一台服务注册中心拿到服务提供者的服务信息。

3、服务发现 / 获取

服务注册在了服务注册中心,调用服务就不需要指定具体服务的实例地址,而是向服务名发起请求。但要注意,实际访问一个服务,还是访问的一个具体的服务实例。启动服务消费者时,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。以实现向具体的服务实例的访问。

为了性能的考虑,Eureka Server会维护一个只读的服务清单来返回给客户端,同时该缓存清单会每隔30s更新一次。 其中两个相关的配置:
eureka.client.fetch-registry=true
eureka.client.registry-fetch-interval-seconds=30 // 缓存清单的更新时间

4、服务调用 / 消费

当服务调用方需要调用服务的时候,便根据 服务清单以某种轮询策略取出一个位置来进行服务调用(客户端负载均衡)。

而服务消费的任务由Ribbon完成。Ribbon是一个基于HTTP和TCP的客户端负载均衡器,它可以通过客户端配置的 ribbonServerList 服务端列表以 轮询的访问方式 达到负载均衡的目的。Ribbon它在Eureka服务发现的基础上,实现了一套对服务实例的选择策略,从而实现服务的消费。

对于访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone, 每个服务客户端需要注册到一个Zone中,即每个服务客户端对应一个Region和一个Zone. 在进行服务调用时,优先访问处于同一个Zone中的服务提供方,若访问不到,就访问其他Zone。同一个Zone内又可以有多个实例。

思考:什么是一个Region? 什么是一个Zone?其具体应用见第五章

 

5、服务续约

服务提供者注册完服务后,服务提供者需要提供一个心跳检测用来持续告诉Eureka Server:『我还活着』。心跳间隔默认是30s, 防止注册中心剔除服务, 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。这个过程称为服务续约。

6、服务下线

当服务实例进行正常的关闭操作时,会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:『我要下线啦』。服务注册中心接受到请求后,将该服务状态置为下线(DOWN),并把该下线的事件传播出去。

 

7、服务剔除

服务实例并不一定会正常下线,比如内存溢出、网络故障等,服务注册中心并未收到下线的请求。为了从服务列表剔除这些失效了的服务,Eureka Server会在启动的时候创建一个定时任务,默认每隔一段时间(默认60s)将服务清单中超时(默认90s)没有续约的服务剔除。

(But, 再看下面的自我保护机制,并不会因为真正的剔除)

 

另外还需要了解的概念还有:

8、自我保护

服务注册中心的信息面板经常跳出的问题,这个警告就是触发了Eureka Server的自我保护机制。

 

『服务续约』中可以看到,服务注册到Eureka Server后,Provider会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间,会统计心跳失败比例在15min之内是否低于85%, 如果出现低于的情况,Eureka Server会将当前的实例注册信息保护起来,让这些实例不过期, 尽可能的保护这些注册信息。但是在这段事件内实例若出现问题,那么客户端就很容易拿到实际上已经不存在的服务实例,会出现调用失效的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。

相关配置:

eureka.server.enable-self-preservation=false // 关闭保护机制,确保服务注册中心可以将不可用的实例正确剔除

其默认配置是true

 

注意: kill + 端口号, 这是正常的下线,会使得Eureka控制面板中的服务立即去掉

            kill -9 +端口号, 这是非正常下线,Eureka控制面板中的服务不会去掉(我理解的这应该是 自我保护机制 > 服务剔除, 导致服务还在)

 

三、Eureka配置详解

Eureka的配置包括Eureka客户端的配置和Eureka服务端的配置。

Eureka服务端的配置可以看类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
这些参数均已eureka.server开头。

Eureka客户端的配置可以看类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
这些参数均已eureka.client开头

在Eureka治理体系中,主要分为客户端和服务端(服务注册中心)。在高可用环境中,服务注册中心也是一个微服务,特殊性在于它既作为一个客户端,又为集群中的其他客户端提供了服务注册功能。所以,Eureka客户端的配置对象存在于所有Eureka服务治理体系下的应用实例中

1、服务注册类的配置信息

org.springframework.cloud.netflix.eureka.EurekaClientConfigBean

(在源码分析中也看到DiscoverClient的依赖类EurekaDiscoveryClient,其中的一个实现类就是EurekaClientConfigBean)

其配置信息都是以 eureka.client开头,常用的配置,指定eureka的注册中心地址:

eureka.client.serviceUrl.defaultZone=http://peer1:11111/eureka/,http://peer2:11112/eureka/
eureka.client.enabled=true
eureka.client.shouldUnregisterOnShutdown=true

于是在这个类中搜索serviceUrl,看到比较有用的信息:

public static final String DEFAULT_ZONE = "defaultZone";

public static final String DEFAULT_PREFIX = "/eureka";

public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX + "/";

private Map serviceUrl = new HashMap<>();

 

{

   // 高可用环境下,url部分是以逗号分割的一个字符串

   this.serviceUrl.put(DEFAULT_ZONE, DEFAULT_URL);

}

 

/**

 * Flag to indicate that the Eureka client is enabled.

 */

private boolean enabled = true;

 

/**

 * Indicates whether the client should explicitly unregister itself from the remote server

 * on client shutdown.(指示客户端是否应在客户端关闭时从远程服务器显式注销自身)

 */

private boolean shouldUnregisterOnShutdown = true;

 

常用的配置:

 

 

2、服务实例类配置

org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean

常用的一个配置(服务注册中心的一个配置)

eureka.instance.hostname=peer1

EurekaInstanceConfigBean的配置信息中,大部分是对服务实例元数据的配置。

服务实例元数据: 指的是Eureka客户端在像服务注册中心发送注册请求时,用来描述自身服务信息的对象,其中包含一些标准话的元数据,比如服务名称、实例名称、实例IP、实例端口等用于服务治理的重要信息,以及一些用于负载均衡策略或是其他特殊用途的自定义元数据信息。

关于元数据的定义,就可以看com.netflix.appInfo.InstanceInfo类(配置信息通过EurekaInstanceConfigBean进行加载,但真正进行服务注册时会包装成InstanceInfo类)中详细定义,来了解原生Eureka对元数据的定义。

 

2.1、实例名配置:

InstanceInfo中的instanceId参数:区别同一服务中不同的实例。这样就可以不用通过修改端口号启动同一个服务的不同实例了。
eureka.instance.instanceId = ${spring.application.name}:${random.int}

 

2.2、端点配置:

 

2.3、健康检测

默认情况下,Eureka的各个服务实例的健康检测并不是通过spring-boot-actuator模块的/health端点来实现的,而是依靠客户端心跳的方式来保持服务实例的存活。

but , 这种情况存在什么问题吗?
要理解心跳检测,检测的时客户端程序是否正常工作,但是客户端能正常工作,不代表客户端就能正常提供服务,比如客户端需要需要依赖其他的资源,比如:数据库、缓存、消息代理等。如果我们的应用无法联通这些外部资源时,实际上已经不能对外提供服务了,但是客户端心跳程序依然在运行。

在Spring Cloud Eureka中,可以通过简单的配置,把Eureka的客户端健康检测交给spring-boot-actuator模块的 /health端点,以实现更加全面 的健康状态维护。

其配置步骤:

  • 在pom.xml中引入spring-boot-starter-actuator模块的依赖
  • 在applicaiton.properties中增减参数配配置:eureka.client.healthcheck.enabled=true  (但是在EurekaClientConfigBean并没有这个配置项)

But, Eureka的服务调用中,服务剔除功能,解释了服务注册中心如何将失效的服务剔除的过程,但是在加上自我保护机制,现在又有个/health检查,所以默认的应该是啥?

 

四、Eureka源码分析

见Euerka源码分析

五、Eureka自我实践

5.1 背景

用户量比较大或者用户地理位置分布范围很广的项目,一般都会有多个机房。这个时候如果上线springCloud服务的话,我们希望一个机房内的服务优先调用同一个机房内的服务,当同一个机房的服务不可用的时候,再去调用其它机房的服务,以达到减少延时的作用。

5.2 region和zone概念

eureka提供了region和zone两个概念来进行分区,这两个概念均来自于亚马逊的AWS(Amazon Web Services ):

  • region:可以简单理解为地理上的分区,比如亚洲地区,或者华北地区,再或者北京等等,没有具体大小的限制。根据项目具体的情况,可以自行合理划分region。
  • zone:可以简单理解为region内的具体机房,比如说region划分为北京,然后北京有两个机房,就可以在此region之下划分出zone1,zone2两个zone。

5.3 代码实践

参考的内容:https://segmentfault.com/a/1190000014107639

我准备了两个eureka server组成一个高可用的服务注册中心, 两个service provider, 一个consumer

1、Eureka Server-1:

配置:

server.port=11111

spring.application.name=eureka-server

 

eureka.client.register-with-eureka=true

eureka.client.fetch-registry=true

eureka.client.prefer-same-zone-eureka=true

eureka.client.region=shanghai

eureka.client.availability-zones.shanghai:zone1

eureka.client.service-url.zone1=http://peer1:11111/eureka/

eureka.client.service-url.zone2=http://peer2:11112/eureka/

 

eureka.instance.hostname=peer1

 

#eureka.client.serviceUrl.defaultZone=http://peer2:11112/eureka/

2、Eureka Server-2:

配置:

server.port=11112

spring.application.name=eureka-server

 

eureka.client.register-with-eureka=true

eureka.client.fetch-registry=true

eureka.client.prefer-same-zone-eureka=true

eureka.client.region=shanghai

eureka.client.availability-zones.shanghai:zone2

eureka.client.service-url.zone1=http://peer1:11111/eureka/

eureka.client.service-url.zone2=http://peer2:11112/eureka/

 

eureka.instance.hostname=peer2

 

#eureka.client.serviceUrl.defaultZone=http://peer1:11111/eureka/

 

说明:Eureka Server-1和Eureka Server-2的region都配置的shanghai, zone配置的分别是zone1和zone2

服务启动(同一个项目可以使用不同的配置文件启动多次):

@EnableEurekaServer

@SpringBootApplication

public class ApplicationEurekaHigh {

    public static void main(String[] args) {

        // 这个地方可以改变成peer1, 这样启动就使用peer2的配置文件

        String profile = "peer2";

        System.setProperty("spring.profiles.active", profile);

        System.setProperty("spring.config.location""classpath:application-" + profile + ".properties");

        SpringApplication.run(ApplicationEurekaHigh.class, args);

    }

}

 

3、Service-1:

配置:

server.port=8081

 

spring.application.name=hello-service

 

eureka.instance.prefer-ip-address=true

eureka.instance.metadata-map.zone=zone1

 

eureka.client.register-with-eureka=true

eureka.client.fetch-registry=true

eureka.client.prefer-same-zone-eureka=true

eureka.client.region=shanghai

eureka.client.availability-zones.shanghai=zone1,zone2

eureka.client.service-url.zone1=http://peer1:11111/eureka/

eureka.client.service-url.zone2=http://peer2:11112/eureka/

 

zone.name=zone1

 

#eureka.client.serviceUrl.defaultZone=http://peer1:11111/eureka/,http://peer2:11112/eureka/


4、Service-2:

配置:

server.port=8082

 

spring.application.name=hello-service

 

eureka.instance.prefer-ip-address=true

eureka.instance.metadata-map.zone=zone2

 

eureka.client.register-with-eureka=true

eureka.client.fetch-registry=true

eureka.client.prefer-same-zone-eureka=true

eureka.client.region=shanghai

eureka.client.availability-zones.shanghai=zone2,zone1

eureka.client.service-url.zone2=http://peer2:11112/eureka/

eureka.client.service-url.zone1=http://peer1:11111/eureka/

 

 

zone.name=zone2

 

#eureka.client.serviceUrl.defaultZone=http://peer1:11111/eureka/,http://peer2:11112/eureka/


Service-1和Service-2 服务提供的代码都如下:

@Slf4j

@RestController

@RequestMapping("/springBootTest")

public class HelloController {

    @Value("${zone.name}")

    private String zoneName;

 

    @Autowired

    private DiscoveryClient discoveryClient;

 

    @RequestMapping(value = "/hello", method = {RequestMethod.POST, RequestMethod.GET})

    public String hello() {

        ServiceInstance instance = discoveryClient.getLocalServiceInstance();

        log.info("/hello, host:" + instance.getHost() + ",  serviceId:" + instance.getServiceId());

        return "hello world, zonename=" + zoneName;

    }

}


5、Consumer-1:

配置:

spring.application.name=ribbon-consumer

server.port=8999

 

#注册服务的时候使用服务的IP地址

eureka.instance.prefer-ip-address=true

eureka.instance.status-page-url-path=/actuator/info

eureka.instance.health-check-url-path=/actuator/health

#服务消费者和服务提供者分别属于哪个zone

eureka.instance.metadata-map.zone=zone1

 

eureka.client.register-with-eureka=true

## 是否需要去检索服务,如果自身就是服务注册中心,就没有这个必要

eureka.client.fetch-registry=true

eureka.client.prefer-same-zone-eureka=true

eureka.client.region=shanghai

eureka.client.availability-zones.shanghai=zone1,zone2

eureka.client.service-url.zone1=http://localhost:11111/eureka/

eureka.client.service-url.zone2=http://localhost:11112/eureka/

服务调用的代码:

@Slf4j

@RestController

public class ConsumerController {

    @Resource

    RestTemplate restTemplate;

 

    @RequestMapping(value="/consumer", method=RequestMethod.GET)

    public String helloConsumer(){

        log.info("/hello, consumer.....");

        return restTemplate.getForEntity("http://hello-service/springBootTest/hello", String.class).getBody();

    }

}

 

5.4 配置文件讲解

当一个服务(作为一个eureka client)向注册中心(eureka server)注册的时候,会根据eureka.client下的配置来进行注册

思考:有多个注册中心的情况下,服务会注册到哪个注册中心,并且和哪个注册中心来维持心跳检测?

5.4.1 注册中心选择逻辑:

preferSameZoneEureka: 表明客户端是否使用处在同一个zone中的eureka server,因为延迟或者其他原因

  理想状态下,eureka客户端配置成和同一个zone中的server连接

1. 如果prefer-same-zone-eureka为false,按照service-url下的 list取第一个注册中心来注册,并和其维持心跳检测。不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。

(总结:false表示按照service-url中的顺序来注册)

2. 如果prefer-same-zone-eureka为true,先通过region取availability-zones内的第一个zone,然后通过这个zone取service-url下的list,并向list内的第一个注册中心进行注册和维持心跳,不会再向list内的其它的注册中心注册和维持心跳。只有在第一个注册失败的情况下,才会依次向其它的注册中心注册,总共重试3次,如果3个service-url都没有注册成功,则注册失败。每隔一个心跳时间,会再次尝试。

(总结:true, 如果每个zone下都只有一个实例,当向同zone下的服务注册中心注册失败时,还是会向其他zone中的服务注册中心注册,不会影响服务的调用

所以说,为了保证服务注册到同一个zone的注册中心,一定要注意availability-zones的顺序,必须把同一zone写在前面。即availability-zones中的第一个值和eureka.instance.metadata-map.zone配置的值保持一样

5.4.1 服务消费者调用服务

eureka.instance.metadata-map.zone=zone2 指定服务提供者或者消费者是属于哪个zone

服务消费者会先通过ribbon去注册中心拉取一份服务提供者的列表,然后通过eureka.instance.metadata-map.zone指定的zone进行过滤,过滤之后如果同一个zone内的服务提供者有多个实例,则会轮流调用。只有在同一个zone内的所有服务提供者都不可用时,才会调用其它zone内的服务提供者。

  • 验证『zone内的服务提供者有多个实例,则会轮流调用』:

两个服务注册中心的zone都设置成zone1, 让后调用服务消费者的接口,会发现Service-1和Service-2交替调用

  • 验证『优先调用同一个zone中的服务,都不可用时才调用其它zone内的服务提供者』

条件:两个服务注册中心的zone分别设置成zone1和zone2, Service-1的zone配置成zone1, Service-2的zone配置成zone2, Consumer-1的配置成zone1

会发现Consumer一致调用的是 Service-1。如果把 Service-1停掉,立即调用消费者的接口,会发现报错,过一段事件后,再调用消费者的接口,会发现调用的是 Service-2

 

       思考:为什么停掉 Service-1后理解调用消费者接口报错呢?

 

也就是说,当一个服务异常down掉后,90s之后注册中心才会知道这个服务不可用了。在此期间,依旧会把这个服务当成正常服务。ribbon调用仍会把请求转发到这个服务上。为了避免这段期间出现无法提供服务的情况,要开启ribbon的重试功能,去进行其它服务提供者的重试。

eureka.instance.lease-renewal-interval-in-seconds: 30

服务和注册中心的心跳间隔时间,默认为30s

eureka.instance.lease-expiration-duration-in-seconds: 90

服务和注册中心的心跳超时时间,默认为90s

六、开发中遇到的坑

Spring Cloud Ribbon使用服务名调用服务提供者异常 java.net.UnknownHostException

https://blog.csdn.net/makyan/article/details/88601507

 

你可能感兴趣的:(spring,cloud,eureka)