SpringCloud-Hoxton.SR1学习

  • Post author:
  • Post category:其他


Spring Cloud主要组件包括

服务注册发现:Eureka,Zookeeper,Consul

服务调用接口:OpenFeign

负载均衡调用:Ribbon

服务降级/熔断/限流:Hystrix

服务网关:SpringCloud Gateway

服务配置:SpringCloud Config

服务总线:SpringCloud Bus

消息驱动:SpringCloud Stream

服务链路追踪:SpringCloud Sleuth

工程代码:

SpringCloud组件学习



1. 微服务架构

微服务架构是一种结构模式,提倡将单一应用程序划分成一组小的服务,服务之间相互协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中,服务与服务之间采用轻量级的通信机制互相协作(

通常是基于HTTP协议的RestFul风格

)。每个服务都环绕着具体业务进行构建,并且能够独立的部署到生产环境,类生成环境等。另外,应该尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建


SpringCloud

分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,称之为微服务全家桶

主流的技术栈:

  • 服务注册发现:Eureka
  • 服务负载与调用:RIbbon
  • 服务负载调用:Netflix
  • 服务熔断降级:Hystrix
  • 服务网关:Zuul
  • 服务分布式配置:Spring Cloud Config
  • 服务开发:Spring boot



2. Spring Cloud和boot版本选型



2.1. 版本选择

Spring Cloud F版本是基于Spring boot 2.0.x构建的,不再使用Spring boot 1.5.x

Spring Cloud D版本和E版本是基于Spring boot1.5.x的,不支持Spring boot 2.0.x

更详细的版本对应查看地址:

https://start.spring.io/actuator/info


请添加图片描述
版本选择:


  • Spring Cloud Hoxton.SR1

    官网

    https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/

  • Spring boot 2.2.2.RELEASE

  • Spring Cloud Alibaba 2.1.0.RELEASE
  • java8
  • Maven 3.5及以上
  • MySQL 5.7及以上



2.2. Cloud组件停更


被动修复Bugs,不再接受合并请求,不再发布新版本

  • 服务注册发现:Eureka的替换有

    Zookeeper; Consul;Nacos
  • 服务调用:Ribbon的替换有

    LoadBalancer

    ,Feign的替换为

    OpenFeign
  • 服务熔断和降级:Hystrix的替换为

    resilience4j; Sentinel(Spring Cloud Alibaba)
  • 服务网关:Zuul替换为

    GateWay
  • 服务配置:Config替换为

    Nacos
  • 服务总线:Bus替换为

    Nacos



3. 搭建父工程

新建maven工程,选择maven-archetype-site作为achetype,在父工程的pom文件中:

  • 声明统一管理的jar包版本
  • 使用

    dependencyManagement

    标签,子模块继承之后,可以锁定版本,并且子module不再需要在pom文件中添加groupId和Version
  • dependencyManagement元素标签提供一种管理依赖版本号的方式,**通常会在一个组织或者项目的最顶层的父Pom文件中看到该元素。**可以让所有子项目中引用一个依赖而不用显示的列出版本号,Maven会沿着父子层次向上寻找,直至找到一个拥有dependencyManagement元素的项目,然后就会使用这个dependencyManagement标签中指定的版本号,聚合统一管理非常方便。

    dependencyManagement标签里只是申明依赖,并不实现引入,是因此子项目需要显式的声明需要使用的依赖
  • Maven如何跳过单元测试:点击maven的

    Skip Test Model
<?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>

  <groupId>com.hz</groupId>
  <artifactId>Cloud_paraent</artifactId>
  <version>1.0-SNAPSHOT</version>
  <!--父工程的pom-->
  <packaging>pom</packaging>

  <name>Maven</name>
  <!-- FIXME change it to the project's website -->
  <url>http://maven.apache.org/</url>
  <inceptionYear>2001</inceptionYear>

  <distributionManagement>
    <site>
      <id>website</id>
      <url>scp://webhost.company.com/www/website</url>
    </site>
  </distributionManagement>

  <properties>
      <!--统一管理jar包版本-->
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <junit.version>4.12</junit.version>
      <lombok.version>1.18.10</lombok.version>
      <log4j.version>1.2.17</log4j.version>
      <mysql.version>5.1.47</mysql.version>
      <druid.version>1.1.16</druid.version>
      <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version>
  </properties>

  <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version-->
  <!--spring boot 2.2.2-->
  <dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.2.2.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <!--spring cloud Hoxton.SR1-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.SR1</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>

    <!--spring cloud 阿里巴巴-->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      <version>2.1.0.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.version}</version>
      <scope>runtime</scope>
    </dependency>
    <!-- druid-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>${druid.version}</version>
    </dependency>

    <!--mybatis-->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>${mybatis.spring.boot.version}</version>
    </dependency>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${junit.version}</version>
    </dependency>
    <!--log4j-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>${log4j.version}</version>
    </dependency>
  </dependencies>
  </dependencyManagement>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <fork>true</fork>
          <addResources>true</addResources>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>



