3. Spring Cloud Eureka 服务注册与发现(超详细说明及使用)


前言

1. Spring Cloud Eureka 的概述

从上个学习内容上我们可以知道 补充, 当中的 、Spring Cloud 组件选型- 图

在这里插入图片描述

从上图可以看出,目前主流的服务注册/发现的组件是 Nacos,但是Eureka 作为一个老牌经典的服务
注册/发现技术还是有必要回顾一下:
原因:

  • 一些早期的分布式 微服务项目使用的是 Eureka ,大家工作中,完全有可能遇到这种情况。
  • 后期的服务注册/发现组件/技术,都参考了 Eureka 的设计和理念,学习了 Eureka 后,我们上手
    Nacos 容易很多,而且理解的更深刻。

分析上述我们上一篇内容补充 当中项目架构问题分析

在这里插入图片描述

当前项目问题分析:

  1. 在企业项目中,服务消费访问请求会存在高并发
  2. 如果只有一个会议中心-提供服务,可用性就比较差了
  3. 所以,会议中心提供服务往往是一个集群,也就是说会有多个会议中心-提供服务微服务模块
  4. 那么这个时候,就存在一个问题就是服务消费方,怎么去发现可以使用的服务(哪些服务又不可以使用)
  5. 当服务消费方,发现了可以使用的服务后(可能是多个,又存在一个问题就是到底调用 A服务,
    还是调用B服务,这就引出了服务注册和负载均衡的问题)
  6. Eureka 就可以解决上述问题

引入 Eureka 项目架构,一图胜千言

在这里插入图片描述

上图 Eureka 项目架构图解析:

  1. 会员中心——> 提供服务的,在项目中,会做成集群 ,提供高可用
  2. Eureka Server 有必要的话,也可以做成集群
  3. Eureka 包含两个组件: Eureka ServerEureka Client
  4. Eureka Server 提供注册服务,各个微服务节点童工配置启动后,会在 EurekaServer 中进行注册,
    这样Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
  5. Eureka Client 通过注册中心进行访问,是一个Java客户端,用于简化 Eureka Server 的交互,
    客户端同时也具备一个内置的,使用轮询(round-robin) 负载算法的负载均衡器。在应用启动后,
    将会向 Eureka Server 发送心跳(默认周期为 30秒)。如果 Eureka Server 在多个心跳周期
    内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认90秒)

1.1 服务治理概述

Eureka 实现服务治理

在传统的 rpc 远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理困难,所以 需要治理服务之间依赖关系

服务治理实现服务调用、负载均衡、容错等,实现服务发现与注册。

二说分布式开发: https://jingyan.baidu.com/article/46650658def479f549e5f83e.html

1.2 服务注册与发现

在这里插入图片描述

  • Eureka 采用了CS[client-server-java] 基础我们讲过一个多人聊天项目。的设计架构,
    Eureka Server 作为服务注册的服务器,它是服务注册中心

  • 系统中的其它微服务,使用Eureka 的客户端连接到 Eureka Server 并维持心跳连接,
    通过 Eureka Server 来监控系统中各个微服务是否正常运行

  • 在服务注册与发现中,有一个注册中心,当服务启动的时候,会把当前自己服务器的信息,比如服务地址通讯地址等以别名方式注册到注册中心上。

  • 服务消费者或者服务提供者,以服务别名的方式去注册中心上获取到实际的服务提供
    者通讯地址,然后,通过 RPC 调用服务

2. 实践:创建单机 Eureka Server 注册中心

2.1 需求说明 + 图解

在这里插入图片描述

2.3 具体实现步骤

2.3.1 创建相关的 Moduel 以及 完成配置

  1. 创建 e-commerce-eureka-server-9001 微服务模块[作为注册中心]

在这里插入图片描述

在这里插入图片描述

父工程的 pom.xml-会做相应变化,管理 e-commerce-eureka-server-9001 微服务子模块

在这里插入图片描述

  1. 修改 e-commerce-eureka-server-9001 的 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">
    <parent>
        <artifactId>e-commerce-center</artifactId>
        <groupId>com.rainbowsea</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-eureka-server-9001</artifactId>


    <dependencies>
        <!--        引入web-starter 说明:这里我们使用版本仲裁(从父项目继承了版本)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--1. starter-actuator 是sprng boot 程序的监控系统,可以实现健康检查,info 信息等
           2. 访问http://localhost:10000/actuator 可以看到相关链接,还可以做相关配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 引入 eureka-server 场景启动器 starter: 使用版本仲裁
          注意:不要搞错了,有一个 starter 的

         -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!--        lombok 引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <!--            不进行转递依赖-->
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

        <!--        引入 test-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--        引入我们自己对 bean 封装成 api 的模块内容-->
        <dependency>
            <groupId>com.rainbowsea</groupId>
            <artifactId>e_commerce_center-common-api</artifactId>
            <version>${project.version}</version>
        </dependency>


    </dependencies>

