SpringCloud基础系列——注册中心 Eureka

347 阅读9分钟

1. Eureka简介

Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件

它主要包括两个组件:Eureka Server 和 Eureka Client

  • Eureka Client:一个Java客户端,用于简化与Eureka Server 的交互(通常就是微服务中的客户端和服务端)
  • Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)

2. 客户端与服务端的关系

  • 同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的), 多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用
  • 识别:Eureka Client 会缓存 Eureka Server 中的信息, 即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者
  • 续约:微服务会周期性(默认30s)地向 Eureka Server, 发送心跳以Renew(续约)信息(类似于heartbeat)
  • 续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能, 它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点

3. Eureka的使用

1. Eureka Server

  1. 如果使用idea开发,可以直接使用软件自带的spring initlazizer创建

查看pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  1. 激活Eureka服务器相关配置

在入口文件添加注解

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

bootstrap.yml

  • bootstrap.yml(bootstrap.properties)用来在程序引导时执行,应用于更加早期配置信息读取,如可以使用来配置application.yml中使用到参数等
  • application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。
  • bootstrap.yml 先于 application.yml 加载
server:
  port: 8761
spring:
  application:
    name: eureka-server-test

eureka:
  instance:
    hostname: eureka-server
  client:
    # 默认既是client又是server
    # 此实例是否从注册中心获取注册信息
    fetch-registry: false
    # 是否将此实例注册到注册中心
    register-with-eureka: false
    # 注册地址
    service-url:
      # 默认注册分区地址
      defaultZone: https://${eureka.instance.hostname}:${server.port}/eureka/
  server:
      # 同步为空时,等待时间
      wait-time-in-ms-when-sync-empty: 0
      # 是否开启自我保护机制
      ## 在分布式系统设计里头,通常需要对应用实例的存活进行健康检查,这里比较关键的问题就是要处理好网络偶尔抖动或短暂不可用时造成的误判。另外Eureka Server端与Client端之间如果出现网络分区问题,在极端情况下可能会使得Eureka Server清空部分服务的实例列表,这个将严重影响到Eureka server的 availibility属性。因此Eureka server引入了SELF PRESERVATION机制。
      ## Eureka client端与Server端之间有个租约,Client要定时发送心跳来维持这个租约,表示自己还存活着。 Eureka通过当前注册的实例数,去计算每分钟应该从应用实例接收到的心跳数,如果最近一分钟接收到的续约的次数小于指定阈值的话,则关闭租约失效剔除,禁止定时任务剔除失效的实例,从而保护注册信息。
      # 此处关闭可以防止问题(测试环境可以设置为false):Eureka server由于开启并引入了SELF PRESERVATION模式,导致registry的信息不会因为过期而被剔除掉,直到退出SELF PRESERVATION模式才能剔除。
      enable-self-preservation: false
      # 指定 Eviction Task 定时任务的调度频率,用于剔除过期的实例,此处未使用默认频率,频率为:5/秒,默认为:60/秒
      # 有效防止的问题是:应用实例异常挂掉,没能在挂掉之前告知Eureka server要下线掉该服务实例信息。这个就需要依赖Eureka server的EvictionTask去剔除。
      eviction-interval-timer-in-ms: 5000
      # 设置read Write CacheMap的expire After Write参数,指定写入多长时间后过期
      # 有效防止的问题是:应用实例下线时有告知Eureka server下线,但是由于Eureka server的REST API有response cache,因此需要等待缓存过期才能更新
      response-cache-auto-expiration-in-seconds: 60
      # 此处不开启缓存,上方配置开启一个即可
      # use-read-only-response-cache: false
      # 指定每分钟需要收到的续约次数的阈值,默认值就是:0.85
      renewal-percent-threshold: 0.85

Eureka 有一个 Region 和 Zone 的概念,你可以理解为现实中的大区(Region)和机房(Zone)

Eureka Client 在启动时需要指定 Zone,它会优先请求自己 Zone 的 Eureka Server 获取注册列表

同样的,Eureka Server 在启动时也需要指定 Zone,如果没有指定的话,其会默认使用 defaultZone application.yml

eureka:
  client:
    healthcheck:
      enabled: true
  1. 测试 http://localhost:8761

2. 添加一个服务提供者和一个消费者,两者都是Eureka Client

provider

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 不加可能会报错 Completed shut down of DiscoveryClient -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

application.yml

server:
  port: 8001
spring:
  application:
    name: demo-provider
      #cloud:
      #inetutils:
      #忽略docker0网卡以及 veth开头的网卡
      #ignored-interfaces:
      #- docker0
      #- veth.*
      #使用正则表达式,使用指定网络地址
      #preferred-networks:
      #- 192.168
    #- 10.0
eureka:
  instance:
    #配置主机名
    hostname: demo-provider
    instance-id: ${spring.application.name}:${server.port}
    # 设置微服务调用地址为IP优先
    prefer-ip-address: true
    # 设置心跳时间,即服务续约间隔时间,默认30s
    lease-renewal-interval-in-seconds: 30
    # 设置服务续约到期时间,默认90s
    lease-expiration-duration-in-seconds: 90
  client:
    service-url:
      #配置Eureka Server zone
      default-zone: http://localhost:8761/eureka/