4. 支付模块搭建(微服务订单模块,微服务消费者订单模块)



4.1. IDEA新建Project工作空间

  • 建Module
  • 修改POM
  • 写YAML
  • 主启动
  • 实现业务类
  • 测试

    • 通过页面请求访问:

      http://localhost:8001/payment/get/{id}
    • 使用Postman
    • 开启Run DashBoard -> 替换成Services(2020以后)



4.2. 开启热部署Devtools

  • 添加devtools jar包到子工程中的pom文件
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
  • 在父工程的pom文件中添加插件spring-boot-maven-plugin
  <build>
    <plugins>
      <plugin>
        <!--热部署-->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <fork>true</fork>
          <addResources>true</addResources>
        </configuration>
      </plugin>
    </plugins>
  </build>
  • 开启自动编译选项

    请添加图片描述
    + 更新:

    ctrl + alt + shift + / -> 打开Registry -> 开启下面两个选项


    请添加图片描述
    + 重启IDEA



4.3. RestTemplate


  • 是什么?

RestTemplate提供了多种便捷访问远程HTTP服务的方法,是一种简单便捷的访问

RestFul服务模板类

,是Spring提供的用于访问Rest服务的

客户端模板工具集

,是一种接口调用方式的封装


  • 如何使用?

使用RestTemplate访问Restful接口非常简单,其中的参数(

url,requestMap,ResponseBean.class

)这三个参数分别表示

REST请求地址,请求参数,HTTP响应被转换成的对象类型



4.4. 工程重构

  • 每个Module中是否存在重复的部分(实体类,方法,工具类,第三方公共组件等)
  • 新建工程

    cloud-api-commons
  • 新建工程POM的文件中添加共有的第三方工具包:lombok,devtools,hutool
  • entities:将两个微服务订单中的实体类拷贝到相同包路径下的cloud-api-commons工程中
  • cloud-api-commons工程使用maven命令clean,install
  • 改造两个订单微服务:

    • 删除各自的原先存在的实体类文件夹
    • 两个微服务订单POM中分别粘贴cloud-api-commons工程:
        <dependency>
            <groupId>com.hz</groupId>
            <artifactId>cloud_api_commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>



5. 服务注册发现Eureka



5.1. 什么是服务治理

SpringCloud封装了Netflix公司的Eureka模块

实现服务治理

在传统的

RPC远程调用框架

中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理,

管理服务与服务之间的依赖关系

,可以实现服务调用,负载均衡,容错等,实现服务发现与注册



5.2. 什么是服务注册与发现
请添加图片描述

Eureka采用了

CS

的设计架构,

Eureka Server作为服务注册功能的服务器,是服务注册中心

。而系统中的其他微服务,

使用Eureka的客户端连接到Eureka Server并维持心跳连接

。这样系统维护人员可以通过Eureka Server来监控系统中每个微服务是否正常运行

在服务注册与发现中,有一个注册中心。当服务器启动时,会把当前自己服务器的信息如服务地址通讯地址等注册到注册中心上,另一方(消费者或服务提供者),以别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现

本地RPC调用RPC远程调用框架核心设计思想:注册中心

,因为使用注册中心管理每个微服务与每个微服务之间的一个依赖关系(服务治理)。在任何RPC远程框架中,都会有一个注册中心(存放服务地址相关信息即解接口地址)

Eureka包含两个组件:


Eureka Server 提供服务注册


每个微服务节点通过配置启动后,会在Eureka Server中进行注册

,其中的服务注册列表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直接看到


Eureka Client 通过注册中心访问

是一个

java客户端,用于简化Eureka Server的交互

,客户端同时也是一个内置的,

使用轮询负载算法

的负载均衡器。在应用启动后,将会在Eureka Server发送心跳(默认周期30s)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90s)



5.3. 服务器端安装

新建maven工程,由于是服务器端只需要配置一个主程序类即可,并且在配置文件中指明端口号和Eureka的地址

导入Eureka的依赖:注意不需要指定版本号,由于在父工程中的spring-cloud中已经进行了依赖绑定

在主程序类上加上注解**@EnableEurekaServer**

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
// 表示该端口7001就是服务注册中心
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {

    public static void main(String[] args) {
        SpringApplication.run(EurekaMain7001.class, args);
    }
}
server:
  port: 7001