</project>
  1. 创建 resources/application.yml

在这里插入图片描述

server:
  port: 9001
# 配置 eureka-server
eureka:
  instance:
    hostname: localhost # 服务实例名或者别名
  client:
    # 配置不向注册中心注册自己,默认是 true
    register-with-eureka: false
    # 表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务,默认是 true
    fetch-registry: false
    service-url:
      #设置与 eureka server 交互的模块,查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:9001/eureka/ ,也可以使用别名,在同一个文件当中可以引用
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

特别说明:注意:这里从架构图上可以知道的是,这里的这个e-commerce-eureka-server-9001 充当为 Eureka Server 服务角色 ,所以自然,我们需要将其配置为 服务角色。

在这里插入图片描述

  1. 创建主启动类 com/rainbwosea/springcloud/EurekaApplication.java
    在这里插入图片描述
package com.rainbowsea.springcloud;


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

@EnableEurekaServer // @EnableEurekaServer 表示该项目/该模块/该程序作为 Eureka Server 角色进行运行
@SpringBootApplication
public class EurekaApplication9001 {

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

  1. 完成测试:

在这里插入图片描述

补充说明: Spring Eureka 完整的可视化页面展示

在这里插入图片描述

2.3.2 配置 member-service-consumer-80 作为 EurekaClient 可 以 拉 取/获取 e-commerce-eureka-server-9001 提供的服务信息

2.3.3 架构示意图

在这里插入图片描述

配置 member-serivce-consumer-10000 作为 EurekaClient 注册发现到 commerce-eureka-server-9001 服务当中,我们需要在 resource目录下的 application.yaml 文件当进行一个配置。如下图所示:

  1. 配置 member-serivce-consumer-80 作为 EurekaClient 注册发现到 commerce-eureka-server-9001 服务当中,我们需要在 resource目录下的 application.yaml 文件当进行一个配置。如下图所示:

在这里插入图片描述

server:
  port: 80 # 浏览器默认就是 80 端口

spring:
  application:
    name: member-service-consumer-80 # 该项目/模块名称

eureka:
  client:
    register-with-eureka: true # 表示将自己注册到 Eureka-Server 当中
    fetch-registry: true # 表示将信息发送到 Eureka-Server 当中
    service-url:
      # 表示将自己注册到那个 eureka-server
      defaultZone: http://localhost:9001/eureka

在这里插入图片描述

