Spring Cloud之Eureka教程

1. 概念

Spring Cloud Netflix的五大组件(神兽):

  • Eureka:服务注册和发现,它提供了一个服务注册中心、服务发现的客户端,还有一个方便的查看所有注册的服务的界面。 所有的服务使用Eureka的服务发现客户端来将自己注册到Eureka的服务器上。

  • Zuul:网关,所有的客户端请求通过这个网关访问后台的服务。他可以使用一定的路由配置来判断某一个URL由哪个服务来处理。并从Eureka获取注册的服务来转发请求。

  • Ribbon:即负载均衡,Zuul网关将一个请求发送给某一个服务的应用的时候,如果一个服务启动了多个实例,就会通过Ribbon来通过一定的负载均衡策略来发送给某一个服务实例。

  • Feign:服务客户端,服务之间如果需要相互访问,可以使用RestTemplate,也可以使用Feign客户端访问。它默认会使用Ribbon来实现负载均衡。

  • Hystrix:监控和断路器。我们只需要在服务接口上添加Hystrix标签,就可以实现对这个接口的监控和断路器功能。

1.1 什么时服务治理

Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理

服务治理就是提供了微服务架构中各微服务实例的快速上线和下线,且保持各服务能正常通信的能力的方法总称。

服务治理的优点:

  • 更高的可用性:服务治理可以支持动态的服务实例集群实例集群环境,任何服务实例可以随时上线或者下线。并且当一个服务实例不可用时,治理服务器可以将请求转给其他服务提供者,当一个新的服务实例上线时,也能够快速地分担服务调用请求
  • 负载均衡:服务治理可以提供动态的负载均衡功能,可以将所有请求动态地分布到其管理的所有服务实例中进行处理
  • 提升易用的弹性:服务治理的客户端会定时从服务治理服务器复制一份服务实例信息缓存到本地中,这样即使当服务治理服务器不可用时,服务消费者也可以使用本地的缓存去访问响应的服务,而不至于中断服务,通过这种机制极大的提高了应用的弹性
  • 高可用性集群:可以构建服务治理集群,通过互相注册机制,将每个治理服务器所管理的服务信息列表进行交换,使得服务治理拥有更高的可用性

1.2 什么是Eureka

EurekaNetflix开源微服务框架中一系列项目中的一个,Spring cloud对其进行了二次封装,形成了Spring cloud Netflix子项目,但未对Netflix微服务实现原理进行更改,只是进行了
Spring Boot化,使开发者更容易使用和整合

Eureka中,对于服务治理有3个概念:

  1. 服务治理服务器(Eureka服务器) :Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,也就是服务注册中心(这里的Eureka Server指的是我们自己专门写一个Java应用来引用Eureka Server的依赖,将这个应用作为注册中心)。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
  1. 服务注册代理(服务提供者):如果一个微服务是一个服务提供者,那么可以通过服务注册代理将服务配置信息注册到治理服务器上。服务注册代理可以理解为一个Eureka客户端,负责将微服务所提供的服务向Eureka服务器执行注册、续约和注销等操作,以使服务消费者可以发现并进行消费。在服务注册时需要向服务治理服务器提供服务名称、宿主服务器IP地址、服务端口号、域名等主要数据
  1. 服务发现客户端(服务消费者):也是一个Eureka客户端。它在启动时会默认从所服务治理服务器中获取所有的服务注册表信息,通过所获取到的服务注册列表信息来消费相应的服务

1.3 Eureka组件

  • Eureka Server提供注册服务功能

    各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用的服务节点的信息;服务节点的信息可以在界面上直观看到

    1
    2
    3
    4
    5
    <!-- Eureka Server 依赖包 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  • EurekaClient通过注册中心进行访问

    EurekaClient可以分为提供者和消费者,他们都是个Java客户端,客户端同时也具备一个内置的、使用轮询(round-robin)负载均衡算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳
    (默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)

    1
    2
    3
    4
    5
    <!-- Eureka client 依赖包 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

1.4 什么场景使用Eureka

准确来说是什么场景需要使用注册中心:

大并发量的应用情况下就需要搭建集群,我们需要通过注册中心来实时掌握每个应用的情况,如果没有大并发场景,项目虽然拆分来服务,但是用不到集群,那么大可不必使用注册中心,从某种意义上讲,使用注册中心增加了程序的复杂度,而且还得维护注册中心,有点大材小用了。

1.5 Eureka停更

