Spring_Cloud笔记一

SpringCloud微服务

1. 微服务架构编码构建

1.1 IDEA新建project工作空间

创建父工程步骤:

  1. 微服务cloud整体聚合父工程Project

    • NewProject

    • 聚合总工程名字:cloud2020

    • Maven选版本:3.6.3

    • 工程名字****

    • 字符编码

    • 注解生效激活

    • java编译版本选jdk1.8

    • File Type过滤

      填入的代码块:

      1
      *.hprof;*.idea;*.iml;*.pyc;*.pyo;*.rbc;*.yarb;*~;.DS_Store;.git;.hg;.svn;CVS;__pycache__;_svn;vssver.scc;vssver2.scc;
  2. 父工程POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    <?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.atguigu.springcloud</groupId>
    <artifactId>cloud2020</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--表示这是一个pom父工程的文件-->
    <packaging>pom</packaging>

    <!-- 统一管理jar包版本 -->
    <properties>
    <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>
    <log4j.version>1.2.17</log4j.version>
    <lombok.version>1.16.18</lombok.version>
    <mysql.version>8.0.19</mysql.version>
    <druid.version>1.1.16</druid.version>
    <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>
    <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
    <dependencyManagement>
    <dependencies>
    <!--spring boot 2.2.2-->
    <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 alibaba 2.1.0.RELEASE-->
    <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>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid.version}</version>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.spring.boot.version}</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>${junit.version}</version>
    </dependency>
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${log4j.version}</version>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${lombok.version}</version>
    <optional>true</optional>
    </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>
  3. Maven工程落地细节复习

    • Maven中的DependencyManager和dependencies
      maven使用DependencyManager元素来提供了一种管理依赖版本号的方式。==通常会在一个组织或者项目的最顶层的父pom中看到此元素。==
      使用dependencyManagement可以使所有子项目中引用一个依赖而不用显示列出版本号。Maven会沿父子层级向上走,直到找到dependencyManagement元素的项目 ,然后它就会使用版本号
      如果某一个子项目需要另外一个版本,加上version即可。如果子项目中声明了版本号,那么使用子项目中的jar包。

==dependencyManagement只是声明依赖,并不实现引入,一次子项目需要显式的声明需要用的依赖。==

* maven中跳过单元测试
  1. 父工程创建完成后执行mvn:install将父工程发不到仓库方便子工程继承

1.2 Rest微服务工程构建

1.2.1 构建步骤

