java框架之SpringCloud(3)-Eureka服务注册与发现

上一章节完成了一个简单的微服务案例,下面就经过在这个案例的基础上集成 Eureka 来学习 Eureka。html

介绍

概述

Eureka 是 Netflix 的一个子模块,也是核心模块之一。Eureka 是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来讲是很是重要的,有了服务发现与注册,只须要使用服务的标识符,就能够访问到服务。功能相似于 dubbo的注册中心,好比 Zookeeper。java

SpringCloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册与发现,Eureka 采用了 C/S 的设计架构。web

Eureka Server 做为服务注册功能的服务器,它是服务注册中心。而系统中的其它微服务,使用 Eureka 的客户端链接到 Eureka Server 并维持心跳链接。这样系统的维护人员就能够经过 Eureka Server 来监控系统中的各个微服务是否正常运行。SpringCloud 的一些其它模块(好比 Zuul)就能够经过 Eureka Server 来发现系统中的其它微服务,并执行相关逻辑。算法

基本架构

Eureka Server 包含两个组件:spring

  • Eureka Server :提供服务注册的服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 EurekaServer 中的服务注册表中将会存储全部可用服务节点的信息,服务节点的信息能够在界面中直观的看到。
  • Eureka Client:是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具有一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向 Eureka Server 发送心跳(默认周期为 30 秒)。若是 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点一处(默认 90 秒)。

三大角色

  • Eureka Server:提供服务注册与发现。
  • Service Provider:服务提供方将自身服务注册到 Eureka,从而是服务消费方可以找到。
  • Service Consumer:服务消费方从 Eureka 获取到注册服务列表,从而可以获取并消费服务。

架构图

图 1:Eureka 架构图apache

图 2:Dubbo 架构图浏览器

使用

EurekaServer端

一、新建名为 "microservicecloud-eureka-7001" 的子工程做为 Eureka 服务端,依赖以下:安全

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>microservicecloud</artifactId>
        <groupId>zze.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservicecloud-eureka-7001</artifactId>

    <dependencies>
        <!--eureka-server服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <!-- 修改后当即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
</project>
pom.xml

二、配置 Eureka :服务器

server:
  port: 7001

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

  client:
    register-with-eureka: false # 表示不向注册中心注册本身
    fetch-registry: false # false 表示本身就是注册中心,不须要检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 设置与 Eureka Server 交互的地址,可用来查询注册的服务
application.yml

三、编写主启动类,并使用注解开启 Eureka Server 功能:网络

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer // 开启 Eureka Server 功能,标识当前程序就是一个 Eureka Server
public class Application_7001 {

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

四、测试:

运行主启动类,浏览器访问 http://localhost:7001/ 进入 Eureka Server 图形化页面:

test
@EnableDiscoveryClient 和 @EnableEurekaClient:
  • 共同点:都是可以让注册中心可以发现,扫描到该服务。
  • 不一样点:@EnableEurekaClient 只适用于 Eureka 做为注册中心,@EnableDiscoveryClient 能够是其余注册中心。 从Spring Cloud Edgware开始,@EnableDiscoveryClient 或@EnableEurekaClient 可省略。只需加上相关依赖,并进行相应配置,便可将微服务注册到服务发现组件上。

客户端Provider注册

一、修更名为 "microservicecloud-provider-dept-8001" 的工程的 pom 文件,添加以下 Eureka 客户端依赖:

<!--Eureka 客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
pom.xml

二、将当前工程做为 Eureka 客户端注册到 Eureka 服务,在配置文件添加以下配置:

eureka:
  client: # 将当前工程做为 Eureka 客户端
    service-url:
      defaultZone: http://localhost:7001/eureka # Eureka 服务端地址
application.yml

三、在主启动类添加注解标识当前工程为 Eureka 客户端:

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient // 标注当前工程为 Eureka 客户端
public class Application_8001 {
    public static void main(String[] args) {
        SpringApplication.run(Application_8001.class, args);
    }
}
zze.springcloud.Application_8001

四、测试:

先启动 7001 Eureka 服务端工程,再启动 8001 Eureka 客户端工程,浏览器访问 http://localhost:7001/,能够看到 8001 客户端实例已经被注册到 Eureka 服务端:

test

服务发现

服务发现实际上就是让 EurekaServer 端可以扫描到咱们注册的服务,默认咱们能够经过 Web UI 的方式查看哪些服务注册到了 EurekaServer,还能够经过 Eureka 客户端依赖提供的服务发现客户端获取注册到 EurekaServer 的服务信息。

一、修更名为 "microservicecloud-consumer-dept-80" 的服务工程添加以下 Eureka 客户端依赖:

<!--Eureka 客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

二、在其主启动类上添加注解启用 Eureka 客户端功能:

package zze.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class Application_80 {

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

三、修改 Controller:

package zze.springcloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import zze.springcloud.entities.Dept;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/consumer/dept")
public class DeptController {
    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/add")
    public boolean add(@RequestBody Dept dept) {
        return restTemplate.postForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/add", dept,Boolean.class);
    }

    @GetMapping("/get/{id}")
    public Dept get(@PathVariable Long id){
        return restTemplate.getForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/get/" + id, Dept.class);
    }

    @GetMapping("/list")
    public List<Dept> list(){
        return restTemplate.getForObject(getRestUrlPrefix("MICROSERVICECLOUD-PROVIDER-DEPT") + "/dept/list", List.class);
    }

    // 服务发现客户端
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 经过服务名称获取到服务实例对应的 url
     */
    private String getRestUrlPrefix(String serviceName){
        List<String> services = discoveryClient.getServices();
        System.out.println("---------------------------"+services);
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        System.out.println(instances);
        // 好比只注册了一个 MICROSERVICECLOUD-PROVIDER-DEPT 微服务实例
        ServiceInstance serviceInstance = instances.get(0);
        return String.format("http://%s:%s", serviceInstance.getHost(), serviceInstance.getPort());
    }

    /**
     * 获取全部注册到 EurekaServer 的服务信息
     * @return
     */
    @GetMapping("/discovery")
    public Object discovery(){
        Map<String, Object> map = new HashMap<>();
        // 获取全部注册到 EurekaServer 的微服务名称,对应 spring.application.name
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            // 获取对应服务全部实例
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            map.put(service, instances);
        }
        return map;
    }

}
zze.springcloud.controller.DeptController

四、测试:

正常访问 http://localhost/consumer/dept/list:

访问 http://localhost/consumer/dept/discovery 查看全部服务信息:

test

即经过服务发现咱们只须要使用约定的服务名称就能够经过注册中心访问到具体服务信息。

EurekaServer集群搭建

一、新建两个子工程做为两个 EurekaServer 端,分别名为 "microservicecloud-eureka-7002"、"microservicecloud-eureka-7003",主启动类分别名为 "Application_7002"、"Application_7003",依赖同 "microservicecloud-eureka-7001"。

二、因为是单机测试,需修改本机 host 映射:

127.0.0.1 www.eurekaserver1.com
127.0.0.1 www.eurekaserver2.com
127.0.0.1 www.eurekaserver3.com

三、分别修改 700一、700二、7003 的配置文件:

server:
  port: 7001

eureka:
  instance:
    hostname: www.eurekaserver1.com # eureka 服务端实例名称