  1. 同时我们也需要将该 member-service-consumer-80项目,配置指明为是 Eureka Client 角色,在该项目的场景启动器上添加 注解即可

在这里插入图片描述

package com.rainbowsea.springcloud;


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

// 如果错误: 加入排除 DataSourceAutoConfiguration 自动配置 
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient //表示将项目/模块/程序 标注作为 Eureka Client
@SpringBootApplication
public class MemberConsumerApplication80 {
    public static void main(String[] args) {
        SpringApplication.run(MemberConsumerApplication80.class, args);
    }
}

运行测试:

注意:测试的时候,我们尽量先将 EukekatServer (也就是我们这里的 e-commerce-eureka-server-9001) 注册中心启动,然后再将 Eukekat Client(也就是我们这里的 member-service-consumer-80) 启动。

然后,打开浏览器输入:http://localhost:9001/ 查看结果。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3.4 将 member-service-provider-10000 作为 EurekaClient 注册到 e-commerce-eureka-server-9001 成为服务提供者

上述操作我们是已经将 定义为了一个 Eureka Server 服务器,那么我们就需要将 服务提供者 注册到我们的 Eureka Server 当中。注册到 Eureka Server 当中,换句话说:就是将我们 Eureka Client 告诉 Eureka Server 我还没有宕机,还活着,我还可以继续提供服务下去。

2.3.5 架构示意图

在这里插入图片描述

配置 member-service-provider-10000 作为 EurekaClient 注册发现到 commerce-eureka-server-9001 服务当中,我们需要在 resource目录下的 application.yaml 文件当进行一个配置。如下图所示:

在这里插入图片描述

server:
  port: 10000
spring:
  application:
    name: member-service-provider-10000 # 配置应用的名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 配置 alibaba 的数据库连接池
    password: MySQL123
    username: root
    url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis:
  mapper-locations: classpath:mapper/*.xml # 指定 mapper.xml 文件位置 classpath 表示 resources 目录下
  type-aliases-package: com.rainbowsea.springcloud.entity # 实例 bean 类所在的包,这样可以通过类名的方式

  # 配置 eureka-client
eureka:
  client:
    register-with-eureka: true # 表示将自己注册到 Eureka-Server
    # 表示从 Eureka-Server 抓取注册信息
    # 如果是单节点,是可以配置的,但是如果是一个集群,则必须配置true
    # 才能配合 Ribbon 使用负载均衡
    fetch-registry: true
    service-url:
      # 表示将自己注册到那个 eureka-server
      defaultZone: http://localhost:9001/eureka


同时我们也需要将该 member-service-provider-10000项目,配置指明为是 Eureka Client 角色,在该项目的场景启动器上添加 注解即可

在这里插入图片描述

package com.rainbowsea.springcloud;


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


@EnableEurekaClient 表示将该程序/项目/模块 标识为 eureka-client 端
@EnableEurekaClient
@SpringBootApplication
public class MemberApplication10000 {

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

测试:

  1. 启动 e-commerce-eureka-server-9001
  2. 启动 member-service-provider-10000
  3. 浏览器: http://localhost:9001

在这里插入图片描述

在这里插入图片描述

3. Service Consumer 、Service Provider 、EurekaServer 的维护机制

示图:

在这里插入图片描述

4. Eureka 自我保护模式

4.1 自我保护模式理论

在默认情况下, Eureka 启动了自我保护模式(如图红字, 需要刷新页面, 可以看到)

在这里插入图片描述

  • 默认情况下EurekaClient 定时向 EurekaServer 端发送 心跳包

  • 如果 Eureka 在 server 端一定时间内(默认90秒) 没有收到 EurekaClient 发送心跳包,便会
    直接从服务注册表中剔除该服务

  • 如果Eureka 开启了**“自我保护机制/模式”** ,那么在短时间(90秒中)内丢失了大量的服务实例心跳。
    这时候Eureka Server 会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通或者
    阻塞)因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的。

  • 自我保护是属于CAP里面的AP分支 ,保证高可用和分区容错性

  • 自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康
    的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务
    。使用自我保护模式,
    可以让 Eureka 集群更加的健壮,稳定,参考:
    https://blog.csdn.net/wangliangluang/article/details/120626014

  • 一致性: 所有节点在同一时间的数据完全一致
  • 可用性:服务在正常时间内一直可用
  • 分区容错性:系统在遇到节点或者网络分区故障的时候,仍然能够对外满足可用性或一致性的服务

测试:

  • 启动member-service-provider-10000 和 e-commerce-eureka-server-9001

  • 让member-service-provider-10000 正确的注册

  • 然后关闭member-service-provider-10000

  • 观察注册的 member-service-provider-10000 服务是否还在.

在这里插入图片描述

4.2 禁用自我保护模式(生产环境中, 一般不禁用)

演示禁用自我保护模式:

修改 e-commerce-eureka-server-9001 的 application.yml

在这里插入图片描述

修改 member-service-provider-10000 的 application.yml

在这里插入图片描述

启 动 e-commerce-eureka-server-9001 和 member-service-provider-10000

在 member-service-provider-10000 注册成功后,再关闭, 看看 eureka server 服务注册信息的变化

在这里插入图片描述

在这里插入图片描述

提醒:测试完毕后,别忘了恢复原状,启用自我保护

5. 搭建 EurekaServer 集群- 实现负载均衡故障容错

5.1 为什么需要集群 Eureka Server

示意图(上图)
在这里插入图片描述

为什么需要集群 Eureka Server
说明:

  1. 微服务 RPC 远程服务调用最核心的是实习高可用
  2. 如果注册中心只有1个,它出故障,会导致整个服务环境不可用
  3. 解决办法: 搭建 Eureka 注册中心集群,实习 负载均衡 + 故障容错

5.2 需求分析/图解

在这里插入图片描述

5.3 搭建 Eureka Server 集群

1. 创建 e-commerce-eureka-server-9002 微服务模块[作为注册中心]

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  1. 修改 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">
    <parent>
        <artifactId>e-commerce-center</artifactId>
        <groupId>com.rainbowsea</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-eureka-server-9002</artifactId>


    <dependencies>
        <!--        引入web-starter 说明:这里我们使用版本仲裁(从父项目继承了版本)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--1. starter-actuator 是spring boot 程序的监控系统,可以实现健康检查,info 信息等
           2. 访问http://localhost:9002/actuator 可以看到相关链接,还可以做相关配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 引入 eureka-server 场景启动器 starter: 使用版本仲裁
          注意:不要搞错了,有一个 starter 的

         -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!--        lombok 引入-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <!--            不进行转递依赖-->
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

        <!--        引入 test-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--        引入我们自己对 bean 封装成 api 的模块内容-->
        <dependency>
            <groupId>com.rainbowsea</groupId>
            <artifactId>e_commerce_center-common-api</artifactId>
            <version>${project.version}</version>
        </dependency>


    </dependencies>


</project>
  1. 创建 resources/application.yml

在这里插入图片描述

在这里插入图片描述

server:
  port: 9002
# 配置 eureka server
eureka:
  instance:
    hostname: eureka9002.com # 服务实例名/或者叫别名
  client:
    # 配置不向注册中心注册自己(因为自己就是注册中心),默认是 true
    register-with-eureka: false
    fetch-registry: false
    service-url:
      #设置与 eureka server 交互的模块,查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:9001/eureka/ ,也可以使用别名,在同一个文件当中可以引用
      # 相互注册,这里应用注册到 eureka server 9001 上
      defaultZone: http://eureka9001.com:9001/eureka/
  1. 创建主启动类 EurekaApplication9002.java

在这里插入图片描述

package com.rainbowsea.springcloud;


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

@SpringBootApplication
@EnableEurekaServer  //该注解标注,当前的项目/模块/程序作为 Eureka Server 角色
public class EurekaApplication9002 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication9002.class, args);
    }
}

  1. 修改 e-commerce-eureka-server-9001 微服务模块,修改其中的内容 resources/application.yml

让: e-commerce-eureka-server-9001 微服务模块 与 e-commerce-eureka-server-9002 相互注册
在这里插入图片描述

在这里插入图片描述

server:
  port: 9001
# 配置 eureka-server
eureka:
  instance:
    hostname: eureka9001.com # 服务实例名或者别名
  client:
    # 配置不向注册中心注册自己,默认是 true
    register-with-eureka: false
    # 表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务,默认是 true
    fetch-registry: false
    service-url:
      #设置与 eureka server 交互的模块,查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:9001/eureka/ ,也可以使用别名,在同一个文件当中可以引用
      # 相互注册,这里应用注册到 eureka server 9002 上
      defaultZone: http://eureka9002.com:9002/eureka/



6. 补充:定义本地的域名机制

如下图所示:
在这里插入图片描述

无论是 9001 还是 9002 ,我们都定义了别名,如果想要,让定义的别名生效的话,我们需要在本地,设置域名映射。

设置本地的域名映射,需要找到本地电脑当中的 host 文件,在该 host文件当中编写添加相应的域名映射。

host文件路径: C:\Windows\System32\drivers\etc\host

在这里插入图片描述

在这里插入图片描述

# 配置 eureka 主机 和 ip 的映射
127.0.0.1 eureka9001.com
127.0.0.1 eureka9002.com
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#	127.0.0.1       localhost
#	::1             localhost
 
127.0.0.1 account.wondershare.com
# 配置 eureka 主机 和 ip 的映射
127.0.0.1 eureka9001.com
127.0.0.1 eureka9002.com

特别提醒:如果发现文件修改后无法保存,那么可以将文件复制到桌面上,然后,修改复制到桌面上的那份复制过来的 host 文件,修改保存完后,再拷贝回原来路径位置,进行一个替换。

运行测试:

  1. 启动 e-commerce-eureka-server-9001
  2. 启动 e-commerce-eureka-server-9002
  3. 浏览器: http://eureka9001.com:9001 浏览器: http://eureka9002.com:9002

在这里插入图片描述

在这里插入图片描述

将 member-service-consumer-80 注册到 EurekaServer 集群(目前 2 台)

修改 member-service-consumer-80 模块当中的:resources/application.yml

在这里插入图片描述

在这里插入图片描述

  1. 启动 e-commerce-eureka-server-9001 和 e-commerce-eureka-server-9002
  2. 启动 member-service-consumer-80
  3. 观察 member-service-consumer-80 是否注册到 Eureka 集群(目前 2 台)

浏览器输入: http://eureka9001.com:9001/

在这里插入图片描述

5.4 搭建会员中心服务提供方-集群

在这里插入图片描述

创建 member-service-provider-10002

参考 member-service-provider-10000 来创建 member-service-provider-10002 即可

创 建 好 后 , 使 用 member-service-provider-10000 的 源 码 和 配 置 替 换 member-service-provider-10002 生成的代码

(不要到磁盘整体拷贝,会出现关联到 member-service-provider-10000 的问题,很麻烦, 可以创建好新项目的包,然后再拷贝对应包下的文件,就不会出问题)

提醒,拷贝时不要忘记拷贝 resources/mapper/MemberMapper.xml 这些 xxx.xml 文 件。

创建 resources/application.yml

创建好 application.yml

从 member-service-provider-10000 拷贝 application.yml 的内容

修改端口号即可

在这里插入图片描述

在这里插入图片描述

server:
  port: 10002
spring:
  application:
    name: member-service-provider # 配置应用的名称
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource # 配置 alibaba 的数据库连接池
    password: MySQL123
    username: root
    url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis:
  mapper-locations: classpath:mapper/*.xml # 指定 mapper.xml 文件位置 classpath 表示 resources 目录下
  type-aliases-package: com.rainbowsea.springcloud.entity # 实例 bean 类所在的包,这样可以通过类名的方式

# 配置 eureka-client
eureka:
  client:
    register-with-eureka: true # 示将自己注册到 Eureka-Server,因为自己是作为 eureka-client客户端的角色的
    # 表示从 Eureka-Server 抓取注册信息
    # 如果是单节点,是可以配置的,但是如果是一个集群,则必须配置true
    # 才能配合 Ribbon 使用负载均衡
    fetch-registry: true
    service-url:
      # 表示将自己注册到那个 eureka-server
      # 将本微服务注册到多个 eureka - server 当中,使用逗号间隔即可
      defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/

创建主启动类名(场景启动器)

在这里插入图片描述

启动 eureka server 集群(目前 2 台)

启动 member-service-provider-10000

启动 member-service-provider-10002

浏览器输入: http://eureka9001.com:9001/

在这里插入图片描述

浏览器输入: http://eureka9002.com:9002/

在这里插入图片描述

5.5 配置服务消费端 member-service-consumer-80 使用会员中心服务集群

因为 member-service-provider-10000 和 member-service-provider-10002 作为一个集 群提供服务, 因此需要将 spring.application.name 进行统一,方便用于负载均衡

在这里插入图片描述

这样消费方通过统一的别名进行负载均衡调用。

示意图:

在这里插入图片描述

修改 member-service-consumer-80 模块下的,MemberConsumerController.java

在这里插入图片描述

package com.rainbowsea.springcloud.controller;

import com.rainbowsea.springcloud.entity.Member;
import com.rainbowsea.springcloud.entity.Result;
import lombok.extern.slf4j.Slf4j;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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


@RestController
@Slf4j
public class MemberConsumerController {


    // 定义 MEMBER_SERVICE_PROVIDER_URL 这是一个基础 url地址
    // 使用 shift+ctrl+u 进行字母大小写的切换
    //private static final String MEMBER_SERVICE_PROVIDER_URL = "http://localhost:10000";
    // 负载均衡统一别名,修改为提供服务模块的注册别名: 不需要配置指明端口号,因为是一个负载集群了。不是特指某个了Service 服务来处理了
    /*
    server:
  port: 10000
spring:
  application:
    name: member-service-provider # 配置应用的名称
     */
    /*
    说明:
    1. MEMBER-SERVICE-PROVIDER 就是服务提供方【集群】,注册到Eureka Server 的名称
    2. 也就是服务提供方【集群】对外暴露的名称为 MEMBER-SERVICE-PROVIDER
    3. MEMBER-SERVICE-PROVIDER 目前有两个 	Availability Zones 	UP (2) - localhost:member-service-provider:10000 , localhost:member-service-provider:10002
      还有一个 member-service-provider:10002
      需要增加一个注解 @LoadBalanced 赋予 RestTemplate 才能有负载均衡的能力,也就是会根据你的负载均衡
      来选择某个服务去访问,默认是“轮询算法”,当然我们也可以自己配置负载均衡算法
    4. 在对应的 RestTemplate 配置类上的,@Bean注解下面加上 @LoadBalanced 注解。
     */
    private static final String MEMBER_SERVICE_PROVIDER_URL = "http://MEMBER-SERVICE-PROVIDER";