eureka目前已经在Netflix社区明确声明不在更新!而springCloud当中的eureka的starter仍然在更新,但是他更新只是为了兼容springcloud版本,并改变不了eureka已经停更的事实。

2. 案例代码

这里使用的是OpenFeign 简介和使用详解 的样例代码,具体结构如下:

里面包含了三个模块:

服务 描述 端口
eureka Eureka Server 8888
consume 服务消费者 8001
provider 服务提供者 8002

2.1 搭建Eureka Server

  1. 创建eureka模块

  2. 添加依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--eureka-server-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    <version>2.1.1.RELEASE</version>
    <exclusions>
    <exclusion>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
  3. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8888

    eureka:
    instance:
    hostname: localhost #eureka服务端实例名称

    client:
    register-with-eureka: false #表示不向注册中心注册自己
    fetch-registry: false #表示自己端就是注册中心,职责是维护服务实例,不需要检索服务
    service-url:
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  4. 启动类添加@EnableEurekaServer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * @author xiaoyuge
    */
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaApplication.class, args);
    }
    }
  5. 启动测试

2.2 搭建EurekaClient端的提供者

  1. 创建provider模块
  1. 添加依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8082
    spring:
    application:
    name: provider
    eureka:
    client:
    register-with-eureka: true #表示向注册中心注册自己
    fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
    defaultZone: http://localhost:8888/eureka
  3. 启动类添加注解@EnableEurekaClient

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * @author xiaoyuge
    */
    @SpringBootApplication
    @EnableEurekaClient
    public class ProviderApplication {
    public static void main(String[] args) {
    SpringApplication.run(ProviderApplication.class, args);
    }
    }
  4. 启动程序并访问Eureka Server

2.3 搭建EurekaClient端的消费者

  1. 创建consume模块
  1. 添加依赖
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  1. 添加配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8081
    spring:
    application:
    name: consume
    eureka:
    client:
    register-with-eureka: true #表示向注册中心注册自己
    fetch-registry: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
    defaultZone: http://localhost:8888/eureka
  1. 启动类添加注解@EnableEurekaClient
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * @author xiaoyuge
    */
    @EnableEurekaClient
    @SpringBootApplication
    @EnableFeignClients
    public class ConsumeApplication {
    public static void main(String[] args) {
    SpringApplication.run(ConsumeApplication.class, args);
    }
    }
  1. 启动程序并访问Eureka Server

3. Eureka集群

上面的代码没有Eureka服务照样可以运行使用,这也就是上面提到的,假如微服务不使用集群的话,完全没必要使用注册中心。下面我们进行分别搭建Eureka集群,和微服务集群。

3.1 Eureka集群原理说明

微服务RPC远程服务调用的核心:

高可用,如果只有一个注册中心,出现了故障则导致整个服务环境不可用,所以需要搭建Eureka集群

3.2 Eureka Server集群搭建

  1. 将上面的eureka模块改名为eureka_8888

  2. 新建模块eureka_9999

  3. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 9999

    eureka:
    instance:
    hostname: localhost #eureka服务端实例名称

    client:
    register-with-eureka: false #表示不向注册中心注册自己
    fetch-registry: false #表示自己端就是注册中心,职责是维护服务实例,不需要检索服务
    service-url:
    #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  4. 添加注解@EnableEurekaServer

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaServer
    public class Eureka2Application {
    public static void main(String[] args) {
    SpringApplication.run(Eureka2Application.class, args);
    }
    }
  5. 修改providerconsume两个模块的注册地址

    1
    defaultZone: http://localhost:8888/eureka,http://localhost:9999/eureka
  6. 测试

    • 先启动eureka_8888以及 eureka_9999
    • 再启动服务提供者provider8082
    • 再启动服务消费者consume8081
    • 查看服务消费者和提供者注册情况

3.2.1 Eureka Server集群问题

在上面的测试过程中发现,刚开始Eureka8888上没有注册服务,过了10分钟左右,出现了第一个服务provider,又过了半个小时才两个服务都注册上。
那么在这个过程中,如果Eureka8888挂了,consume消费者是无法调用provider服务的。

为什么都会优先注册?有说是配置文件的先后顺序问题,但是会出现一个注册中心有一个服务的情况,而且都注册在最后的注册中心节点上。

3.3 主机名称修改和IP提示

3.3.1 主机名称修改

1
2
3
eureka:
instance:
instance-id: provider8082 #主机名称修改