  client:
    register-with-eureka: false # 表示不向注册中心注册本身
    fetch-registry: false # false 表示本身就是注册中心,不须要检索服务
    service-url:
      defaultZone: http://www.eurekaserver2.com:7002/eureka/,http://www.eurekaserver3.com:7003/eureka/
application.yml#7001
server:
  port: 7002

eureka:
  instance:
    hostname: www.eurekaserver2.com # eureka 服务端实例名称

  client:
    register-with-eureka: false # 表示不向注册中心注册本身
    fetch-registry: false # false 表示本身就是注册中心,不须要检索服务
    service-url:
      defaultZone: http://www.eurekaserver1.com:7001/eureka/,http://www.eurekaserver3.com:7003/eureka/
application.yml#7002
server:
  port: 7003

eureka:
  instance:
    hostname: www.eurekaserver3.com # eureka 服务端实例名称

  client:
    register-with-eureka: false # 表示不向注册中心注册本身
    fetch-registry: false # false 表示本身就是注册中心,不须要检索服务
    service-url:
      # 单机版
      # defaultZone: http://${e ureka.instance.hostname}:${server.port}/eureka/ # 设置与 Eureka Server 交互的地址,可用来查询注册的服务
      defaultZone: http://www.eurekaserver1.com:7001/eureka/,http://www.eurekaserver2.com:7002/eureka/
application.yml#7003

四、修改 8001 客户端 Eureka 配置,让客户端注册到多个 Eureka 服务端:

eureka:
  client: # 将当前工程做为 Eureka 客户端
    service-url:
      # 单机版
      # defaultZone: http://localhost:7001/eureka # Eureka 服务端地址
      defaultZone: http://www.eurekaserver1.com:7001/eureka,http://www.eurekaserver2.com:7002/eureka,http://www.eurekaserver3.com:7003/eureka
  instance:
    instance-id: microservicecloud-provider-dept
    prefer-ip-address: true # 访问路径显示 IP
application.yml

五、测试:

依次启动 700一、700二、7003 EurekaServer 服务,接着再启动 8001 客户端服务,客户端服务能够正常访问。
访问 http://www.eurekaserver1.com:7001/


访问 http://www.eurekaserver2.com:7002/


访问 http://www.eurekaserver3.com:7003/:

test

由测试结果可知,客户端服务已经注册到了多个 Eureka 服务端,每一个 Eureka 服务端上又挂载了其它 Eureka 服务端的副本,Eureka 集群搭建成功。

补充

actuator与信息完善

上面咱们经过访问 Eureka 的 Web 页看到以下界面:

服务名称修改

该界面是描述的是有哪些 Eureka 客户端实例注册到了当前 Eureka 服务端,说明以下:

