SpringCloud系列(二)Eureka详解

Eureka简介

Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于REST服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件。

SpringCloud封装了Netflix公司开发的Eureka模块来实现服务注册和发现。Eureka采用了C-S的设计架构。EurekaServer作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心路连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行。SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑。

Eureka的基本架构

在这里插入图片描述

  • Eureka:注册中心,相当于管理者
  • ApplicationService:提供服务,相当于生产者
  • ApplicationClient:用户客户端,相当于消费者

Eureka简单实现

这里使用的均为 properties文件,如果你喜欢使用 yml 风格,推荐一个神器 在线yml和properties文件互转

新建一个 SpringCloud 项目,此项目基于 SpringBoot 2.2.2.RELEASE 版本

再创建3个子模块,一个生产者,一个消费者,一个注册中心

在这里插入图片描述

父工程

pom.xml

<?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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fu.springcloud</groupId>
    <artifactId>springcloud</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

生产者

新建一个 porducer,端口设置为 8081

在这里插入图片描述

项目架构如图所示,简单 crud 代码不再展示,只展示核心配置代码

pom.xml

<dependencies>
    <!-- Eureka客户端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    ......
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

application.properties

server.port=8081
# 服务的名字,注册到注册中心的名字,消费者来根据名字调用服务 可以重复
spring.application.name=springcloud-producer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID  不可以重复  标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-producer-8181

启动类

@SpringBootApplication
@EnableEurekaClient		// 需要此注解,启用Eureka客户端
public class SpringCloud01Producer {
    public static void main(String[] args) {
        System.out.println("生产者服务启动...8081");
        SpringApplication.run(SpringCloud01Producer.class, args);
    }
}

Controller

@RestController
@Scope("prototype")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("list")
    public List<User> queryUserList() {
        return userService.queryUserList();
    }
}

消费者

在这里插入图片描述

pom.xml

<dependencies>
    <!-- Eureka客户端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <!-- http远程调用的协议 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>3.9.0</version>
    </dependency>
    ......
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

application.properties

server.port=8080
spring.application.name=springcloud-consumer
# EurekaServer地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8888/eureka
# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true
# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1
# 执行当前服务的应用ID  不可以重复  标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-consumer-8080

RestTemplateConfig.java

@SpringBootConfiguration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
    }
}

启动类

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

Controller

@RestController
@Scope("prototype")
public class UserController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("queryUserList")
    public List queryUserList() {
        // 使用如下代码可以:不使用 Eureka,也能直接远程调用生产者
//        String url = "http://127.0.0.1:8081/list";
//        return restTemplate.getForObject(url, List.class);

        // 获取所有远程服务的调用地址
        List<String> services = discoveryClient.getServices();
        System.out.println("注册中心上服务列表有: " + services);
        // 通过服务名获取调用的服务信息
        List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-producer");
        for(ServiceInstance element: instances){
            System.out.println(element.getServiceId()+"\t"+element.getHost()+"\t"+element.getPort()+"\t" +element.getUri());
        }
        if (instances.size() == 0) {
            return null;
        }
        // 因为现在是单体服务,并不是集群模式,所以只可能有一个服务,直接调用第一个即可
        ServiceInstance instance = instances.get(0);
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/list";
        return this.restTemplate.getForObject(url, List.class);
    }
}

注册中心

properties文件用於单个 注册中心,yml文件用于Eureka集群

这里使用的是properties文件

在这里插入图片描述

pom.xml

<dependencies>
    <!-- Eureka服务端 -->
    <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>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

application.properties

server.port=8888
spring.application.name=eureka-server

# 注册中心主机名
eureka.instance.hostname=127.0.0.1
# 是否将自己注册到EurekaServer,默认是true
eureka.client.fetch-registry=false
# 是否进行检测,false: 因为自己是注册中心,不需要去检索服务信息
eureka.client.register-with-eureka=false
# 注册地址 多节点用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

启动类

@SpringBootApplication
@EnableEurekaServer		// 表示这是一个 Eureka的服务端,即注册中心
public class SpringCloud01EurekaServer {
    public static void main(String[] args) {
        System.out.println("Eureka服务启动...8888");
        SpringApplication.run(SpringCloud01EurekaServer.class, args);
    }
}

测试

先启动Eureka注册中心,再启动生产者和消费者。

在这里插入图片描述

查看eureka的控制面板,这两个服务都已经注册进去了

在这里插入图片描述

访问消费者的控制器,可以正常获取到数据

在这里插入图片描述

Eureka自我保护机制

什么是Eureka自我保护机制

官方解释: 自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。

默认情况下,Eureka Client会定时的向 Eureka Server端发送心跳包,默认是30s发送一次,目的是告诉 Eureka Server当前客户端实例还处于存活状态,如果Eureka server在一定时间内没有收到实例的心跳,便会把该实例从注册表中删除(默认是90秒),但是,如果短时间内丢失大量的实例心跳,便会触发Eureka server的自我保护机制。