3.3.2 IP提示

当我们服务多了,有时候以应用名称无法快速锁定它在那台服务器部署,所以可以添加以下配置:

1
2
3
4
eureka:
instance:
instance-id: provider8082
prefer-ip-address: true #优先IP地址

3.4 服务发现Discovery

对于注册到Eureka里面的微服务,可以通过服务发现来获取该服务的信息,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

/**
* @author xiaoyuge
*/
@RestController
public class ExampleController {

@Resource
DiscoveryClient discoveryClient;

@GetMapping("/discovery")
public void discovery() {
//获取注册的服务
List<String> services = discoveryClient.getServices();
for (String element : services) {
System.out.println(element);
}
//根据服务名称获取服务详细信息
List<ServiceInstance> instances = discoveryClient.getInstances("PROVIDER");
for (ServiceInstance si : instances) {
System.out.println(si.getServiceId() + "\t" + si.getHost() + "\t" + si.getPort() + "\t" + si.getUri());
}
}
}

4. Eureka心跳机制

4.1 什么是心跳机制

使用注册中心,那我们的服务信息就需要注册到注册中心,注册中心起到的作用是服务治理,那他就需要时刻掌握服务是否可用,心跳机制就是Eureka Client
一旦注册到注册中心后,就需要每隔一段时间告诉Eureka Server我是活的。

4.2 Eureka Client心跳频率设置

在Eureka Client启动的时候,都会有一个HeartbeatThread的心跳线程,这个线程的作用就是保证默认30秒的时候向EurekaServer发送一个信息的请求,告诉Eureka Server当前Client还存活着。

下面这个参数可以设置心跳间隔时间

1
2
3
eureka:
instance:
lease-renewal-interval-in-seconds: 30

4.3 Eureka Server端收到心跳后的操作

EurekaServer 在接收到请求之后,先去自己的注册表中,找到请求的对应服务器信息,在这个服务器信息里面有个Lease的对象,
这里面就是可以进行心跳续约操作的,更新对象里面的LastUpdateTimestamp时间戳,每一次接收到都会更新这个时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Lease<T> {
public static final int DEFAULT_DURATION_IN_SECS = 90;
private T holder;
private long evictionTimestamp;
private long registrationTimestamp;
private long serviceUpTimestamp;
private volatile long lastUpdateTimestamp;
private long duration;

public void renew() {
this.lastUpdateTimestamp = System.currentTimeMillis() + this.duration;
}

public void cancel() {
if (this.evictionTimestamp <= 0L) {
this.evictionTimestamp = System.currentTimeMillis();
}
}

public void serviceUp() {
if (this.serviceUpTimestamp == 0L) {
this.serviceUpTimestamp = System.currentTimeMillis();
}
}
public boolean isExpired(long additionalLeaseMs) {
return this.evictionTimestamp > 0L || System.currentTimeMillis() > this.lastUpdateTimestamp + this.duration + additionalLeaseMs;
}
//..........省略getter/setter

static enum Action {
Register,
Cancel,
Renew;

private Action() {
}
}
}

4.4 Eureka Server查看心跳是否收到

Eureka Server在启动的时候会每60秒遍历一次注册表信息,然后查看注册表中的信息是否有过期的,如果过期会假如到一个列表里面单独处理。

清理间隔时间配置(单位:毫秒,默认是60*1000, 在EurekaServer端配置)

1
2
3
eureka:
server:
eviction-interval-timer-in-ms: 60

4.5 Eureka Client心跳最大等待时间

Eureka服务端在收到最后一次心跳后等待时间上线,单位为秒(默认90秒),超时将剔除服务。通过下面配置可以修改等待时间(Eureka Client端配置)

1
2
3
eureka:
instance:
lease-expiration-duration-in-seconds: 90

4.6 总结

通过以上了解,一共接触来三个时间配置,通过租房的形式描述如下:

  • 注册中心扫描过期时间(60秒)-Server端配置: 房东要看哪些租客的租约到期了,需要收回

  • 心跳间隔时间(30秒)-Client端配置: 租客每个一段时间就告诉房东,我要还续租

  • 心跳最大等待时间(90秒)-Client端配置: 如果租客超过这个时间没有跟房东续约,那就默认不租了

1
2
3
4
5
6
7
eureka:
instance:
lease-expiration-duration-in-seconds: 90 #租期到期时间
lease-renewal-interval-in-seconds: 30 #租期更新时间