    // 装配 RestTemplate bean/对象
    @Resource
    private RestTemplate restTemplate;


    @PostMapping("/member/consumer/save")
    public Result<Member> save(Member member) {
        // 1.第1个参数: 就是请求的完整的url:MEMBER_SERVICE_PROVIDER_URL + "/member/save" => http://localhost:10000/member/save
        // 表示你向将该请求,发送给那个微服务处理,注意无论是返回值,还是参数, @PostMapping() 请求方式都要一一对应上对应处理的微服务上的内容
        //2. 第2个参数: member : 就是通过 restTemplate 发出的 post 请求携带数据或者对象
        //3. 第3个参数: Result.class ,微服务当中的对应处理的方法的,返回值,也就是返回对象类型
        // 注意:坑点
        // 这里通过 restTemplate 调用服务模块的接口,就是一个远程调用
        //
        log.info("member-service-consumer-80 save member={}", member);
        return restTemplate.postForObject
                (MEMBER_SERVICE_PROVIDER_URL + "/member/save", member, Result.class);

    }

    /*
    1.与
     @PostMapping("/member/save")
    public Result save(Member member) {
        int affected = memberService.save(member);
        if (affected > 0) { // 说明添加成功
            return Result.success("添加会员成功", affected);
        } else {
            return Result.error("401", "添加会员失败");
        }
    }
     */