官方解释总是这么晦涩难懂,接下来使用一个实际场景,助你理解Eureka的自我保护机制。

启动项目完成,打开eureka的控制面板,可以看到如下内容

在这里插入图片描述

现在我们停止了 Consumer 服务,刷新页面

在这里插入图片描述

可以看到,出现了一个警告:紧急情况!Eureka可能不正确地声称实例在不在的情况下出现。续订小于阈值,因此不会为了安全而过期实例。

但是并没有把consumer服务剔除掉,而是使用了缓存,将consumer读取出来。

有可能这是因为网络波动的缘故,导致Eureka没有及时的收到 consumer 服务的心跳,这时候就需要Eureka的自我保护机制。如果没有自我保护,直接把consumer服务给剔除了,那网络连接稳定的时候,又需要重新将服务注册到Eureka上,这显然是非常消耗性能的。

关闭Eureka自我保护机制

为什么要关闭

这么好的功能为什么要关闭呢?

在生产环境下,肯定是要开启的。

但是在开发环境下,我们需要不停的测试各种代码,难免会一直重启服务,但是 Eureka 服务一般不会更改,也就不必要重启;我们在进行测试的时候,明明服务已经关闭了,但是Eureka上仍然显示有此服务,会给我们开发人员带来影响,不利于我们的开发。所以,在开发环境下,我们需要关闭Eureka的自我保护机制。

关闭方式

注册中心关闭自我保护机制,修改检查失效服务的时间,以确保注册中心将不可用的实例及时正确剔除

在eureka项目的 application.properties 文件中,添加以下配置

# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false
# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

减短客户端服务发送服务心跳给服务端的时间, 在开发测试时,将值设置设置小些,保证服务关闭后注册中心能及时踢出服务。

在 consumer 项目的 application.properties 文件中,添加如下配置

# 租赁到期持续时间,即 如果6秒还没有收到信息,就在eureka中删除此服务
eureka.instance.lease-expiration-duration-in-seconds=6
# 租赁更新时间间隔,即 每隔2秒向注册中心发送一个确认信息
eureka.instance.lease-renewal-interval-in-seconds=2

测试关闭是否成功

重新启动3个项目

在这里插入图片描述

我们看到一个警告信息,是提示你Eureka的自我保护已经关闭了,忽略即可

​ 自动保存模式关闭。这可能无法在出现网络或其他问题时保护实例。

然后关闭 consumer 服务,再次刷新页面(6秒后,会删除consumer服务)

在这里插入图片描述

因为我们只配置了 consumer 服务,所以当关闭 producer服务时,eureka并不能及时将 producer 服务剔除

Eureka如何优雅停服

我们手动关闭了 eureka 的自我保护机制,有点太暴力了,而且关闭自我保护机制之后,控制面板还会有一个红色警告,这让强迫症的开发者看起来很不爽。并且每个服务都需要配置那些信息,项目上线的时候,每个配置都需要修改,比较繁琐。

那么如何优雅的将 eureka中不可用的服务及时剔除呢?

配置

需要在 client 客户端上配置:

pom.xml

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

application.properties

# 暴露已经停止的服务
management.endpoints.web.exposure.include=shutdown
# 关闭已经停止的服务
management.endpoint.shutdown.enabled=true

测试

启动3个项目,然后使用 post请求 访问如下地址,使用form,或者ajax都行,这里使用 PostMan 进行测试

http://127.0.0.1:8080/actuator/shutdown
在这里插入图片描述

然后刷新页面

在这里插入图片描述

可以看到,producer服务已经从eureka注册中心移除。而且也没有烦人的警告信息,项目上线的时候也不需要更改配置信息

Eureka常用配置总结

# 注册中心主机名
eureka.instance.hostname=127.0.0.1

# 是否将自己注册到EurekaServer,默认是true
eureka.client.fetch-registry=false

# 是否进行检测,false: 因为自己是注册中心,不需要去检索服务信息
eureka.client.register-with-eureka=false

# 注册地址 多节点用 , 分隔
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false

# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

# 当调用getHostname获取实例的hostname时,返回ip而不是host名称
eureka.instance.prefer-ip-address=true

# 指定自己的ip信息,不指定的话会自己寻找
eureka.instance.ip-address=127.0.0.1

# 执行当前服务的应用ID  不可以重复  标识的是每一个具体的的服务
eureka.instance.instance-id=springcloud-producer-8181

# 是否开启保护模式,开发环境下关闭自我保护机制,保证不可用服务及时踢出,默认是 true,开启保护机制
eureka.server.enable-self-preservation=false

# 清理不可用服务的间隔时间,默认是 60* 1000 (60秒)
eureka.server.eviction-interval-timer-in-ms=2000

# 表示eureka client间隔多久去拉取服务器注册信息,默认为30秒
eureka.client.registry-fetch-interval-seconds=5

# 不启动此服务
eureka.client.enabled=false

Eureka搭建集群实现高可用

详情参见:SpringCloud系列(三)Eureka搭建集群实现高可用(三种方式)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章