入口

@EnableEurekaClient
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

UserController

一个http接口,相当于dubbo的provider

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping("/getName/{name}")
    public String getName(@PathVariable("name") String name) {
        return String.format("this word: %sis you entered", name);
    }
}
consumer

此处只为演示,实际不会这样调用

server:
  port: 8002
spring:
  application:
    name: demo-consumer1
      #cloud:
      #inetutils:
      #忽略docker0网卡以及 veth开头的网卡
      #ignored-interfaces:
      #- docker0
      #- veth.*
      #使用正则表达式,使用指定网络地址
      #preferred-networks:
    #- 192.168
    #- 10.0
eureka:
  instance:
    #配置主机名
    hostname: demo-consumer1
    instance-id: ${spring.application.name}:${server.port}
    # 设置微服务调用地址为IP优先
    prefer-ip-address: true
    # 设置心跳时间,即服务续约间隔时间,默认30s
    lease-renewal-interval-in-seconds: 30
    # 设置服务续约到期时间,默认90s
    lease-expiration-duration-in-seconds: 90
  client:
    service-url:
      #配置Eureka Server zone
      default-zone: http://localhost:8761/eureka/

config

@Configuration
public class RestConfiguration {
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

UserController

@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    DiscoveryClient discoveryClient;

    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/getName/{name}")
    public String getName(@PathVariable("name") String name) {
        List<ServiceInstance> serviceInstanceList = this.discoveryClient.getInstances("DEMO-PROVIDER");
        String uri = "";

        for (ServiceInstance instance : serviceInstanceList) {
            if (Objects.nonNull(instance.getUri())) {
                uri = instance.getUri().toString();
                break;
            }
        }

        return restTemplate.getForObject(uri + "/user/getName/" + name, String.class);
    }
}

Eureka总结

  • EurekaServer 提供服务发现的能力,各个微服务启动时,会向EurekaServer注册自己的信息(例如:ip、端口、微服务名称等),EurekaServer会存储这些信息;
  • EurekaClient是一个Java客户端,用于简化与EurekaServer的交互; 微服务启东后,会定期性(默认30s)的向EurekaServer发送心跳以续约自己的“租期”;
  • 如果EurekaServer在一定时间内未接收某个微服务实例的心跳,EurekaServer将会注销该实例(默认90s);
  • 默认情况下,EurekaServer同时也是EurekaClient。多个EurekaServer实例,互相之间通过复制的方式,来实现服务注册表中数据的同步;
  • EurekaClient也会缓存服务注册表中的信息;

综上,Eureka通过心跳检查、客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性,所以作为一个微服务架构,需要一个服务注册中心来统筹管理服务

Eureka 与 Zookeeper 的区别

两者都可以充当注册中心的角色,且可以集群实现高可用,相当于小型的分布式存储系统。

CAP 理论

CAP 分别为 consistency(强一致性)、availability(可用性) 和 partition toleranc(分区容错性)。

理论核心:一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性这三个需求。因此,根据 CAP 原理将 NoSQL 数据库分成满足 CA 原则、满足 CP 原则和满足 AP 原则三大类:

  • CA:单点集群,满足一致性,可用性的系统,通常在可扩展性上不高
  • CP: 满足一致性,分区容错性的系统,通常性能不是特别高
  • AP: 满足可用性,分区容错性的系统,通过对一致性要求较低

简单的说:CAP 理论描述在分布式存储系统中,最多只能满足两个需求。

Zookeeper 保证 CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟前的注册信息,但不能接受服务直接挂掉不可用了。因此,服务注册中心对可用性的要求高于一致性。

但是,zookeeper 会出现一种情况,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举。问题在于,选举 leader 的时间较长,30 ~ 120 秒,且选举期间整个 zookeeper 集群是不可用的,这期间会导致注册服务瘫痪。在云部署的环境下,因网络问题导致 zookeeper 集群失去 master 节点的概率较大,虽然服务能最终恢复,但是漫长的选举时间导致注册服务长期不可用是不能容忍的。

Eureka 保证 AP

Eureka 在设计上优先保证了可用性。EurekaServer 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和发现服务。

而 Eureka 客户端在向某个 EurekaServer 注册或发现连接失败时,会自动切换到其他 EurekaServer 节点,只要有一台 EurekaServer 正常运行,就能保证注册服务可用,只不过查询到的信息可能不是最新的。

除此之外,EurekaServer 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 EurekaServer 将认为客户端与注册中心出现网络故障,此时会出现一下几种情况:

  • EurekaServer 不再从注册列表中移除因为长时间没有收到心跳而应该过期的服务
  • EurekaServer 仍然能够接收新服务的注册和查询请求,但不会被同步到其他节点上

当网络稳定时,当前 EurekaServer 节点新的注册信息会同步到其他节点中 因此,Eureka 可以很好的应对因网络故障导致部分节点失去联系的情况,而不会向 Zookeeper 那样是整个注册服务瘫痪。