    /**
     * 方法/接口,调用服务接口,返回 member 对象信息
     *
     * @param id
     * @return
     */
    @GetMapping("/member/consumer/get/{id}")
    public Result<Member> getMemberById(@PathVariable("id") Long id) {

        // 这里就用两个参数
        // 第一个参数,因为是查询,所以这里我们直接字符串拼接上去
        // 这里通过
        return restTemplate.getForObject(MEMBER_SERVICE_PROVIDER_URL + "/member/get/" + id, Result.class);
    }

    /*
    保持一致:
      * 提交方式
      * 返回类型
      * 参数
      * 路径映射要对应上
     @GetMapping("/member/get/{id}")
    public Result getMemberById(@PathVariable("id") Long id) {
        Member member = memberService.queryMemberById(id);

        // 使用 Result 把查询到的结果返回
        if (member != null) {
            return Result.success("查询会员成功", member);
        } else {
            return Result.error("402", "ID" + id + "不存在");
        }
    }

     */


    // 注意:有两个是这个包下的 : org.springframework.cloud.client.discovery.DiscoveryClient

    @Resource // 装配到 Spring ioc 容器当中
    private DiscoveryClient discoveryClient;


    // 注意:想要获取到服务端信息,还需要在SpringBoot 启动场景上加上:@EnableDiscoveryClient // 启用服务发现 注解才行
    @GetMapping("/member/consumer/discovery")
    public Object discovery(){
        List<String> services = discoveryClient.getServices();

        // 遍历 services
        for (String service : services) {
            log.info("服务名==={}",service);
            // 再根据服务名获取更加详细的信息
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            for (ServiceInstance instance : instances) {
                log.info("id={},host={},uri={}",instance.getServiceId(),instance.getHost(),instance.getUri());
            }
        }


        return this.discoveryClient;
    }
}