1.2.1.1 Cloud-provider-payment8001微服务提供者支付Module模块
  1. 建cloud-provider-payment8001

  2. 改pom文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
        <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8001</artifactId>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <!--<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.3.1.RELEASE</version>
    </dependency>-->
    </dependencies>
    </project>
  3. 写yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    server:
    port: 8001

    spring:
    application:
    name: cloud-payment-service
    datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    # serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
    url: jdbc:mysql://111.229.203.5:3306/db2019?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: weaver
    password: 192612
    type: com.alibaba.druid.pool.DruidDataSource

    mybatis:
    mapperLocations: classpath:mapper/*.xml
    type-aliases-package: com.atguigu.springcloud.entities
  4. 主启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.atguigu.springcloud;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @program: cloud2020
    * @description: 主启动类
    * @author: Mr.Wang
    * @create: 2020-08-15-22-19
    */
    @SpringBootApplication
    public class PaymentMain8001 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8001.class,args);
    }
    }
  5. 业务类

    1. 建表sql

      1
      2
      3
      4
      5
      CREATE TABLE `payment` (
      `id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `serial` VARCHAR (200) DEFAULT'',
      PRIMARY KEY (id)
      ) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8
    2. entitles

      • 主实体Payment

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        package com.atguigu.springcloud.entities;

        import lombok.AllArgsConstructor;
        import lombok.Data;
        import lombok.NoArgsConstructor;

        import java.io.Serializable;

        /**
        * @program: cloud2020
        * @description: 支付实体类
        * @author: Mr.Wang
        * @create: 2020-08-16-00-08
        */
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class Payment implements Serializable {
        private Long id;
        private String serial;
        }
      • son封装体CommonResult

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        package com.atguigu.springcloud.entities;

        import lombok.AllArgsConstructor;
        import lombok.Data;
        import lombok.NoArgsConstructor;

        /**
        * @program: cloud2020
        * @description: 穿结果给前端的类
        * @author: Mr.Wang
        * @create: 2020-08-16-00-14
        */
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class CommonResult<T> {
        private Integer code;
        private String message;
        private T data;
        public CommonResult(Integer code,String message){
        this(code,message,null);
        }
        }
    3. dao

      • 接口PaymentDao

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        package com.atguigu.springcloud.dao;

        import com.atguigu.springcloud.entities.Payment;
        import org.apache.ibatis.annotations.Mapper;
        import org.apache.ibatis.annotations.Param;
        import org.springframework.stereotype.Repository;
        import org.springframework.web.bind.annotation.RestController;

        //@Repository
        @Mapper
        public interface PaymentDao {
        public int create(Payment payment);
        public Payment getPaymentById(@Param("id")Long id);

        }
      • mybatis的映射文件PaymentMapper.xml

        • 路径:src\main\resources\mapper\PaymentMapper.xml

        • mapper映射文件

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          <?xml version="1.0" encoding="UTF-8"?>
          <!DOCTYPE mapper
          PUBLIC '-//mybatis.org//DTD Mapper 3.0//EN'
          'http://mybatis.org/dtd/mybatis-3-mapper.dtd'>
          <mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
          <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
          insert into payment(serial) values (#{serial});
          </insert>
          <resultMap id="BaseResultMap" type="Payment">
          <id column="id" property="id" jdbcType="BIGINT"></id>
          <result column="serial" property="serial" jdbcType="VARCHAR"/>
          </resultMap>
          <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
          select * from payment where id = #{id};
          </select>
          </mapper>
    4. service

      • 接口PaymentService

        1
        2
        3
        4
        5
        6
        7
        8
        9
        package com.atguigu.springcloud.service;

        import com.atguigu.springcloud.entities.Payment;
        import org.apache.ibatis.annotations.Param;

        public interface PaymentService {
        public int create(Payment payment);
        public Payment getPaymentById(Long id);
        }
      • 实现类

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        package com.atguigu.springcloud.service.impl;

        import com.atguigu.springcloud.dao.PaymentDao;
        import com.atguigu.springcloud.entities.Payment;
        import com.atguigu.springcloud.service.PaymentService;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Service;

        import javax.annotation.Resource;

        /**
        * @program: cloud2020
        * @description: 支付功能的service层
        * @author: Mr.Wang
        * @create: 2020-08-16-00-42
        */
        @Service
        public class PaymentServiceImpl implements PaymentService {
        @Resource
        private PaymentDao paymentDao;
        @Override
        public int create(Payment payment) {
        return paymentDao.create(payment);
        }

        @Override
        public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
        }
        }
    5. controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.atguigu.springcloud.Controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
* @program: cloud2020
* @description: 支付的控制层
* @author: Mr.Wang
* @create: 2020-08-16-00-48
*/
@RestController
@Slf4j
public class PaymentController {

@Resource
private PaymentService paymentService;

@PostMapping(value = "/payment/create")
public CommonResult create(Payment payment){
int result = paymentService.create(payment);
log.info("****插入结果:"+result);
if(result > 0){
return new CommonResult(200,"插入数据库成功",result);
}else{
return new CommonResult(444,"插入数据失败");
}
}

@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("****查询结果:"+payment);
if(payment != null){
return new CommonResult(200,"查询成功",payment);
}else{
return new CommonResult(444,"没有对应记录,查询查询失败");
}
}
}
1.2.1.2 热部署Devtools
  1. Adding devtools to your project

    1
    2
    3
    4
    5
    6
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
  2. Adding plugin to your pom.xml
    下段的配置我们需要粘贴进聚合父类总工程的pom.xml里面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <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>
  3. Enabling automatic build

  4. Update the value of
    使用快捷键command+option+shift+/选择Registry

  5. 重启IDEA
    关闭并重新启动idea开发工具

1.2.1.3 cloud-consumer-order80微服务消费者订单Module模块
  1. 建cloud-consumer-order80
    同8001服务端创建moudle的方法相同

  2. 改POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</artifactId>
    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>
  3. 写YML

    1
    2
    server:
    port: 80
  4. 主启动
    同上

  5. 业务类

    • RestTemplate是什么
      RestTemplate提供了多种便捷访问远程Http服务的方法
      是一种简单便捷的访问restful服务模版累,是Spring提供的用于访问Rest服务的客户端模版工具集

    • 官网及使用
      官网地址

    • 使用:*
      使用restTemplate访问restful接口非常的简单粗暴无脑。(url,requestMap,ResponseBean.class)这三个参数分别代表Rest请求地址、请求参数、Http响应转换被转换成对象类型

    • config配置类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package com.atguigu.springcloud.config;

      import org.springframework.beans.factory.annotation.Configurable;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.client.RestTemplate;

      /**
      * @program: cloud2020
      * @description: 配置类
      * @author: Mr.Wang
      * @create: 2020-08-16-20-33
      */

      @Configuration
      public class ApplicationContextConfig {
      @Bean
      public RestTemplate getRestTemplate(){
      return new RestTemplate();
      }
      }
    • 创建controller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      package com.atguigu.springcloud.controller;

      import com.atguigu.springcloud.entities.CommonResult;
      import com.atguigu.springcloud.entities.Payment;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;
      import org.springframework.web.client.RestTemplate;

      /**
      * @program: cloud2020
      * @description: 订单类web层
      * @author: Mr.Wang
      * @create: 2020-08-16-19-57
      */
      @RestController
      @Slf4j
      public class OrderController {
      public static final String PAYMENT_URL = "http://localhost:8001";
      @Autowired
      private RestTemplate restTemplate;

      @GetMapping("/consumer/payment/create")
      public CommonResult<Payment> create(Payment payment){
      return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
      }
      @GetMapping("/consumer/payment/get/{id}")
      public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
      return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
      }
      }
  6. 测试

  7. Run Dashboard的配置

    • 通过修改idea的workspace.xml的方式来快速打开Run Dashboard窗口

    • 开启Run DashBoard

      1
      2
      3
      4
      5
      <option name="configurationTypes">
      <set>
      <option value="SpringBootApplicationConfigurationType" />
      </set>
      </option>
    • 可能由于idea版本的不同,需要关闭重新启动

1.2.1.4 工程重构
  1. 观察问题
    系统中有重复的部分,需要进行重构

  2. 新建-cloud-api-commons

  3. pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-commons</artifactId>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.0</version>
    </dependency>
    </dependencies>

    </project>
  4. entities

  5. maven命令clean install

  6. 订单80和支付8001分别改造

    • 删除各自的原先有过的entities文件夹

    • 各自黏贴pom内容

      1
      2
      3
      4
      5
      6
      <!--引入自定义的api通用包-->
      <dependency>
      <groupId>com.atguigu.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
      </dependency>

1.2.2 目前工程详图

2. Eureka服务注册与发现

2.1 Eureka基础知识

  1. 什么是服务治理

  2. 什么是服务注册
    Eureka Server 作为服务注册功能的服务器,它是服务注册中心,而系统中其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接,这样系统维护人员就可以通过 Eureka Server来监控各个微服务是否正常运行。
    在服务注册与发现中有一个注册中心,服务器启动时,会把当前自己的服务器信息比如服务地址,通信地址等注册到注册中心上,另一方(消费者)以别名的方式在注册中心上获取实际的服务器通讯地址,然后再实现本地RPC调用远程RPC。

  3. Eureka两组件

    1. Eureka Server 提供服务注册服务
      各个微服务节点通过配置启动后,会在 EurekaServer中进行注册,这样 EurekaServer中的服务注册表中将会存储所有可用服务节点的信息。
    2. EurekaClient通过注册中心进行访问
      是一个Java客户端,用于简化与 Eureka Server的交互,客户端也同时具备一个内置的,使用 轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server 发送心跳(默认周期30秒)。如果Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中将这个服务节点移除(默认90秒)

2.2 单机Eureka构建步骤

2.2.1 IDEA生成eurekaServer端服务注册中心类似物业公司

  1. 建Moudelcloud-eureka-server7001

  2. 改POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-eureka-server7001</artifactId>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    </dependency>
    </dependencies>

    </project>
  3. 写YML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 7001
    eureka:
    instance:
    hostname: localhost # eureka服务端实例名称
    client:
    #false表示不向注册中心注册自己
    register-with-eureka: false
    #false表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
    # 设置与 eureka server交互的地址查询服务和注册服务都需要依赖这个地址
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  4. 主启动

    @EnableEurekaServer:声明自己是 eureka 的服务端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.atguitu.springcloud;

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

    /**
    * @program: cloud2020
    * @description: Eureka主启动类
    * @author: Mr.Wang
    * @create: 2020-08-17-14-21
    */
    @SpringBootApplication
    @EnableEurekaServer //表示这个就是注册中心,等级注册
    public class EurekaMain7001 {
    public static void main(String[] args) {
    SpringApplication.run(EurekaMain7001.class,args);
    }
    }
  5. 测试

    • 测试地址:http://localhost:7001
    • 结果页面

2.2.2 EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider,类似尚硅谷学校对外提供授课服务

  1. 建立cloud-provider-payment8001

  2. 改POM
    增加客户端的jar包

    1
    2
    3
    4
    5
    <!--eureka-client-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  3. 写YML
    更改8001的yaml配置文件,增加一下配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    eureka:
    client:
    #表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用 负载均衡
    fetch-registry: false
    service-url:
    # 入住的地址
    defaultZone: http://localhost:7001/eureka
  4. 主启动
    在主启动上添加@EnableEurekaClient注解,表示这个是客户端

  5. 测试

  6. 自我保护机制

2.2.3 EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer,类似来尚硅谷上课消费的各位同学

  1. 修改cloud-consumer-order80模块
  2. POM文件修改,同8001模块相同
  3. 写YML
  4. 主启动,同8001模块相同
  5. 测试

2.3 集群Eureka构建步骤

2.3.1 集群原理说明


微服务RPC远程服务调用最核心是什么:
高可用,如果注册中心只有一个,出了故障就会导致整个服务环境不可用
解决方法:搭建Eureka注册中心集群,实现负载均衡+故障排错
集群注册的原理:互相注册,相互守望

2.3.2 EurekaServer集群环境构建步骤

  1. 参考cloud-eureka-server7001新建cloud-eureka-server7002

  2. 改POM

  3. 修改映射配置
    在系统的hosts文件中添加一下配置

    1
    2
    127.0.0.1  eureka7001.com
    127.0.0.1 eureka7002.com
  4. 写YML(以前单机)
    单机的看以前的配置

    • 7001的yml配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      server:
      port: 7001
      eureka:
      instance:
      hostname: 127.0.0.1 # eureka服务端实例名称,集群名字要不同有区分
      client:
      #false表示不向注册中心注册自己
      register-with-eureka: false
      #false表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
      fetch-registry: false
      service-url:
      # 设置与 eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://localhost:7002/eureka/
    • 7002的yml配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      server:
      port: 7002
      eureka:
      instance:
      hostname: localhost
      client:
      #false表示不向注册中心注册自己
      register-with-eureka: false
      #false表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
      fetch-registry: false
      service-url:
      # 设置与 eureka server交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://127.0.0.1:7001/eureka/
  5. 主启动

2.3.3 将支付服务8001微服务发布到上面2台Eureka集群配置中

yml配置文件的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server:
port: 8001

spring:
application:
name: cloud-payment-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
url: jdbc:mysql://111.229.203.5:3306/db2019?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&useSSL=false
username: weaver
password: 192612
type: com.alibaba.druid.pool.DruidDataSource

mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities

eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用 负载均衡
fetch-registry: false
service-url:
# 入住的地址
#defaultZone: http://localhost:7001/eureka
# 集群的配置
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka

2.3.4 将订单服务80微服务发布到上面2台Eureka集群配置中

修改配置同2.3.3

2.3.5 测试01

  1. 先要启动EurekaServer,7001/7002服务
  2. 再要启动服务提供者provider,8001服务
  3. 再要启动消费者,80
  4. http://localhost/consumer/payment/get/31

2.3.6 支付服务提供者8001集群环境构建

  1. 参考cloud-provider-payment8001新建cloud-provider-payment8002

  2. 改POM
    同8001的POM文件一致

  3. 写YML

    • 7001
    • 7002
  4. 主启动类

  5. 修改8001/8002的Controller

    • 8001

      1
      2
      3
      // 在各个方法中调用 serverPort 查看端口号
      @Value("${server.port}")
      private String serverPort;
    • 8002
      配置方法同8001

2.3.7 负载均衡

  1. bug
    订单服务访问地址不能写死
  2. 使用@LoadBalance注解赋予RestTemplate负载均衡的能力
    • 在之前单机版中写死为8001,但是集群后有8001与8002,
    • 进入 http://eureka7001.com:7001/ 查看 8001 与 8002 对应的名称 application
    • 将 PAYMENT_URL 改为 http+application 名 :http://CLOUD-PAYMENT-SERVICE
    • 此时未开启负载均衡不能访问页面:将80端口下的配置类ApplicationContextConfig 下生成的 RestTemplate 的bean方法上添加注解 @LoadBalanced
    • http://localhost/consumer/payment/get/34 访问查看端口号,可以看到在8001与8002之间来回切换
    • 在客户端的yml配置中需要开启对负载均衡的支持
  3. ApplicationContextBean
    提前说下Ribbon的负载均衡功能

2.3.8 测试02

  1. 先要启动EurekaServer,7001/7002服务
  2. 再要启动服务提供者provider,8001/8002服务
  3. http://localhost/consumer/payment/get/31
  4. 结果
    • 负载均衡效果达到
    • 8001/8002端口交替出现
  5. Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能了

2.4 actuator微服务信息完善

  1. 主机名称:服务名称修改

    • 当前问题

    • 修改cloud-provider-payment8001

      • 修改部分

        1
        2
        3
        instance:
        instance-id: payment8002 # 自定义主机名
        #prefer-ip-address: true # 设置暴露ip地址

      • 完整内容

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        server:
        port: 8002

        spring:
        application:
        #s
        name: cloud-payment-service
        datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        # serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
        url: jdbc:mysql://111.229.203.5:3306/db2019?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: weaver
        password: 192612
        type: com.alibaba.druid.pool.DruidDataSource

        mybatis:
        mapperLocations: classpath:mapper/*.xml
        type-aliases-package: com.atguigu.springcloud.entities

        eureka:
        client:
        #表示是否将自己注册进EurekaServer默认为true
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用 负载均衡
        fetch-registry: false
        service-url:
        # 入住的地址
        #defaultZone: http://localhost:7001/eureka
        # 集群的配置
        defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
        instance:
        instance-id: payment8002 # 自定义主机名
        prefer-ip-address: true # 设置暴露ip地址
    • 修改之后

  2. 访问信息有ip信息提示

    • 当前问题:没有ip提示
    • 修改修改cloud-provider-payment8001
      • 修改部分
        见上面截图部分
      • 完整内容
        见上面截图部分
    • 修改之后

2.5 服务发现Discovery

  1. 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

  2. 修改cloud-provider-payment8001的Controller
    目前存在一个问题,返回的discoveryClient中不能像视频中一样返回带有服务名称的json串,目前返回的为空

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("/payment/discovery")
    public DiscoveryClient discovery(){
    // 得到所有服务名
    List<String> services = discoveryClient.getServices();
    services.forEach(ele->{
    log.info("***service***"+ele);
    });
    // 得到服务名对应的信息
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    instances.forEach(ele->{
    log.info(ele.getServiceId()+"\t"+ele.getHost()+"\t"+ele.getPort()+"\t"+ele.getUri());
    });

    return discoveryClient;
    }
  3. 8001主启动类
    8001 主类添加注解 @EnableDiscoveryClient

  4. 自测

2.6 Eureka自我保护

  1. 故障现象

  2. 导致原因
    一句话:某一时刻某一个微服务不可用了,Eureka不会立即清理,依旧会对该微服务的信息进行保存。属于CAP里面的AP分支

    1. 为什么会产生自我保护?
      为了防止 EurekaClient 可以正常运行,但是在 EurekaServer 网络不通的情况下,EurekaServer 不会立刻将 EurekaClient 服务剔除。
    2. 什么是自我保护模式?
      默认情况下,EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90s),但是当网络分区故障发生,微服务与 EurekaServer 之间无法正常通信,以上行为就非常危险。当EurekaServer节点在短时间内丢失过多客户端时,那么这个节点就会进入自我保护模式。
  3. 怎么禁止自我保护(一般生产环境中不会禁止自我保护)

    • 注册中心eureakeServer端7001

      1. 出厂默认,自我保护机制是开启的eureka.server.enable-self-preservation = true

      2. 使用eureka.server.enable-self-preservation = false可以禁用自我保护模式

      3. 关闭效果

      4. 在eurekaServer端7001处设置关闭自我保护机制

    • 生产者客户端eureakeClient端8001

      • eureka.instance.lease-renewal-interval-in-seconds=30 eureka.instance.lease-expiration-duration-in-seconds=90

      • 测试

        • 7001和8001都配置完成
        • 先启动7001在启动8001
        • 先关闭8001 马上被删除

3. Zookeeper服务注册与发现

3.1 Eureka停止更新了你怎么办

https://github.com/Netflix/eureka/wiki

3.2 SpringCloud整合Zookeeper代替Eureka

3.2.1 注册中心Zookeeper

  1. zookeeper是一个分布式协调工具,可以实现注册中心功能
  2. 关闭Linux服务器防火墙后启动zookeeper服务器
  3. zookeeper服务器取代Eureka服务器,zk作为服务注册中心

3.2.2 服务提供者

  1. 新建cloud-provider-payment8004

  2. POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerzk-order80</artifactId>


    </project>
  3. YML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 8004表示注册到zookeeper服务器的支付服务提供端口号
    server:
    port: 8004

    # 服务别名----注册zookeeper到注册中心名称
    spring:
    application:
    name: cloud-provider-payment
    cloud:
    zookeeper:
    connect-string: 49.235.24.12:2181
  4. 主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.atguigu.springcloud;

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

    /**
    * @program: cloud2020
    * @description: zk服务端启动类
    * @author: Mr.Wang
    * @create: 2020-08-18-17-20
    */
    @SpringBootApplication
    //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    @EnableDiscoveryClient
    public class PaymentMain8004 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8004.class,args);
    }
    }
  5. Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.atguigu.springcloud.controller;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.UUID;

    /**
    * @program: cloud2020
    * @description: 控制层
    * @author: Mr.Wang
    * @create: 2020-08-18-20-32
    */
    @RestController
    @Slf4j
    public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/zk")
    public String paymentzk(){
    return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
    }
    }
  6. 启动8004注册进zookeeper

    • 启动后的问题
    • 解决zookeeper半分jar包冲突
    • 排除zk冲突后的新POM
  7. 验证测试
    http://localhost:8004/payment/zk

  8. 验证测试2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 获取服务名
    [zk: localhost:2181(CONNECTED) 0] ls /services
    [cloud-provider-payment]
    # 获取流水号
    [zk: localhost:2181(CONNECTED) 1] ls /services/cloud-provider-payment
    [efc76371-522d-4d5d-8f56-f8fe4deb7a47]
    # 获取详细信息
    [zk: localhost:2181(CONNECTED) 2] get /services/cloud-provider-payment/efc76371-522d-4d5d-8f56-f8fe4deb7a47
    {"name":"cloud-provider-payment","id":"efc76371-522d-4d5d-8f56-f8fe4deb7a47","address":"WINDOWS-N0GUAG7","port":8004,"sslPort":null,"payload":{"@class":"org.springframework.cloud.zookeeper.discovery.ZookeeperInstance","id":"application-1","name":"cloud-provider-payment","metadata":{}},"registrationTimeUTC":1590232919360,"serviceType":"DYNAMIC","uriSpec":{"parts":[{"value":"scheme","variable":true},{"value":"://","variable":false},{"value":"address","variable":true},{"value":":","variable":false},{"value":"port","variable":true}]}}
  9. 思考

    • 服务节点是临时还是持久

      1
      2
      3
      4
      5
      6
      7
      8
      9
      [zk: localhost:2181(CONNECTED) 18] ls /services/cloud-provider-payment
      [efc76371-522d-4d5d-8f56-f8fe4deb7a47]
      [zk: localhost:2181(CONNECTED) 19] ls /services/cloud-provider-payment
      [efc76371-522d-4d5d-8f56-f8fe4deb7a47]
      [zk: localhost:2181(CONNECTED) 20] ls /services/cloud-provider-payment
      [efc76371-522d-4d5d-8f56-f8fe4deb7a47]
      [zk: localhost:2181(CONNECTED) 21] ls /services/cloud-provider-payment
      []
      [zk: localhost:2181(CONNECTED) 22]

3.2.3 服务消费者

  1. 新建cloud-consumerzk-order80

  2. POM
    同zookeeper服务端配置(8004)相同

  3. YML
    同zookeeper服务端配置(8004)相同

  4. 主启动
    同zookeeper服务端配置(8004)相同

  5. 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.atguigu.springcloud.controller;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;

    import javax.annotation.Resource;

    /**
    * @program: cloud2020
    * @description: 控制层
    * @author: Mr.Wang
    * @create: 2020-08-19-09-16
    */
    @RestController
    @Slf4j
    public class ConsumerController {

    private static final String INVOKE_URL = "http://cloud-provider-payment";
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/zk")
    public String paymentInfo(){
    String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
    return result;
    }
    }
  6. 启动8004注册进zookeeper

  7. 验证测试:http://localhost:8004/payment/zk

  8. 访问测试地址:http://localhost/consumer/payment/zk

4. Consul服务注册与发现

4.1 Consul简介

  1. 是什么:https://www.consul.io/intro/index.html
    Consul是一种服务网格解决方案,提供具有服务发现,配置和分段功能的全功能控制平面。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建完整的服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul附带了一个简单的内置代理,因此一切都可以直接使用,还支持Envoy等第三方代理集成。

  2. 能干嘛

    • 服务发现:Consul的客户端可以注册服务,例如 api或mysql,其他客户端可以使用Consul来发现给定服务的提供者。使用DNS或HTTP,应用程序可以轻松找到它们依赖的服务。

    • 运行状况检查:领事客户端可以提供任何数量的运行状况检查,这些检查可以与给定服务(“ Web服务器返回200 OK”)或与本地节点(“内存利用率低于90%”)相关联。操作员可以使用此信息来监视群集的运行状况,服务发现组件可以使用此信息将流量路由到运行状况不佳的主机之外。

    • KV商店:应用程序可以将Consul的分层键/值存储用于多种目的,包括动态配置,功能标记,协调,领导者选举等。简单的HTTP API使其易于使用。

    • 安全的服务通信:领事可以为服务生成并分发TLS证书,以建立相互TLS连接。 意图 可用于定义允许哪些服务进行通信。可以使用可以实时更改的意图轻松管理服务分段,而不必使用复杂的网络拓扑和静态防火墙规则。

    • 多数据中心:Consul开箱即用地支持多个数据中心。这意味着Consul的用户不必担心会构建其他抽象层以扩展到多个区域。

  3. 去哪下:https://www.consul.io/downloads.html

  4. 怎么玩:https://www.springcloud.cc/spring-cloud-consul.html

4.2 安装并运行Consul

  1. 官网安装说明:https://learn.hashicorp.com/consul/getting-started/install.html
  2. 下载完成后只有一个consul.exe文件,硬盘路径下双击运行,查看版本信息
  3. 使用开发模式启动

  1. 将下载的exe文件双击即可安装
  2. 在 exe ==文件的目录==下打开黑窗口
  3. 输入 consul –version 检测是否安装成功
  4. 启动服务:consul agent -dev
  5. 通过 http://localhost:8500/ 访问web界面

4.3 服务提供者

  1. 新建Module支付服务provider8006cloud-providerconsul-payment8006

  2. POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>
  3. YML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 8006
    spring:
    application:
    name: consul-provider-payment
    cloud:
    consul:
    discovery:
    service-name: ${spring.application.name}
    host: 111.229.203.5
    port: 8500
  4. 主启动类

  5. 业务类Controller

  6. 验证测试:http://localhost:8006/payment/consul

4.4 服务消费者

  1. pom

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerconsul-order80</artifactId>

    <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-consul-discovery -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>
  2. yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 80
    spring:
    application:
    name: cloud-consumer-order
    cloud:
    consul:
    host: localhost
    port: 8500
    discovery:
    service-name: ${spring.application.name}

4.5 三个注册中心异同点

  1. CAP
    • C:Consistency(强一致性)
    • A:Availability(可用性)
    • P:Partition tolerance(分区容错)
    • CAP理论关注粒度是数据,而不是整体系统设计的策略
  2. 经典CAP图

5. Ribbon负载均衡服务调用

5.1 概述

  1. 是什么
    Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套==客户端负载均衡的工具==。
    简单的说,Ribbonn是Netflix的开源项目,主要功能 是提供==客户端的软件负载均衡算法和服务调用。==Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。就是在配置文件中列出 Loa Balancer后面所有机器,Ribbon会自动帮助你基于某种规则 (如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

  2. 官网资料:https://github.com/Netflix/ribbon/wiki/Getting-Started

  3. 能干嘛

    • LB(负载均衡)
      • 负载均衡(Load Balance)是什么
        将用户的请求平摊的分配到多个服务上,从而达到HA(高可用),常见的负载均衡有 Nginx,LVS,硬件 F5等。
      • Ribbon 本地负载均衡客户端 VS Nginx 服务端负载均衡
        Nginx 是服务器 负载均衡,客户端所有请求都会交给 nginx,然后由 nginx实现请求转发。即负载均衡是由服务端实现的。
        Ribbon 是本地负载均衡,在微服务调用接口时,在注册中心上获取注册信息服务列表 之后缓存在JVM本地,从而实现本地RPC远程服务调用技术
    • 前面我们讲解过了80通过轮询负载访问8001/8002
    • 一句话
      负载均衡+RestTemplate 调用

5.2 Ribbon负载均衡演示

  1. 架构说明
    Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eurake结合之事其中一个实例

    • Ribbon工作时有两步
      1. 第一步先选择 EurekaServer,优先选择统一区域负载较少的 server
      2. 第二部再根据用户指定的策略,从server取到的服务注册列表中选择一个地址。其中 Riibon 提供了多种策略(轮询,随机,根据响应时间加权)。
  2. POM
    不需要引入

  3. RestTemplate的使用

    • getForObject方法/getForEntity方法
      getForObject返回对象为响应体中数据转化成的对象,基本上可以理解为json
      getForEntity返回对象为ResponseEntity对象,包含响应中的一些重要信息,比如响应头、响应状态码、响应体

    • postForObject/postForEntity

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @GetMapping("/consumer/payment/forentity/{id}")
      public CommonResult<Payment> getPayment1(@PathVariable("id") Long id){
      ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
      if(entity.getStatusCode().is2xxSuccessful()){
      return entity.getBody();
      }else{
      return new CommonResult(444,"操作失败");
      }
      }
    • GET请求方法

    • POST请求方法

5.3 Ribbon核心组件IRule

  1. IRule:根据特定算法从服务列表中选取一个要访问的服务

    1. RoundRobinRule 轮询
    2. RandomRule 随机
    3. RetryRule 先按照RoundRobinRule的 策略获取服务,如果获取服务失败则在指定时间里进行重试,获取可用服务
    4. WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快,实例选择权重越大 ,越容易被选择
    5. BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器 跳闸状态的服务,然后选择一个并发一个最小的服务
    6. BestAvaibilityFilteringRule 先过滤掉故障实例,再选择并发量较小的实例
    7. ZoneAvoidanceRule 默认规则,符合server所在区域的性能和server的可用性选择服务器
  2. 如何替换

    • 修改cloud-consumer-order80

    • 注意配置细节
      IRule配置类不能放在@ComponentSan 的包及子包下,因为默认的扫描会变成全局负载均衡都按照这样的规则。

    • 新建package:com.atguigu.myrule

    • 上面包下新建MySelfRule规则类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package com.atguigu.myrule;

      import com.netflix.loadbalancer.IRule;
      import com.netflix.loadbalancer.RandomRule;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      /**
      * @program: cloud2020
      * @description: 规则类
      * @author: Mr.Wang
      * @create: 2020-08-20-09-12
      */
      @Configuration
      public class MyselfRule {
      @Bean
      public IRule myRule(){
      //使用随机的负载均衡
      return new RandomRule();
      }
      }
      • 主启动类添加@RibbonClient

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        package com.atguigu.springcloud;

        import com.atguigu.myrule.MyselfRule;
        import javafx.application.Application;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
        import org.springframework.cloud.netflix.ribbon.RibbonClient;

        /**
        * @program: cloud2020
        * @description: 订单类的启动器
        * @author: Mr.Wang
        * @create: 2020-08-16-19-52
        */
        @SpringBootApplication
        @EnableEurekaClient
        // 选择要接收的服务和配置类
        @RibbonClient(name="cloud-payment-sesrvice",configuration = MyselfRule.class)
        public class OrderMain80 {
        public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
        }
        }

    • 测试

5.4 Ribbon负载均衡算法

  1. 原理
    负载均衡算法:rest接口第几次请求数 % 服务器集群=实际调用服务器位置下标,每次服务重启后rest接口计数从1开始

    总台数:2台

请求数 调用下标
1 1%2=1
2 2%2=0
3 3%2=1
4 4%2=0

  1. RoundRobinRule源码

  2. 手写
    具体详细的请看脑图笔记…

    6. OpenFeign服务接口调用

    6.1 概述

  3. OpenFeign是什么
    Feign是声明性Web服务客户端。它使编写Web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用HttpMessageConvertersSpring Web中默认使用的注释。Spring Cloud集成了Ribbon和Eureka以及Spring Cloud LoadBalancer,以在使用Feign时提供负载平衡的http客户端。
    https://github.com/spring-cloud/spring-cloud-openfeign

  4. 能干嘛

    • 使编写Java Http客户端更加容易
      使用 RestTemplate+Ribbon 时,利用 RestTemplate 对http 请求的封装处理,形成一套模板化的调用方法,但是在实际中,由于对服务的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由他来帮助我们定义和实现服务接口的定义。在==Feign的实现下我们只需要创建一个接口并使用注解来配置它(以前是Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)==。自动封装服务调用客户端的开发量。
    • Feign集成了Ribbon
      利用Ribbon维护了Payment的服务列表信息,并且实现了轮询实现客户端的负载均衡。而与Ribbon不同的是,==feign只需要定义服务绑定接口且以声明式的方法==,优雅而简单的实现服务调用
  5. Fegin和OpenFeign两者区别

6.2 OpenFeign使用步骤

  1. 接口+注解:微服务调用的接口+@FeignClient

  2. 新建cloud-consumer-feign-order80
    Fegin用在消费端

  3. POM

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-order80</artifactId>

    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>
  4. YML

    1
    2
    3
    4
    5
    6
    7
    server:
    port: 80
    eureka:
    client:
    register-with-eureka: false
    service-url:
    defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
  5. 主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.atguigu.springcloud;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;

    /**
    * @program: cloud2020
    * @description: 主启动类
    * @author: Mr.Wang
    * @create: 2020-08-20-11-24
    */
    @SpringBootApplication
    @EnableDiscoveryClient
    //激活fengin并使用
    @EnableFeignClients
    public class OrderfeignMain80 {
    public static void main(String[] args) {
    SpringApplication.run(OrderfeignMain80.class,args);
    }
    }
  6. 业务类

    • 业务逻辑接口+@FeignClient配置调用provider服务

    • 新建PaymentFeignService接口并新政注解@FeignClient

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      package com.atguigu.springcloud.service;

      import com.atguigu.springcloud.entities.CommonResult;
      import com.atguigu.springcloud.entities.Payment;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.stereotype.Component;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;

      @Component
      /**
      * name:找哪个微服务
      */
      // 将业务提供者的名写进去
      @FeignClient(name="cloud-payment-service")
      public interface PaymentFeignService {
      // 将业务提供者的controller路径和方法复制粘贴进来
      @GetMapping(value = "/payment/get/{id}")
      public CommonResult getPaymentById(@PathVariable("id") Long id);
      }
    • 控制层Controller

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      package com.atguigu.springcloud.controller;

      import com.atguigu.springcloud.entities.CommonResult;
      import com.atguigu.springcloud.entities.Payment;
      import com.atguigu.springcloud.service.PaymentFeignService;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;

      /**
      * @program: cloud2020
      * @description: 控制层
      * @author: Mr.Wang
      * @create: 2020-08-20-11-36
      */
      @RestController
      @Slf4j
      public class OrderFeignController {
      @Resource
      private PaymentFeignService paymentFeignService;

      @GetMapping("/consumer/payment/get/{id}")
      public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
      return paymentFeignService.getPaymentById(id);
      }
      }
  7. 测试

  8. 小总结

6.3 OpenFeign超时控制

  1. 超时设置,故意设置超时演示出错情况

    • 服务提供方8001故意写暂停程序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @GetMapping("/payment/feign/timeout")
      public String paymentFeignTimeout(){
      try {
      TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      return serverPort;
      }
    • 服务消费方80添加超时方法PaymentFeginService

    • 服务消费方80添加超时方法OrderFeginController

    • 测试

  2. OpenFeign默认等待一秒钟,超期后报错

  3. 是什么
    默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置客户端的超时控制。

  4. YML文件里需要开启OpenFeign客户端超时控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
# 添加一下的配置
# 设置feign客户端超时时间(openfign默认支持ribbon)
ribbon:
# 指得失建立链接所用的时间,适用于网络状况正常的情况下。两端链接所用的时间
ReadTimeout: 5000
# 指的是建立链接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

6.4 OpenFeign日志打印功能

  1. 日志打印功能

  2. 是什么
    Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是==对Feign接口调用情况进行监控和输出==

  3. 日志级别

    • NONE:默认不显示日志
    • BASIC:仅记录请求方法,URL,响应状态及执行时间
    • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
    • FULL:除了HEADERS中定义的信息外,还有请求和响应的正文及元数据
  4. 配置日志bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.atguigu.springcloud.config;

    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * @program: cloud2020
    * @description: feign的配置类
    * @author: Mr.Wang
    * @create: 2020-08-20-14-52
    */
    @Configuration
    public class FeignLog {
    @Bean
    Logger.Level feignLoggerlevel(){
    return Logger.Level.FULL;
    }
    }
  5. YML文件里需要开启日期的Feign客户端

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    server:
    port: 80
    eureka:
    client:
    register-with-eureka: false
    service-url:
    defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/
    # 设置feign客户端超时时间(openfign默认支持ribbon)
    ribbon:
    # 指得失建立链接所用的时间,适用于网络状况正常的情况下。两端链接所用的时间
    ReadTimeout: 5000
    # 指的是建立链接后从服务器读取到可用资源所用的时间
    ConnectTimeout: 5000
    logging:
    level:
    #feign日志以什么级别监控哪个接口
    com.atguigu.springcloud.service.PaymentFeginService: debug
  6. 后台日志查看

7. Hystrix断路器

7.1 概述

  1. 分布式系统面临的问题
    复杂分布式系统中的应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免的失败。
    多个微服务之间调用时,假设 A 调B和C,B和C又调其他微服务,就是所谓的扇出。当扇出的链路上某个微服务响应时间过长或不可用对A的调用就会占用越来越多的资源,进而引起系统崩溃 ,所谓的雪崩效应。
  2. 是什么
    Hystrix 是处理分布式系统的延迟和容错的开源库,保证一个依赖出现问题时不会导致整体服务失败,避免级联故障,以提高分布式系统弹性。
    断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控,向调用方返回一个符合预期的可处理的备选响应,而不是长时间的等待或抛出调用方法无法处理的异常 。
  3. 能干嘛
    • 服务降级
    • 服务熔断
    • 接近实时的监控
  4. 官网资料:https://github.com/Netflix/Hystrix/wiki/How-To-Use
  5. Hystrix官宣,停更进维:https://github.com/Netflix/Hystrix
    • 被动修复bugs
    • 不再接受合并请求
    • 不再发布新版本

7.2 Hystrix重要概念

  1. 服务降级
    服务器忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。fallback
    哪些情况会导致服务降级?
    • 程序运行异常
    • 超时
    • 服务熔断触发服务降级
    • 线程池/信号量打满
  2. 服务熔断
    • 类比保险丝达到最大服务访问时,直接拒绝访问,拉闸限电,然后调用服务降级的方法返回友好提示。
    • 服务降级->进而熔断->恢复调用链路
  3. 服务限流
    • 秒杀高并发等操作,严禁一窝蜂过来拥挤,一秒N个有序进行

7.3 hystrix案例

  1. 构建

    • 新建cloud-provider-hystrix-payment8001

    • POM

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      <?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>cloud2020</artifactId>
      <groupId>com.atguigu.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-provider-hystrix-payment8001</artifactId>

      <dependencies>
      <!--新增hystrix-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
      <groupId>com.atguigu.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>

      </project>
    • YML

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      server:
      port: 8001
      spring:
      application:
      name: cloud-provider-hystrix-payment

      eureka:
      client:
      service-url:
      defaultZone: http://localhost:7001/eureka
      register-with-eureka: true
      fetch-registry: true
    • 主启动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      package com.atguigu.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;

      /**
      * @program: cloud2020
      * @description: 主启动类
      * @author: Mr.Wang
      * @create: 2020-08-20-20-51
      */
      @SpringBootApplication
      @EnableDiscoveryClient
      @EnableEurekaClient
      public class PaymentHystrixMain8001 {
      public static void main(String[] args) {
      SpringApplication.run(PaymentHystrixMain8001.class,args);
      }
      }
    • 业务类

      • service

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        package com.atguigu.springcloud.service;

        import ch.qos.logback.core.util.TimeUtil;
        import org.springframework.stereotype.Service;

        import java.util.concurrent.TimeUnit;

        /**
        * @program: cloud2020
        * @description: 业务类
        * @author: Mr.Wang
        * @create: 2020-08-20-21-17
        */
        @Service
        public class PaymentService {
        //正常访问肯定OK的方法
        public String paymentInfo_ok(Integer id){
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_ok,id"+id+"\t"+"哈哈😄";
        }

        public String paymentInfo_timeout(Integer id){
        try {
        TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_timeout,id"+id+"\t"+"耗时3秒钟";
        }
        }
      • controller

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        package com.atguigu.springcloud.controller;

        import com.atguigu.springcloud.service.PaymentService;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RestController;

        import javax.annotation.Resource;

        /**
        * @program: cloud2020
        * @description: kongzhiceng
        * @author: Mr.Wang
        * @create: 2020-08-20-21-22
        */
        @RestController
        @Slf4j
        public class PaymentController {
        @Resource
        private PaymentService paymentService;
        @Value("${server.port}")
        private String serverPort;

        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_ok(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_ok(id);
        log.info("****result"+result);
        return result;

        }
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_timeout(id);
        log.info("****result"+result);
        return result;

        }
        }
  • 正常测试

    • 启动eureka7001
    • 启动cloud-provider-hystrix-payment8001
    • 访问:http://localhost:8001/payment/hystrix/ok/31
    • 每次调用耗费5秒钟:http://localhost:8001/payment/hystrix/timeout/31
    • 上述module均OK:以上述为根基平台,从正确->错误->降级熔断->恢复
      1. 高并发测试
      • 上述在非高并发情形下,还能勉强满足 but…..
      • Jmeter压测测试
    • 开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
    • 再来一个访问:http://localhost:8001/payment/hystrix/ok/31、http://localhost:8001/payment/hystrix/timeout/31
    • 看演示结果:两个都在自己转圈圈
    • 为什么会被卡死:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
      • Jmeter压测结论
        上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
      • 看热闹不嫌弃事大,80新建加入
    1. 新建cloud-consumer-feign-hystrix-order80

    2. POM

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      <?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>cloud2020</artifactId>
      <groupId>com.atguigu.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

      <dependencies>
      <!--新增hystrix-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
      <groupId>com.atguigu.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>

      </project>
    3. YML

      1
      2
      3
      4
      5
      6
      7
      8
      server:
      port: 80

      eureka:
      client:
      register-with-eureka: false
      service-url:
      defaultZone: http://localhost:7001/eureka/
    4. 主启动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      package com.atguigu.springcloud;

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

      /**
      * @program: cloud2020
      * @description: 主启动类
      * @author: Mr.Wang
      * @create: 2020-08-20-22-25
      */
      @SpringBootApplication
      @EnableEurekaClient
      @EnableFeignClients
      public class OrderHystrixMain80 {
      public static void main(String[] args) {
      SpringApplication.run(OrderHystrixMain80.class
      ,args);
      }
      }
    5. 业务类

      • service

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        package com.atguigu.springcloud.service;

        import org.springframework.cloud.openfeign.FeignClient;
        import org.springframework.stereotype.Component;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;

        /**
        * @program: cloud2020
        * @description: 服务类
        * @author: Mr.Wang
        * @create: 2020-08-20-22-29
        */
        @Component
        @FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
        public interface PaymentHystrixService {

        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_ok(@PathVariable("id") Integer id);
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id") Integer id);
        }
      • controller

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
            package com.atguigu.springcloud.controller;

        import com.atguigu.springcloud.service.PaymentHystrixService;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RestController;

        import javax.annotation.Resource;

        /**
        * @program: cloud2020
        * @description: kongzhiceng
        * @author: Mr.Wang
        * @create: 2020-08-20-22-42
        */
        @RestController
        @Slf4j
        public class PaymentHystirxController {

        @Resource
        private PaymentHystrixService paymentHystrixService;
        @GetMapping("/consumer/payment/hystrix/ok/{id}")
        public String paymentInfo_ok(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_ok(id);
        }
        @GetMapping("/consumer/payment/hystrix/timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_timeout(id);
        }
        }
    6. 正常测试:http://localhost/consumer/payment/hystrix/ok/31

    7. 高并发测试

      • 2W个线程压8001
      • 消费端80微服务再去访问正常的OK微服务8001地址
      • http://localhost/consumer/payment/hystrix/timeout/31
      • 消费者80:要么转圈圈等待,要么消费端报超时错误
        1. 故障现象和导致原因
          8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕,0此时调用8001,客户端访问响应缓慢,转圈圈
        2. 上诉结论
          正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
        3. 如何解决,解决的要求
        • 超时导致服务器变慢(转圈:超时不再等待
        • 出错(宕机或程序运行出错):出错要有兜底
        • 解决
    • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
    • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
    • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

7.3.1 服务降级

7.3.1.1. 降级配置

@HystrixCommand

7.3.1.2. 8001先从自身找问题

设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback

7.3.1.3. 8001fallback
  1. 业务类启用
    @HystrixCommand报异常后如何处理
    一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法。
    ==代码示例:==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
       
    /**
    * fallbackmetod:系统异常或者超时后的兜底方案
    * commandProperties -> HystrixProperty -> execution.isolation.thread.timeoutInMilliseconds :系统超时的时间
    * @param id
    * @return
    */
    @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_timeout(Integer id){
    try {
    TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    return "线程池:"+Thread.currentThread().getName()+" paymentInfo_timeout,id"+id+"\t"+"耗时3秒钟";
    }
    public String paymentInfo_timeoutHandler(Integer id){
    return "线程池:"+Thread.currentThread().getName()+" paymentInfo_timeoutHandler,id"+id;
    }

  2. 主启动类激活:添加新注解@EnableCircuitBreaker

7.3.1.4. 80fallback
  1. 80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护

  2. 题外话,切记
    我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

  3. YML
    在80的客户端yml文件中添加如下代码配置

    1
    2
    3
    feign:
    hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。

  4. 主启动

  5. 业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @HystrixCommand(fallbackMethod = "paymentTimeoutFallcackMethod",commandProperties  ={
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_timeout(@PathVariable("id") Integer id){
    return paymentHystrixService.paymentInfo_timeout(id);
    }
    public String paymentTimeoutFallcackMethod(@PathVariable("id") Integer id){
    return "消费者客户端80的服务报错了";
    }

7.3.1.5. 目前问题
  1. 每个业务方法对应一个兜底的方法,代码膨胀
  2. 统一和自定义的分开
7.3.1.6. 解决问题
  1. 每个方法配置一个???膨胀

    • feign接口系列

    • @DefaultProperties(defaultFallback = “”)

      说明:
      1:1 每个方法配置一个服务降级的方法,技术上可以,实际上操作是不可实现的
      1:N 除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = "")统一跳转到统一处理结果页面
      通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。

    • controller配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      package com.atguigu.springcloud.controller;

      import com.atguigu.springcloud.service.PaymentHystrixService;
      import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
      import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
      import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RestController;

      import javax.annotation.Resource;

      /**
      * @program: cloud2020
      * @description: kongzhiceng
      * @author: Mr.Wang
      * @create: 2020-08-20-22-42
      */
      @RestController
      @Slf4j
      @DefaultProperties(defaultFallback = "payment_Global_FallbaxkMethod")
      public class PaymentHystirxController {

      @Resource
      private PaymentHystrixService paymentHystrixService;

      @GetMapping("/consumer/payment/hystrix/ok/{id}")
      public String paymentInfo_ok(@PathVariable("id") Integer id){
      return paymentHystrixService.paymentInfo_ok(id);
      }

      /*@HystrixCommand(fallbackMethod = "paymentTimeoutFallcackMethod",commandProperties ={
      @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
      })*/
      @HystrixCommand
      @GetMapping("/consumer/payment/hystrix/timeout/{id}")
      public String paymentInfo_timeout(@PathVariable("id") Integer id){
      return paymentHystrixService.paymentInfo_timeout(id);
      }
      public String paymentTimeoutFallcackMethod(@PathVariable("id") Integer id){
      return "消费者客户端80的服务报错了";
      }

      //下面这个是全局的fallback方法
      public String payment_Global_FallbaxkMethod(){
      return "Global异常处理信息,请稍后再是....";
      }
      }

  2. 和业务逻辑混在一起???混乱

    • 服务降级,客户端去调用服务端,碰上服务端宕机或关闭

    • 本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

    • 未来我们要面对的异常:运行、超时、宕机

    • 再看我们的业务类PaymentController

    • 修改cloud-consumer-feign-hystrix-order80

    • 根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

    • PaymentFallbackService类实现PaymentFeignClientService接口

    • YML

      1
      2
      3
      feign:
      hystrix:
      enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
    • PaymentFeignClientService接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      package com.atguigu.springcloud.service;

      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.stereotype.Component;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;

      /**
      * @program: cloud2020
      * @description: 服务类
      * @author: Mr.Wang
      * @create: 2020-08-20-22-29
      */
      @Component
      @FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackHystrixService.class)
      public interface PaymentHystrixService {

      @GetMapping("/payment/hystrix/ok/{id}")
      public String paymentInfo_ok(@PathVariable("id") Integer id);
      @GetMapping("/payment/hystrix/timeout/{id}")
      public String paymentInfo_timeout(@PathVariable("id") Integer id);
      }

    • 测试

      • 单个eureka先启动7001
      • PaymentHystrixMain8001启动
      • 正常访问测试:http://localhost/consumer/payment/hystrix/ok/31
      • 故意关闭微服务8001
      • 客户端自己调用提升:此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

7.3.2 服务熔断

  1. 断路器
    一句话就是家里的保险丝

  2. 熔断是什么
    https://martinfowler.com/bliki/CircuitBreaker.html
    类比保险丝,达到最大访问后直接拒绝访问,拉闸限电,然后调用服务降级。当检测==到该节点微服务调用正常后,恢复调用链路。==
    当失败的调用达到一定阈值,缺省是5s内20次调用失败,就会启动熔断机制。熔断机制的注解是,@HystrixCommand

  3. 实操

    • 修改cloud-provider-hystrix-payment8001

    • PaymentService(为什么配置这些参数)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      /**
      * 服务熔断
      */
      @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
      @HystrixProperty(name="circuitBreaker.enabled",value = "true"),//是否开启断路器
      @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数
      @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//时间范围
      @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value = "60"),//失败率达到多少次后跳闸
      })
      //// 加起来就是在10s内的10次请求中如果失败超过6次进入服务熔断
      public String paymentCircuitBreaker(@PathVariable("id") Integer id){
      if(id < 0){
      throw new RuntimeException("*****id 不能为负数");
      }
      String serialNumber = IdUtil.simpleUUID();
      return Thread.currentThread().getName()+" \t 调用成功,流水号"+serialNumber;
      }
      public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
      return "id不能为附属,请稍后在是";
      }
    
1
2
3
4
5
6
@GetMapping("/payment/circuit/{id}")
public String paumentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*****info"+result);
return result;
}
* 测试 * 自测cloud-provider-hystrix-payment8001 * 正确:http://localhost:8001/payment/circuit/31 * 错误:http://localhost:8001/payment/circuit/-31 * 一次正确一次错误trytry * 重点测试:多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行访问,需要慢慢的恢复链路 * 结果:一直输入id为负数,达到失败率后即使输入id为正数也进入错误页面。
  1. 原理(小结)

    • 断路器在什么情况下开始起作用

      设计三个参数:时间窗,请求总阈值,错误百分比阈值
    1. 快照时间窗:默认为最近的10s
    2. 请求总数阈值:必须满足请求总阈值才有资格熔断。默认为20。意味着在10s内,如果命令调用次数不足20次,即使所有请求都超时或其他原因失败断路器都不会打开
    3. 错误百分比阈值:在快照时间窗内请求总数超过阈值,且错误次数占总请求次数的比值大于阈值,断路器将会打开
    • 断路器开启或者关闭的条件
      • 当满足一定阀值的时候(默认10秒内超过20个请求次数)
      • 当失败率达到一定的时候(默认10秒内超过50%请求失败)
      • 到达以上阀值,断路器将会开启
      • 当开启的时候,所有请求都不会进行转发
      • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
    • 断路器打开之后
    • all配置:详细的看官网

7.3.3 服务限流

后面高级篇讲解alibaba的Sentinel说明

7.4 hystrix工作流程

https://github.com/Netflix/Hystrix/wiki/How-it-Works

7.5 服务监控hystrixDashboard

  • 概述

  • 仪表盘9001

    • 新建cloud-consumer-hystrix-dashboard9001

    • POM

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      <?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>cloud2020</artifactId>
      <groupId>com.atguigu.springcloud</groupId>
      <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>

      <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>

      <dependencies>
      <!--新增hystrix dashboard-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>

      </project>
    • YML
      server.port:9001

    • HystrixDashboardMain9001+新注解@EnableHystrixDashboard

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      package com.atguigu.springcloud;

      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

      /**
      * @program: cloud2020
      * @description: qidonglei
      * @author: Mr.Wang
      * @create: 2020-08-21-15-37
      */
      @SpringBootApplication
      @EnableHystrixDashboard
      public class HystrixDashboardMain9001 {
      public static void main(String[] args) {
      SpringApplication.run(HystrixDashboardMain9001.class,args);
      }
      }
    • 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配

    • 启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001
      http://localhost:9001/hystrix

  • 断路器演示

    • 修改cloud-provider-hystrix-payment8001
      注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
      Unable to connect to Command Metric Stream
      404

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Bean
      public ServletRegistrationBean getServlet(){
      HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
      ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
      registrationBean.setLoadOnStartup(1);
      registrationBean.addUrlMappings("/hystrix.stream");
      registrationBean.setName("HystrixMetricsStreamServlet");
      return registrationBean;
      }
    • 监控测试

      1. 启动1个eureka或者3个eureka集群均可
      2. 观察监控窗口

8. zuul路由网关(没讲)

9. Gateway新一代网关

9.1 概述简介

  1. 官网
  2. 是什么
    Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
  3. 能干嘛
    • 反向代理
    • 鉴权
    • 流量控制
    • 熔断
    • 日志监控
  4. 微服务架构中网关在哪里

  5. 有了zuul怎么又出来gateway
    spring cloud gateway具有如下的特性
    • 基于Spring Framework5 ,Project Reactor和Spring boot 2.0进行构建的
    • 动态路由:能够匹配任何请求属性
    • 可以对路由指定Preidcate(断言)和Filter(过滤器)
    • 集成Hystrix的断路器功能
    • 集成Spring Cloud 服务发现功能
    • 易于编写的Predicate(断言)和Filter(过滤器)
    • 请求限流功能
    • 支持路径重写

9.2 三大核心概念

  1. Route(路由)
    路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  2. Predicate(断言)
    参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  3. Filter(过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
  4. 总体
    web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后进行一些精细化控制。predicate就是我们的匹配条件;而filter,就可以理解为一个无所不能的拦截器,有了这两个元素,在加上目标uri就可以实现一个具体的路由了

    9.3 Gateway工作流程

    客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序通过特定于请求的过滤器链来运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有“前置”过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器逻辑。

9.4 入门配置

  1. 新建module:cloud-gateway-gateway9527

  2. POM文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <?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>cloud2020</artifactId>
    <groupId>com.atguigu.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <dependencies>
    <!--新增gateway-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    <groupId>com.atguigu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    </project>

  3. YML配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server:
    port: 9527

    spring:
    application:
    name: cloud-gateway
    eureka:
    instance:
    hostname: cloud-gateway-service
    client:
    service-url:
    register-with-eureka: true
    fetch-registry: true
    defaultZone: http://localhost:7001/eureka
  4. 业务类:无

  5. 主启动类:同前几个步骤中的主启动类

  6. 9527网关如何做路由映射

    • cloud-provider-payment8001看看controller的访问地址:get/lib
    • 我们目前不想暴露8001端口,希望在8001外面套一层9527
  7. YML新增网关配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    server:
    port: 9527

    spring:
    application:
    name: cloud-gateway
    cloud:
    gateway:
    routes:
    - id: payment_routh # 路由的ID没有固定规则,但是要求统一,建议配合服务名
    uri: http://localhost:8001 #匹配后提供服务的路由地址
    predicates:
    - Path=/payment/get/** #断言,路由匹配进行路由
    - id: payment_routh2
    uri: http://localhost:8001
    predicates:
    - Path=/payment/lb/**

    eureka:
    instance:
    hostname: cloud-gateway-service
    client:
    service-url:
    register-with-eureka: true
    fetch-registry: true
    defaultZone: http://localhost:7001/eureka

  8. 测试

  9. YML配置说明-gateway网关路由两种配置方式

    • 在yml文件中的配置
    • 代码中注入RouteLocator的Bean
      • 官网案例
      • 百度国内新闻网址,需要外网
      • 自己写一个
        • 百度新闻
        • 业务需求:通过9527网关访问到外网的百度新闻地址
        • 编码
          1)cloud-gateway-gateway9527
          2) 业务实现

9.5 通过微服务名实现动态路由

9.6 Predicate的使用

9.7 Filter的使用

10. SpringCloud config分布式配置中心

10.1 概述

10.2 Config服务端配置与测试

10.3 Config客户端配置与测试

10.4 Config客户端之动态刷新

11. SpringCloud Bus 消息总线

11.1 概述

11.2 RabbitMQ环境配置

11.3 SpringCloud Bus动态刷新全局广播

11.4 SpringCloud Bus动态刷新定点通知

12. SpringCloud Stream消息驱动

12.1 消息驱动概述

12.2 案例说明

12.3 消息驱动之生产者

12.4 消息驱动之消费者

12.5 分组消费与持久化

13. SpringCloud Sleuth分布式请求链路追踪

13.1 概述

13.2 搭建链路监控步骤

14. SpringCloud Alibaba入门简介

14.1 why会出现SpringCloud alibaba

14.2 SpringCloud alibaba带来了什么?

14.3 SpringCloud alibaba学习资料获取

15. SpringCloud Alibaba Nacos服务注册和配置中心

15.1 Nacos简介

15.2 安装并运行Nacos

15.3 Nacos作为服务注册中心演示

15.4 Nacos作为服务配置中心演示

15.5 Nacos集群和持久化配置(重要)

16. SpringCloud Alibaba Sentinel实现熔断与限流

16.1 Sentinel

16.2 安装Sentinel控制台

16.3 初始化演示工程

16.4 流控规则

16.5 降级规则

16.6 热点key限流

16.7 系统规则

16.8 @SentinelResource

16.9 服务熔断功能

16.10 规则持久化

17. SpringCloud Alibaba Seata处理分布式事务

17.1 分布式事务问题

17.2 Seata简介

17.3 Seata-Server安装

17.4 订单/库存/账户业务数据库准备

17.5 订单/库存/账户业务微服务准备

17.6 Test

17.7 Seata之原理简介