eureka:
  instance:
    hostname: localhost # Eureka服务器端的实例名
  client:
    register-with-eureka: false # 表示不向注册中心注册自己
    fetch-registry: false       # 本地端口就是服务注册中心,维护服务实例,不需要去检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 设置于Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址

  server:
    enable-self-preservation: false



5.4. 服务注册

将cloud-provider-payment8001注册到Eureka Server成为服务提供者provider

  • 在cloud-provider-payment8001工程中导入Eureka Client依赖
        <dependency>
            <!--Eureka Client-->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  • 在主程序类中加注解**@EnableEurekaClient**

将cloud-consumer-order80注册到Eureka Server成为服务消费者consumer

是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true,才能配合Ribbon使用负载均衡

  • 在微服务工程的配置文件中注册到Eureka Server
server:
  port: 8001

eureka:
  client:
    register-with-eureka: true # 表示注册中心注册
    fetchRegistry: true       # 是否从EurekaServer抓取已有的注册信息默认为true 单节点无所谓 集群必须设置为true才能配合Ribbon使用负载均衡
    service-url:
      defaultZone: http://localhost:7001/eureka # Eureka Server

请添加图片描述
注意两点:

  • 在配置文件中配置service-url时传递的是一个Map键值对,

    defaultZone需要缩进,并且空格之后跟上Eureka Server的地址

  • 开启Eureka Server之后,再开启添加到Server的两个client微服务

    ,才能显示出Application有两个微服务注册到Eureka Server



5.5. Eureka集群



a. 原理


服务注册

:将服务信息注册到注册中心


服务发现

:从注册中心上获取服务信息

实质上存取key为服务名,获取value值为调用地址

具体过程:

  • 启动Eureka注册中心
  • 启动服务器提供者payment支付服务
  • 支付服务启动后会自把自身信息(如服务地址以别名的方式注册到Eureka)
  • 消费者Order服务需要在调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
  • 消费者获取调用地址后,底层实际是利用HttpClient技术实现远程调用
  • 消费者获取服务地址后会缓存在本地的JVM内存中,默认间隔30s更新一次服务调用地址



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

高可用性,如果注册中心只有一个,一旦出故障或者宕机,会导致整个服务环境不可用。

搭建Eureka注册中心集群,实现负载均衡+故障容错



b. 搭建集群(Eureka Server)

  • 创建Eureka Server7002服务和Eureka Server7003服务
  • 修改映射配置

    请添加图片描述

    在hosts文件中添加三个Eureka Server信息

    请添加图片描述
  • 修改单机版的yaml文件:每个Server的hostname不能是localhost,为了区分需要指明当前是哪一个Server注册中心,对于eureka7001的Server需要注册7002和7003的信息,在defaultZone中使用逗号分隔。同理,eureka7002和eureka7003的也需要注册
server:
  port: 7001

# 对于Eureka集群 互相注册 相互守望 每个Server都需要注册其他Server的信息
eureka:
  instance:
    hostname: eureka7001.com # Eureka服务器端的实例名
  client:
    register-with-eureka: false # 表示不向注册中心注册自己
    fetch-registry: false       # 本地端口就是服务注册中心,维护服务实例,不需要去检索服务
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
      # 设置于Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机版)
      # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

同时开启三个Server注册中心:eureka7001.com:7001中绑定7002和7003的信息,其余两个同理

请添加图片描述
请添加图片描述
请添加图片描述

  • 将支付服务8001注册到Eureka集群中,只需要修改该工程的yaml文件中的service-url
eureka:
  client:
    register-with-eureka: true # 表示注册中心注册
    fetchRegistry: true       # 是否从EurekaServer抓取已有的注册信息默认为true 单节点无所谓 集群必须设置为true才能配合Ribbon使用负载均衡
    service-url:              # 将8001微服务注册到Eureka集群中的每一个节点中
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
      # defaultZone: http://localhost:7001/eureka # Eureka Server(单机版的Client注册)

测试结果:在

http://eureka7001.com:7001

中可以看到payment-service已经注册成功,同理其他两个server也能注册成功

请添加图片描述



c. 支付微服务的集群搭建(Service Provider)

  • 新建一个Module为cloud-provider-payment8002
  • 将8001服务工程中的

    dao,service以及dao层的映射文件daomapper全部进行拷贝
  • 在Controller中加上一个serverPort成员属性,并且在查询返回的结果CommonResult的msg中追加serverPort属性,表示当前服务来自哪一个
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        Payment res = service.getPaymentById(id);
        log.info("根据id查询结果:" + res);
        if(res != null){
            return new CommonResult(200, "查询成功,serverPort: " + serverPort, res);
        }else{
            return new CommonResult(555, "记录id不存在", null);
        }
    }
  • 修改配置文件
server:
  port: 8002