为了看到测试效果,修改服务提供方

在两个服务提供方的查询和添加返回结果处增加自己服务名称信息,如图, 其它位置可参考加入

在这里插入图片描述

    /**
     * 这里我们使用 url占位符 + @PathVariable
     *
     * @param id
     * @return
     */
    @GetMapping("/member/get/{id}")
    public Result getMemberById(@PathVariable("id") Long id) {
        Member member = memberService.queryMemberById(id);

        // 使用 Result 把查询到的结果返回
        if (member != null) {
            return Result.success("查询会员成功 member-service-provider-10000 ", member);
        } else {
            return Result.error("402", "ID" + id + "不存在 member-service-provider-10000 ");
        }
    }


    /**
     * 这里我们使用 url占位符 + @PathVariable
     *
     * @param id
     * @return
     */
    @GetMapping("/member/get/{id}")
    public Result getMemberById(@PathVariable("id") Long id) {
        Member member = memberService.queryMemberById(id);

        // 使用 Result 把查询到的结果返回
        if (member != null) {
            return Result.success("查询会员成功 member-service-provider-10002 ", member);
        } else {
            return Result.error("402", "ID" + id + "不存在 member-service-provider-10002  ");
        }
    }

特别说明: 我们想要能够实现 member 交替访问的话,需要在 RestTemplate 配置类上@Bean方法上加上该 @LoadBalanced注解的)才行。
底层是 Ribbon 支持算法 交替访问 member服务说明:

  1. 注解@LoadBalanced(在RestTemplate 配置类的@Bean方法上加上该 @LoadBalanced注解的)
    底层是 Ribbon 支持算法

在这里插入图片描述

  1. Ribbon 和 Eureka 整合后 consumer 直接调用服务而不用再关心地址和端口号,且该服务还有负载功能

运行测试:

启动 member-service-consumer-80

浏览器访问:http://eureka9001.com:9001/

在这里插入图片描述

浏览器访问: http://localhost/member/consumer/get/1 这个是通过客户端 Eureka Client 发送的请求路径映射:

在这里插入图片描述

在这里插入图片描述

5.6 获取 Eureka Server 服务注册信息 - DiscoveryClient

先看需求分析示意图:

在这里插入图片描述

这里我们以服务消费方 去获取 Eureka Server 的服务注册信息为例讲解

在这里插入图片描述

当然也可以在服务提供方获取 Eureka Server 的服务注册信息。

5.5.1 具体操作步骤:

所在模块 member-service-consumer-80,修改:com.rainbowsea.springcloud.controller.MemberConsumerController 包当中的

增加如下代码:

在这里插入图片描述

 @Resource // 装配到 Spring ioc 容器当中
    private DiscoveryClient discoveryClient;


    // 注意:想要获取到服务端信息,还需要在SpringBoot 启动场景上加上:@EnableDiscoveryClient // 启用服务发现 注解才行
    @GetMapping("/member/consumer/discovery")
    public Object discovery(){
        List<String> services = discoveryClient.getServices();

        // 遍历 services
        for (String service : services) {
            log.info("服务名==={}",service);
            // 再根据服务名获取更加详细的信息
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            for (ServiceInstance instance : instances) {
                log.info("id={},host={},uri={}",instance.getServiceId(),instance.getHost(),instance.getUri());
            }
        }


        return this.discoveryClient;
    }
package com.rainbowsea.springcloud.controller;