  • Application 栏对应工程配置中的 spring.application.name 属性,即说明这个客户端是属于哪一个工程。
  • 而 Status 栏则表示相应 Eureka 客户端实例的标识,能够经过 eureka.instance.instance-id 属性进行修改。

IP信息提示

Status 栏所显示的实例名称是能够点击的,它所跳转的页面为 随机域名:端口/info ,以下:

若是咱们但愿将这个地址的随机域名改成 IP 地址,则能够在配置文件中修改 eureka.instance.prefer-ip-address 属性值为 true 实现:

Info内容构建

为方便咱们使用,Eureka 容许咱们经过点击客户端实例访问实例的详细信息,对应路径为 /info ,但咱们访问时会发现会返回 404 以下:

其实 SpringBoot 自己提供的监控功能就能够帮咱们解决这个问题,须要在客户端工程中引入监控相关依赖:

<!--监控信息-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

使用 maven 资源插件,让项目属性加载到项目环境变量中:

<build>
    <finalName>microservicecloud</finalName>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <delimiters>
                    <delimit>$</delimit>
                </delimiters>
            </configuration>
        </plugin>
    </plugins>
</build>

接着咱们就能够在客户端配置文件中添加 info 相关配置,例如:

info:
  host: ${java.rmi.server.hostname}
  port: ${server.port}
  app.name: microservicecloud-provider-dept-8001
  build.artifactId: ${project.artifactId}
  build.version: ${project.version}

重启客户端程序,从新访问 /info :

自我保护机制

当咱们在作上述测试的时候,咱们可能会发现 Eureka 页可能会显示以下红字:

这个其实就是由于 Eureka 的自我保护机制引发的。默认状况下,若是 EurekaServer 在必定时间内没有接收到某个微服务实例的心跳,EurekaServer 将会注销该实例(默认为 90 秒)。可是当网络分区故障时,微服务与 EurekaServer之间没法正常通讯,以上行为可能就变得很是危险了——由于微服务自己实际上是健康的,此时本不该该注销这个微服务。Eureka 经过“自我保护模式”来解决这个问题:当 EurekaServer 节点在短期内丢失过多的客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer 就会保护服务注册表中的信息,再也不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障回复后,EurekaServer 节点会自动退出自我保护模式。

在自我保护模式中,EurekaServer 会保护服务注册表中的信息,不注销任何服务实例。当它收到的心跳数从新恢复到阈值以上时,该 EurekaServer 就会自动退出自我保护模式。它的设计哲学就是宁肯保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例,其目的是遵循 AP 原则。

关于 CAP 原则可参考【CAP 定理的含义

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

在 SpringCloud Eureka 服务端工程中,能够经过 eureka.server.enable-self-preservation = false 来禁用自我保护模式。

Eureka VS Zookeeper

做为服务注册中心,Eureka 比 Zookeeper 好在哪里?

著名的 CAP 理论指出,一个分布式系统不可能同时知足 C(一致性)、A(可用性)和 P(分区容错性)。因为分区容错性 P 是在分布式系统中必须保证的,所以咱们只能在 A 和 C 之间权衡。

所以 Zookeeper 保证的是 CP,而 Eureka 则是保证 AP。

Zookeeper 保证 CP:
当向注册中心查询服务列表时,咱们能够容忍注册中心返回的是几分钟之前的注册信息,但不能接受服务直接 down 掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。可是 Zookeeper 会出现这样一种状况,当 master 节点由于网络故障与其它节点失去联系时,剩余节点会从新进行 leader 选举。问题在于,选举 leader 的时间太长(30 - 120s),且选举期间整个 Zookeeper 集群都是不可用的,这就致使在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 Zookeeper 失去 master 节点是较大几率会发生的事,虽然服务在最终可以恢复,可是漫长的选举时间致使的注册长期不可用是不能容忍的。
Eureka 保证 AP:
Eureka 看明白了这一点,所以在设计时就优先保证可用性。Eureka 各个节点都是平等的,几个节点挂掉不会影响正常节点的工做,剩余节点依然能够提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册师若是发现链接失败,则会自动切换至其它节点,只要有一台 Eureka 服务还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此以外,Eureka 还有一种自我保护机制,若是在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时就会出现以下几种状况:
  1. Eureka 再也不从注册列表中移除由于长时间没收到心跳而应该过时的服务。
  2. Eureka 仍然可以接受新服务的注册和查询请求,可是不会被同步到其它节点上(即保证当前节点依然可用)。
  3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中。
所以, Eureka 能够很好的应对因网络故障致使部分节点失去联系的状况,而不会向 Zookeeper 那样使整个注册服务瘫痪。
相关文章
相关标签/搜索