eureka:
  client:
    register-with-eureka: true # 表示注册中心注册
    fetchRegistry: true       # 是否从EurekaServer抓取已有的注册信息默认为true 单节点无所谓 集群必须设置为true才能配合Ribbon使用负载均衡
    service-url:              # 将8001微服务注册到Eureka集群中的每一个节点中
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka
      # defaultZone: http://localhost:7001/eureka # Eureka Server(单机版的Client注册)
  • 修改订单微服务的Controller中的url属性,URL为注册到server中的provider别名
// 对于单机版的服务订单地址可以写死
// public static final String PAYMENT_URL = "http://localhost:8001";
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

  • 启动EurekaServer,7001和7002服务

  • 再启动Service Provider,8001和8002服务
  • 此时如果直接使用order中的url是不能访问到数据的,由于只能找到是哪一组别名为CLOUD-PAYMENT-SERVICE来提供服务,但是具体是哪一台服务器却不能找到。因此需要在restTemplat配置类中使用

    注解@LoadBalanced开启负载均衡功能
    @Bean()
    @LoadBalanced // 开启restTemplate的负载均衡功能(否则order服务不能访问Service Provider)
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
  • 测试结果:默认使用

    轮询

    来调用具体的服务器来提供服务:服务端口每次轮流切换

    请添加图片描述

    请添加图片描述


Ribbon和Eureka整合后,order服务可以直接调用服务而不用再关心具体的地址和端口号



  • 主机名:服务名修改

在两个支付微服务的配置文件中增加,8002同理修改

eureka:
  instance:
    instance-id: payment8001

修改之前的主机名和服务名

请添加图片描述
修改之后的主机名和服务名(

如果存在4个service服务,只需要将Eureka Server重新启动就好了



请添加图片描述


  • 访问信息有IP信息提示
  # 主机名服务名修改  访问路径可以显示IP地址(默认是主机名:服务名)
  instance:
    instance-id: payment8001
    prefer-ip-address: true



5.6. 服务发现Discovery


对于注册进Eureka Server中的微服务,可以通过服务发现来获取该服务的信息

在两个微服务的主程序类上添加注解

@EnableDiscoveryClient

在Controller中增加一个方法,日志打印出服务和服务实例

     // 服务发现来获取注册到server的服务信息
    @Autowired
    private DiscoveryClient discoveryClient;

@GetMapping("/payment/discovery")
    public Object getDiscovery(){
        // 获取所有的微服务(payment,order,...)
        List<String> services = discoveryClient.getServices();
        for(String service : services){
            log.info("******service: " + service);
        }
        // 获取某一个微服务的所有实例(payment8001,payment8002,...)
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD_PAYMENT_SERVICE");
        for(ServiceInstance instance : instances){
            log.info("******instance:\t" + instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }
        return this.discoveryClient.toString();
    }

测试服务发现信息

请添加图片描述



5.7. Eureka的自我保护

概念:保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,

Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务

,也就是启动Eureka Server之后,在Eureka首页上看到的 一串红字


某个时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存

属于CAP理论中的

AP分支

(可用性和分区容忍性):

当发生网络分区时,最大努力保证可用性



为什么会产生Eureka自我保护机制?

为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,Eureka Server

不会立刻

将EurekaClient服务删除



什么是自我保护模式?

默认情况下,如果EurekaServer在一定时间内没有接受到某个微服务实例的心跳,EurekaServer将会立刻注销该实例(默认90s)。但是

当网络分区故障发生(延时,卡顿,拥挤)时,微服务与EurekaServer之前无法正常通信

,以上行为可能变得非常危险。

由于微服务本身是健康的,此时不应该注销掉这个微服务,Eureka通过‘自我保护机制’来解决这个问题,当EurekaServer节点在短时间内丢失过多客户端时(可能发生网络分区故障),那么这个节点就会进入自我保护模式

默认情况下,Eureka Client会定时向Eureka Server端发送心跳包,如果Eureka在Server端一定时间内(90s)没有收到Client端的心跳包,EurekaServer将会立刻注销该实例。当EurekaServer节点在短时间内丢失过多客户端时(可能发生网络分区故障),那么这个节点就会进入自我保护模式,不会删除该服务




怎么禁止自我保护机制?

  • 设置Server端口的配置文件
eureka:
  server:
    enable-self-preservation: false      # 关闭自我保护机制
    eviction-interval-timer-in-ms: 20000 # 20s没有发送心跳包,直接注销微服务
  • 设置Client端的配置文件
eureka:
  instance:
   # 设置client发送心跳包的时间间隔(默认30s)
    lease-renewal-interval-in-seconds: 15
    # EurekaServer收到最后一次心跳包之后的等待时间,默认90s
    lease-expiration-duration-in-seconds: 60



版权声明:本文为m0_45971439原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。