import com.rainbowsea.springcloud.entity.Member;
import com.rainbowsea.springcloud.entity.Result;
import lombok.extern.slf4j.Slf4j;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

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


@RestController
@Slf4j
public class MemberConsumerController {


    // 定义 MEMBER_SERVICE_PROVIDER_URL 这是一个基础 url地址
    // 使用 shift+ctrl+u 进行字母大小写的切换
    //private static final String MEMBER_SERVICE_PROVIDER_URL = "http://localhost:10000";
    // 负载均衡统一别名,修改为提供服务模块的注册别名: 不需要配置指明端口号,因为是一个负载集群了。不是特指某个了Service 服务来处理了
    /*
    server:
  port: 10000
spring:
  application:
    name: member-service-provider # 配置应用的名称
     */
    /*
    说明:
    1. MEMBER-SERVICE-PROVIDER 就是服务提供方【集群】,注册到Eureka Server 的名称
    2. 也就是服务提供方【集群】对外暴露的名称为 MEMBER-SERVICE-PROVIDER
    3. MEMBER-SERVICE-PROVIDER 目前有两个 	Availability Zones 	UP (2) - localhost:member-service-provider:10000 , localhost:member-service-provider:10002
      还有一个 member-service-provider:10002
      需要增加一个注解 @LoadBalanced 赋予 RestTemplate 才能有负载均衡的能力,也就是会根据你的负载均衡
      来选择某个服务去访问,默认是“轮询算法”,当然我们也可以自己配置负载均衡算法
    4. 在对应的 RestTemplate 配置类上的,@Bean注解下面加上 @LoadBalanced 注解。
     */
    private static final String MEMBER_SERVICE_PROVIDER_URL = "http://MEMBER-SERVICE-PROVIDER";


    // 装配 RestTemplate bean/对象
    @Resource
    private RestTemplate restTemplate;


    @PostMapping("/member/consumer/save")
    public Result<Member> save(Member member) {
        // 1.第1个参数: 就是请求的完整的url:MEMBER_SERVICE_PROVIDER_URL + "/member/save" => http://localhost:10000/member/save
        // 表示你向将该请求,发送给那个微服务处理,注意无论是返回值,还是参数, @PostMapping() 请求方式都要一一对应上对应处理的微服务上的内容
        //2. 第2个参数: member : 就是通过 restTemplate 发出的 post 请求携带数据或者对象
        //3. 第3个参数: Result.class ,微服务当中的对应处理的方法的,返回值,也就是返回对象类型
        // 注意:坑点
        // 这里通过 restTemplate 调用服务模块的接口,就是一个远程调用
        //
        log.info("member-service-consumer-80 save member={}", member);
        return restTemplate.postForObject
                (MEMBER_SERVICE_PROVIDER_URL + "/member/save", member, Result.class);

    }

    /*
    1.与
     @PostMapping("/member/save")
    public Result save(Member member) {
        int affected = memberService.save(member);
        if (affected > 0) { // 说明添加成功
            return Result.success("添加会员成功", affected);
        } else {
            return Result.error("401", "添加会员失败");
        }
    }
     */


    /**
     * 方法/接口,调用服务接口,返回 member 对象信息
     *
     * @param id
     * @return
     */
    @GetMapping("/member/consumer/get/{id}")
    public Result<Member> getMemberById(@PathVariable("id") Long id) {

        // 这里就用两个参数
        // 第一个参数,因为是查询,所以这里我们直接字符串拼接上去
        // 这里通过
        return restTemplate.getForObject(MEMBER_SERVICE_PROVIDER_URL + "/member/get/" + id, Result.class);
    }

    /*
    保持一致:
      * 提交方式
      * 返回类型
      * 参数
      * 路径映射要对应上
     @GetMapping("/member/get/{id}")
    public Result getMemberById(@PathVariable("id") Long id) {
        Member member = memberService.queryMemberById(id);

        // 使用 Result 把查询到的结果返回
        if (member != null) {
            return Result.success("查询会员成功", member);
        } else {
            return Result.error("402", "ID" + id + "不存在");
        }
    }

     */


    // 注意:有两个是这个包下的 : org.springframework.cloud.client.discovery.DiscoveryClient

    @Resource // 装配到 Spring ioc 容器当中
    private DiscoveryClient discoveryClient;


    // 注意:想要获取到服务端信息,还需要在SpringBoot 启动场景上加上:@EnableDiscoveryClient // 启用服务发现 注解才行
    @GetMapping("/member/consumer/discovery")
    public Object discovery(){
        List<String> services = discoveryClient.getServices();

        // 遍历 services
        for (String service : services) {
            log.info("服务名==={}",service);
            // 再根据服务名获取更加详细的信息
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            for (ServiceInstance instance : instances) {
                log.info("id={},host={},uri={}",instance.getServiceId(),instance.getHost(),instance.getUri());
            }
        }


        return this.discoveryClient;
    }
}

  1. 这里修改所在模块 member-service-consumer-80当中的主启动类(场景启动类) com.rainbowsea.springcloud.MemberConsumerApplication80