server: #Server端配置
eviction-interval-timer-in-ms: 60 # 清理间隔

5. Eureka自我保护机制

Eureka自我保护机制有很多bug,而且中文资料很缺乏

5.1 什么是自我保护机制

保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护,为了防止Eureka Client之间可以正常调用;但是与Eureka Server网络不畅通情况下(网络延迟等原因),在保护模式开始的情况下,Eureka Server不会立刻将Client服务剔除。

如果在Eureka Server的页面看到以下这段提示,则说明Eureka 进入了保护模式:

这个需要在注册中心Server端配置的:

1
2
3
4
eureka:
server:
#关闭自我保护机制,保证不可用服务被及时踢除,默认为true开启的
enable-self-preservation: false

5.2 为什么会出现自我保护机制

某时刻某个服务因网络原因不可用了,Eureka不会立刻清除,依旧会对该微服务的信息进行保存,属于CAP里面的AP

因为网络通信是可能恢复的,但是Eureka客户端只会在启动是才会服务端注册。如果因为网络原因而剔除了客户端,将造成客户端无法再注册到服务端,
为了避免该问题,Eureka提供了自我保护机制。

CAP:Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性)

服务清理条件:

  • Eureka服务端会检查最近15分钟内所有Eureka实例正常心跳占比(这15分钟是再源码当中有个每15分钟执行一次的定时任务),如果低于85%就会触发自我保护机制。
  • 触发了保护机制,Eureka将暂时把这些失效的服务保护起来,不让其过期,但这些服务并不是永远不会过期(该现象可能出现再网络不同但是EurekaClient未出现宕机)
  • Eureka在启动完成后,每隔60秒会检查一次服务健康状态
  • 如果这些被保护起来失效的服务过一段时间后(默认90秒,这是上面提到的心跳最大等待时间)还是没有恢复,就会把这些服务剔除。如果在此期间服务恢复了并且实例心跳占比高于85%,就会自动关闭自我保护机制。
  • 如果注册中心在一定时间内没有收到心跳就将服务剔除,会出现严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的。

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

5.3 如何选择自我保护机制?

Eureka服务端默认情况下是会开启自我保护机制的。但我们在不同环境应该选择是否开启保护机制。

自我保护机制的配置:eureka.server.enable-self-preservation=true

一般情况下,我们会选择开发环境关闭保护机制,而在生产环境下启动自我保护机制

开发环境下,我们我们启动的服务数量较少而且会经常修改重启。如果开启自我保护机制,很容易触发Eureka客户端心跳占比低于85%的情况。使得Eureka不会剔除我们的服务,从而在我们访问的时候,会访问到可能已经失效的服务,导致请求失败,影响我们的开发。

在生产环境下,我们启动的服务多且不会反复启动修改。环境也相对稳定,影响服务正常运行的人为情况较少。适合开启自我保护机制,让Eureka进行管理。

5.4 Eureka控制台参数

  • Lease expiration enable: Eureka自动保护机制启动后该值为false

  • Renews threshold: Eureka Server每分钟收到客户端实例续约的阈值

    Renews threshold = 服务实例总数 * (60 / 续约间隔)* 自我保护续约百分比阈值因子(阈值因子默认85%, 续约间隔默认是30)

  • Renews(last min):Eureka Server最后1分钟收到客户端实例续约总数.

    Renews(last min) = 服务实例总数 * (60 / 续约间隔(默认30))

如果自我保护模式开启了,且当续约阈值 > 0,上一分钟的续约数 > 阈值,那么可以清理。
言外之意是:当上一分钟续约数 < 阈值,就不清理(进行保护)Renews(last min) < Renews threshold

6. Eureka健康检查

由于server和client通过心跳保持服务状态,而只有状态为UP的服务才能被访问

比如心跳一致正常,服务也是UP,但是此服务连不上,无法提供正常服务

  • 需要将微服务状态也同步到Server

  • 只需要启动eureka的健康检查

  • 这样微服务就会将自己的健康状态同步到Eureka

这个需要在Eureka client端配置,而且默认是开始的。

1
2
3
4
eureka:
client:
healthcheck:
enabled: true

刚开始我以为是只要这个设置true,然后当服务的mysql挂掉后,会自动更新注册中心的状态,然后可以避免其他服务调用异常的服务,然而并不是,他还需要配置很多东西,并没有我想象的那么神奇!

版权声明:本文为CSDN博主「怪 咖@」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43888891/article/details/125325794