在这里插入图片描述

package com.rainbowsea.springcloud;


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

// 如果错误: 加入排除 DataSourceAutoConfiguration 自动配置
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
//@EnableEurekaClient 表示将项目/模块/程序 标注作为 Eureka Client
@EnableEurekaClient
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现
public class MemberConsumerApplication80 {
    public static void main(String[] args) {
        SpringApplication.run(MemberConsumerApplication80.class, args);
    }
}

重启 member-service-consumer-80

浏览器输出 http://localhost/member/consumer/discovery
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.5.1.1 注意事项和细节说明

在引入 DiscoveryClient 时,不要引入错误的包
正确的包: import org.springframework.cloud.client.discovery.DiscoveryClient;
错误的包: import com.netflix.discovery.DiscoveryClient;

在这里插入图片描述

演示的是在服务消费方使用 DiscoveryClient 来完成服务发现,同样,在服务提供 方/模块也 OK

7. Eureka 后续说明

  1. Eureka 停更说明: https://github.com/Netflix/eureka/wiki

在这里插入图片描述

  1. 对于一些早期使用 Eureka 项目,掌握老师讲解技术基本可以应付了(这也是老师为什么还要讲Eureka的原因)

  2. 目前替代Eureka做服务注册和发现均衡负载的最佳组件是Nacos , 后面会重点讲解
    Spring Cloud Alibaba

  3. 虽然 Eureka 停更,目前用的不多,但是它的服务注册和发现均衡负载的思想是优先
    的,有了Eureka的基础,我们学习Spring Cloud Alibaba Nacos会轻松很多

8. 总结:

  1. Spring Cloud Eureka 服务注册于发现
  2. Eureka 自我保护模式
  • 默认情况下EurekaClient 定时向 EurekaServer 端发送 心跳包

  • 如果 Eureka 在 server 端一定时间内(默认90秒) 没有收到 EurekaClient 发送心跳包,便会
    直接从服务注册表中剔除该服务

  • 如果Eureka 开启了**“自我保护机制/模式”** ,那么在短时间(90秒中)内丢失了大量的服务实例心跳。
    这时候Eureka Server 会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通或者
    阻塞)因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的。

  • 自我保护是属于CAP里面的AP分支 ,保证高可用和分区容错性

  • 自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康
    的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务
    。使用自我保护模式,
    可以让 Eureka 集群更加的健壮,稳定,参考:
    https://blog.csdn.net/wangliangluang/article/details/120626014

在生产环境中,一般不会禁用自我保护模式

  1. Eureka 集群-实现负载均衡,故障容错

为什么需要集群 Eureka Server
说明:

  1. 微服务 RPC 远程服务调用最核心的是实习高可用
  2. 如果注册中心只有1个,它出故障,会导致整个服务环境不可用
  3. 解决办法: 搭建 Eureka 注册中心集群,实习 负载均衡 + 故障容错

因为 member-service-provider-10000 和 member-service-provider-10002 作为一个集 群提供服务, 因此需要将 spring.application.name 进行统一,方便用于负载均衡

在这里插入图片描述

这样消费方通过统一的别名进行负载均衡调用。

在这里插入图片描述

特别说明: 我们想要能够实现 member 交替访问的话,需要在 RestTemplate 配置类上@Bean方法上加上该 @LoadBalanced注解的)才行。 底层是 Ribbon 支持算法 交替访问 member服务说明:

注解@LoadBalanced(在RestTemplate 配置类的@Bean方法上加上该 @LoadBalanced注解的) 底层是 Ribbon 支持算法

在这里插入图片描述

Ribbon 和 Eureka 整合后 consumer 直接调用服务而不用再关心地址和端口号,且该服务还有负载功能

  1. 定义本地的域名机制:

设置本地的域名映射,需要找到本地电脑当中的 host 文件,在该 host文件当中编写添加相应的域名映射。

host文件路径: C:\Windows\System32\drivers\etc\host

在这里插入图片描述


127.0.0.1 account.wondershare.com
# 配置 eureka 主机 和 ip 的映射
127.0.0.1 eureka9001.com
127.0.0.1 eureka9002.com

特别提醒:如果发现文件修改后无法保存,那么可以将文件复制到桌面上,然后,修改复制到桌面上的那份复制过来的 host 文件,修改保存完后,再拷贝回原来路径位置,进行一个替换。

  1. 获取 Eureka Server 服务注册信息 - DiscoveryClient;在引入 DiscoveryClient 时,不要引入错误的包,正确的包: import org.springframework.cloud.client.discovery.DiscoveryClient;

9. 最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部