SpringCloud入门

微服务的小白刚刚入门,文章中描述不当之处请谅解,欢迎各位大佬批评指正

1.简介2.SpringBoot与SpringCloud的版本选型3. Cloud组件停更说明
4.父工程project的搭建5. 父工程的pom文件6.复习DependencyManagement和Dependencies
7. 支付模块的构建(上)8.支付模块构建(中)9. 支付模块构建(下)
10.热部署Devtools11.消费者模块(上)12.工程重构
13.Erueka基础知识14.EurekaServer服务端安装15.支付微服务8001入驻进
16.订单微服务80入驻进EurekaServer 17.Eureka集群原理说明18.Eureka集群环境的搭建
19.订单支付两微服务注册进Eureka集群20.支付微服务配置21.actuator微服务信息完善
22.服务发现Discovery23.Eureka自我保护理论知识24.如何禁用保护机制
25.Eureka停更说明26.Consul简介27.安装并运行Consul
28.服务提供者注册进Consul29. 服务消费者注册进Consul30.三个注册中心异同点
31.Ribbon入门介绍32.Ribbon的负载均衡和Rest调用33.Ribbon默认自带的负载规则
34. Ribbon负载规则替换 35.Ribbon默认负载轮询算法原理36.RoundRobinRule源码分析
37.Ribbon之手写轮询算法38. OpenFeign是什么39. OpenFeign服务调用
40. 超时设置41.OpenFeign日志增强42.Hystrix是什么
43.Hystrix停更进维44.Hystrix的服务降级熔断限流概念初讲45Hystrix支付微服务构建
46. JMeter高并发压测后卡顿47. 订单微服务调用支付服务出现卡顿48.降级容错解决的维度要求
49. Hystrix之服务降级支付侧fallback50.Hystrix之服务降级订单侧fallback51.Hystrix之全局服务降级DefaultProperties
52.Hystrix之通配服务降级FeignFallback53.Hystrix之服务熔断理论 54. Hystrix之服务熔断案例(上)
55.Hystrix之服务熔断案例(下)56.Hystrix之服务熔断总结57.Hystrix工作流程最后总结
58.Hystrix图形化Dashboard搭建59.Hystrix图形化Dashboard监控实战60.GateWay是什么
61.GateWay非阻塞异步模型62.Gateway工作流程63.GateWay9527搭建
64.GateWay配置动态路由65.GateWay常用的Predicat66.GateWay的Filter
67.Stream为什么被引入68.Stream是什么及Binder介绍69.Stream的设计思想
70.Stream编码常用注解简介71Stream消息驱动之生产者72.Stream消息驱动之消费者
73.Stream之消息重复消费 74Stream之group解决消息重复消费75.Stream之消息持久化
76.Sleuth是什么 77.Sleuth之zipkin搭建安装78.服务提供者

1.简介

1.1什么是微服务

  1. 微服务是一种架构风格
  2. 一个应用拆分为一组小型服务
  3. 每个服务运行在自己的进程内,也就是可独立部署和升级
  4. 服务之间使用轻量级HTTP交互
  5. 服务围绕业务功能拆分
  6. 可以由全自动部署机制独立部署
  7. 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术

1.2 分布式微服务落地纬度

  • 服务调用
  • 服务降级
  • 服务注册与发先
  • 服务熔断
  • 负载均衡
  • 服务消息队列
  • 服务网关
  • 配置中心管理
  • 自动化构建部署
  • 服务监控
  • 全链路追踪
  • 服务定时任务
  • 调度操作

1.3 springcloud介绍

SpringCloud=分布式微服务构架的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶,是微服务开发的主流技术栈

猜猜SpringCloud这个大集合里有多少种技术?
在这里插入图片描述

“微”力十足,互联网大厂微服务架构案例

京东的
在这里插入图片描述
阿里的
在这里插入图片描述

SpringCloud技术栈

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

2.SpringBoot与SpringCloud的版本选型

SpringCloud VersionSpringBoot Version
2021.0.x aka Jubilee2.6.x, 2.7.x (Starting with 2021.0.3)
2020.0.x aka Ilford2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton2.2.x, 2.3.x (Starting with SR5)
Greenwich2.1.x
Finchley2.0.x
Edgware1.5.x
Dalston1.5.x
  • 本次SpringCloud所需要的环境
选项版本
cloudHoxton.SR1
boot2.2.2.REALEASE
cloud alibaba2.1.0.REALEASE
javajava8
Maven3.5以上
MySQL5.7以上

3. Cloud组件停更说明

  • 停更引发的“升级惨案”

    • 停更不停用
    • 被动修复bugs
    • 不再接受合并请求
    • 不再发布新版本
  • Cloud升级

    • 服务注册中心
      • × Eureka
      • √ Zookeeper
      • √ Consul
      • √ Nacos
    • 服务调用
      • √ Ribbon(可以用但已停止更新)
      • √ LoadBalancer
    • 服务调用2
      • × Feign
      • √ OpenFeign
    • 服务降级
      • × Hystrix
      • √ resilience4j
      • √ sentienl
    • 服务网关
      • × Zuul
      • ! Zuul2(还没出来)
      • √ gateway
    • 服务配置
      • × Config
      • √ Nacos
    • 服务总线
      • × Bus
      • √ Nacos

SpringCloud官方文档
中文文档
SpringBoot官网

4.父工程project的搭建

在搭建项目的过程中一般的先后顺序是:约定 > 配置 > 编码
创建微服务cloud整体聚合父工程Project,有8个关键步骤:

  1. New Project - maven工程 - create from archetype: maven-archetype-site
  2. 聚合总父工程名字
  3. Maven选版本
  4. 工程名字
  5. 字符编码 - Settings - File encoding
  6. 注解生效激活 - Settings - Annotation Processors
  7. Java编译版本选8
  8. File Type过滤 - Settings - File Type

5. 父工程的pom文件

<?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>
    <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>5.1.47</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>

6.复习DependencyManagement和Dependencies

Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父POM中看到dependencyManagement元素。
使用pom.xml中的dependencyManagement元素能让所有在子项目中引用个依赖而不用显式的列出版本量。
Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个
dependencyManagement元素中指定的版本号。

<dependencyManagement>
    <dependencies>
        <dependency>
        <groupId>mysq1</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.2</version>
        </dependency>
    <dependencies>
</dependencyManagement>

然后在子项目里就可以添加mysql-connector时可以不指定版本号,例如:

<dependencies>
    <dependency>
    <groupId>mysq1</groupId>
    <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version就可。

dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。
如果不在子项目中声明依赖,是不会从父项目中继承下来的;只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。
如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

7. 支付模块的构建(上)

建立微服务的流程:

  1. 建Module
  2. 改POM
  3. 写YML
  4. 主启动
  5. 业务类

7.1 建Module

建名为cloud-provider-payment8001的Maven工程

7.2 改pom

<?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-provfider-payment8001</artifactId>
    <dependencies>
        <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.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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>

7.3 写yml

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: org.gjt.mm.mysql.Driver              # mysql驱动包
    url: jdbc:mysql://localhost:3306/my?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 1234
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities

7.4 主启动

package com.atguigu.springcloud;

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

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

8.支付模块构建(中)

8.1构建数据库表

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=utf8mb4

8.2 实体类

实体类Payment

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
    private Long id;
    private String serial;
}

json同一结果集封装

@Data
@ToString
@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);
    }
}

8.3 dao层

@Mapper
public interface PaymentDao {

    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);

}

8.4 xml

<?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 column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
        select * from payment where id=#{id};
    </select>

</mapper>

8.5 Service

public interface PaymentService {

    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);

}

8.6 实现

public interface PaymentService {

    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);

}

8.7 Controller

@RestController
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

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

    @GetMapping("/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        if(payment != null){
            return new CommonResult(200,"查询成功",payment);
        }else {
            return new CommonResult(500,"没有查到对应的记录id:"+id,null);
        }
    }
}

9. 支付模块构建(下)

9.1测试

浏览器 - http://localhost:8001/payment/get/1
Postman - http://localhost:8001/payment/create?serial=lun2

9.2.小总结

创建微服务模块套路:

  1. 建Module
  2. 改POM
  3. 写YML
  4. 主启动
  5. 业务类

10.热部署Devtools

没有修改代码后,我们必须重启项目代码才可以生效,为了避免频繁的重启,我们可以使用热部署

  1. 添加Devtools的依赖到项目中
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
  1. 在父工程中安装插件
    <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>
  1. 开启字段编译的选项
    File -> Settings(New Project Settings->Settings for New Projects) ->Complier
    在这里插入图片描述
    在这里插入图片描述

下面项勾选

Automatically show first error in editor
Display notification on build completion
Build project automatically
Compile independent modules in paralle
在这里插入图片描述

  1. Update the value of
    键入Ctrl + Shift + Alt + / ,打开Registry,勾选:
    • compiler.automake.allow.when.app.running
    • actionSystem.assertFocusAccessFromEdt
  2. 重启
    说明:开发阶段可以使用热部署,正式上线之后要关闭

11.消费者模块(上)

11.1.建Module

创建名为cloud-consumer-order81的maven工程。

11.2.改POM

<?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-order-80</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <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>

11.3 写yml

server:
  port: 81

11.4 主启动

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

11.5 业务类

  1. 实体类
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
    private Long id;
    private String serial;
}
@Data
@ToString
@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);
    }
}

  1. 配置类
package com.atguigu.springcloud.config;

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

@Configuration
public class ApplicationContextConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

  1. controller
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;

@RestController
@Slf4j
public class OrderController {

    private 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> get(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
}

11.6 测试

通过访问8001端口的服务和81端口的服务都可以请求到相同的结果

restTemplate说明
RestTemplate

RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
官网地址
使用:

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

## 12.消费者订单模块下
浏览器 - http://localhost:81/consumer/payment/create?serial=lun3
虽然,返回成功,但是观测数据库中,并没有创建serial为lun3的行。

解决之道:在loud-provider-payment8001工程的PaymentController中添加@RequestBody注解。

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

12.工程重构

12.1重构原因

观察cloud-consumer-order81与cloud-provider-payment8001两工程有重复代码(entities包下的实体),需要对项目进行重构。

12.2 重构的步骤

  1. 新建 - cloud-api-commons module
  2. 改pom
    <dependencies>
        <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>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
  1. 转移实体类
    将cloud-consumer-order81与cloud-provider-payment8001两工程的公有entities包移至cloud-api-commons工程下。
  2. maven clean、install cloud-api-commons工程,以供给cloud-consumer-order81与cloud-provider-payment8001两工程调用
  3. 订单模块81与支付模块8001进行改造
    将cloud-consumer-order80与cloud-provider-payment8001两工程的公有entities包移除
    引入cloud-api-commons依赖
		<dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

13.Erueka基础知识

13.1什么是服务治理

Spring Cloud封装了Netflix 公司开发的Eureka模块来实现服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。

13.2 什么是服务注册与发现

Eureka采用了CS的设计架构,Eureka Sever作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)
在这里插入图片描述

13.3 Eureka包含两个组件:Eureka Server和Eureka Client

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

14.EurekaServer服务端安装

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

14.1.创建名为cloud-eureka-server7001的Maven工程

14.2 改pom

<?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-server-7001</artifactId>
    <dependencies>
        <!--eureka-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--boot web actuator-->
        <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>
        </dependency>
        <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>

14.3 写yml

server:
  port: 7001

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

14.4 主启动

注意要加注解:@EnableEurekaServer

package com.atguigu.springcloud;

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

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

14.5 测试

5.测试运行EurekaMain7001,浏览器输入http://localhost:7001/回车,会查看到Spring Eureka服务主页
在这里插入图片描述

15.支付微服务8001入驻进eurekaServer

EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider,类似学校对外提供授课服务。
修改cloud-provider-payment8001微服务

  1. 改xml
    添加依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. 改yml
eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/
  1. 主启动
    添加@EnableEurekaClient注解
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.context.annotation.Configuration;

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

  1. 测试
    在浏览器中输入http://localhost:7001/发现服务注册进来了
    在这里插入图片描述

16.订单微服务80入驻进EurekaServer

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

  1. cloud-consumer-order80微服务
  2. pom
    添加依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
  1. yml
    注册进Eureka
server:
  port: 81

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

spring:
  application:
    name: cloud-order-service
  1. 主启动
    添加@EnableEurekaClient注解
package com.atguigu.springcloud;

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

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

  1. 测试

启动cloud-provider-payment8001、cloud-eureka-server7001和cloud-consumer-order80这三工程。
浏览器输入 http://localhost:7001 , 在主页的Instances currently registered with Eureka将会看到cloud-provider-payment8001、cloud-consumer-order80两个工程名。
在这里插入图片描述

17.Eureka集群原理说明

在这里插入图片描述

服务注册:将服务信息注册进注册中心
服务发现:从注册中心上获取服务信息
实质:存key服务命取value闭用地址
过程:

  1. 先启动eureka注主册中心
  2. 启动服务提供者payment支付服务
  3. 支付服务启动后会把自身信息(比服务地址L以别名方式注朋进eureka
  4. 消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
  5. 消去者导调用地址后,底屋实际是利用HttpClient技术实现远程调用
  6. 消费者实癸导服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新—次服务调用地址

问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one,万一它出故障了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错。

互相注册,相互守望。

18.Eureka集群环境的搭建

  1. 创建cloud-eureka-server7002工程,过程参考14.EurekaServer服务端安装
    在这里插入图片描述
  2. 修改cloud-eureka-server7001配置文件
server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://127.0.0.1:7002

  1. 修改cloud-eureka-server7003配置文件
server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://127.0.0.1:7001

19.订单支付两微服务注册进Eureka集群

  • 将支付服务8001微服务,订单服务80微服务发布到上面2台Eureka集群配置中
    将它们的配置文件的eureka.client.service-url.defaultZone进行修改
eureka:
  client:
    #表示是否将自己注册进Eurekaserver默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群版
 #     defaultZone: http://localhost:7001/eureka/
  • 测试01
    1. 先要启动EurekaServer,7001/7002服务
    2. 再要启动服务提供者provider,8001
    3. 再要启动消费者,80
    4. 浏览器输入 - http://localhost/consumer/payment/get/1

20.支付微服务配置

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

参考cloud-provicer-payment8001

  1. 新建cloud-provider-payment8002
  2. 改POM
  3. 写YML - 端口8002
  4. 主启动
  5. 业务类
  6. 修改8001/8002的Controller,添加serverPort
package com.atguigu.springcloud.controller;


import com.atguigu.springcloud.service.PaymentService;
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.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

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

    @GetMapping("/payment/get/{id}")
    public CommonResult create(@PathVariable("id") Long id){
        Payment payment = paymentService.getPaymentById(id);
        if(payment != null){
            return new CommonResult(200,"查询成功,serverPort:"+serverPort,payment);
        }else {
            return new CommonResult(500,"没有查到对应的记录id:"+id,null);
        }
    }
}

负载均衡
cloud-consumer-order80订单服务访问地址不能写死

@RestController
@Slf4j
public class OrderController {

//    private static final String PAYMENT_URL="http://localhost:8001";
    private static final String PAYMENT_URL="http://CLOUD-PAYMENT-SERVICE";

    @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> get(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
}

在配置中使用@LoadBalanced注解赋予RestTemplate负载均衡的能力

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

}

ApplicationContextBean - 提前说一下Ribbon的负载均衡功能
测试

  1. 先要启动EurekaServer,7001/7002服务
  2. 再要启动服务提供者provider,8001/8002服务
  3. 浏览器输入 - http://localhost/consumer/payment/get/31
  4. 结果:负载均衡效果达到,8001/8002端口交替出现
  5. Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载功能。
    相互注册,相互守望
    在这里插入图片描述

21.actuator微服务信息完善

主机名称:服务名称修改(也就是将IP地址,换成可读性高的名字)
修改cloud-provider-payment8001,cloud-provider-payment8002
修改部分 - YML - eureka.instance.instance-id

eureka:
  ...
  instance:
    instance-id: payment8001 #添加此处
eureka:
  ...
  instance:
    instance-id: payment8002 #添加此处

修改之后
eureka主页将显示payment8001,payment8002代替原来显示的IP地址。

访问信息有IP信息提示,(就是将鼠标指针移至payment8001,payment8002名下,会有IP地址提示)
修改部分 - YML - eureka.instance.prefer-ip-address

eureka:
  ...
  instance:
    instance-id: payment8001 
    prefer-ip-address: true #添加此处
eureka:
  ...
  instance:
    instance-id: payment8002
    prefer-ip-address: true #添加此处

22.服务发现Discovery

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

  1. 修改cloud-provider-payment8001的Controller
@RestController
@Slf4j
public class PaymentController{
	...
    
    @Resource
    private DiscoveryClient discoveryClient;

    ...

    @GetMapping(value = "/payment/discovery")
    public Object discovery()
    {
        List<String> services = discoveryClient.getServices();
        for (String element : services) {
            log.info("*****element: "+element);
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
        }

        return this.discoveryClient;
    }
}
  1. 8001主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentMain001 {

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

自测

  1. 先要启动EurekaSeryer
  2. 再启动8001主启动类,需要稍等一会儿
  3. 浏览器输入http://localhost:8001/payment/discovery
  4. 浏览器输出:
{"services":["cloud-payment-service"],"order":0}

后台输出:

*****element: cloud-payment-service
CLOUD-PAYMENT-SERVICE	192.168.199.218	8001	http://192.168.199.218:8001

23.Eureka自我保护理论知识

23.1概述

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE

23.1导致原因

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

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

为了EurekaClient可以正常运行,防止与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除

23.3什么是自我保护模式?

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

自我保护机制∶默认情况下EurekaClient定时向EurekaServer端发送心跳包

如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。

它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。

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

24.如何禁用保护机制

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

出厂默认,自我保护机制是开启的

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

eureka:
  ...
  server:
    #关闭自我保护机制,保证不可用服务被及时踢除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

关闭效果:
spring-eureka主页会显示出一句:

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

  • 生产者客户端eureakeClient端8001
    默认:
eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90

关闭时

eureka:
  ...
  instance:
    instance-id: payment8001
    prefer-ip-address: true
    #心跳检测与续约时间
    #开发时没置小些,保证服务关闭后注册中心能即使剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2
  • 测试
    7001和8001都配置完成
    先启动7001再启动8001
    结果:先关闭8001,马上被删除了

25.Eureka停更说明

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

Eureka 2.0 (Discontinued)

The existing open source work on eureka 2.0 is discontinued. The code base and artifacts that were released as part of the existing repository of work on the 2.x branch is considered use at your own risk.

Eureka 1.x is a core part of Netflix’s service discovery system and is still an active project.

26.Consul简介

Consul官网
Consul下载地址
Consul是一个服务网格解决方案,它提供了一个功能齐全的控制平面,具有服务发现、配置和分段功能。这些特性中的每一个都可以根据需要单独使用,也可以一起用于构建全服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul船与一个简单的内置代理,使一切工作的开箱即用,但也支持第三方代理集成,如Envoy。

Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp 公司用Go语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。

它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨平台,支持Linux、Mac、Windows。

能干嘛?

  1. 服务发现 - 提供HTTP和DNS两种发现方式。
  2. 健康监测 - 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化
  3. KV存储 - Key、Value的存储方式
  4. 多数据中心 - Consul支持多数据中心
  5. 可视化Web界面

怎么玩

27.安装并运行Consul

官网安装说明

  1. windows版解压缩后,得consul.exe,打开cmd
  2. 开发模式启动consul agent -dev:
  3. 浏览器输入 - http://localhost:8500/ - 打开Consul控制页。
    在这里插入图片描述

28.服务提供者注册进Consul

  1. 新建Module支付服务provider8006
  2. pom
<?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>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--SpringCloud consul-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <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>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  1. yml
server:
  port: 8006


spring:
  application:
    name: consul-provider-payment

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
  1. 主启动
package com.atguigu.springcloud;

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

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

  1. 业务类
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;

@RestController
@Slf4j
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/consul")
    public String paymentConsul(){
        return "springcloud with consul:"+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

  1. 验证测试
  • http://localhost:8006/payment/consul
    在这里插入图片描述

  • http://localhost:8500 - 会显示provider8006
    在这里插入图片描述

29. 服务消费者注册进Consul

  1. 新建Module消费服务order80 - cloud-consumerconsul-order80
  2. pom
<?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>
        <!--SpringCloud consul-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <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>
  1. yml
server:
  port: 80
spring:
  application:
    name: cloud-consumer-order
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}
  1. 主启动
package com.atguigu.springcloud;

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

@SpringBootApplication
@EnableDiscoveryClient //向consul的注册中心注册服务
public class OrderConsulMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderConsulMain80.class,args);
    }
}

  1. 配置类
package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

  1. controlller
package com.atguigu.springcloud.controller;

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;

@RestController
public class OrderConsulController {

    @Autowired
    private static final String INVOKE_ERL="http://consul-provider-payment";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/consul/get")
    public String payment(){
        String result = restTemplate.getForObject(INVOKE_ERL + "/payment/consul", String.class);
        return result;
    }
}

  1. 验证测试

运行consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80

http://localhost:8500/ 主页会显示出consul,cloud-providerconsul-payment8006,cloud-consumerconsul-order80三服务。
在这里插入图片描述

  1. 访问测试地址 - http://localhost/consumer/payment/consul/get

30.三个注册中心异同点

组件名语言 CAP服务健康检查对外暴露接口Spring Cloud集成
EurekaJavaAP可配支持HTTP
ConsulGoCP支持HTTP/DNS
ZookeeperJavaCP支持客户端已集成

CAP:

  • C:Consistency (强一致性)
  • A:Availability (可用性)
  • P:Partition tolerance (分区容错性)

最多只能同时较好的满足两个。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。

因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

  1. CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
  2. CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
  3. AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。

AP架构(Eureka)
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
在这里插入图片描述

CP架构(ZooKeeper/Consul)
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
在这里插入图片描述
CP 与 AP 对立同一的矛盾关系。

31.Ribbon入门介绍

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
Ribbon 官网
Ribbon目前也进入维护模式。
Ribbon未来可能被Spring Cloud LoadBalacer替代。
LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。
常见的负载均衡有软件Nginx,LVS,硬件F5等。
Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别

  1. Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。
  2. Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;

进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
一句话
负载均衡 + RestTemplate调用

32.Ribbon的负载均衡和Rest调用

32.1架构说明

总结:Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例。
在这里插入图片描述
Ribbon在工作时分成两步:

  1. 第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。
  2. 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

POM

先前工程项目没有引入spring-cloud-starter-ribbon也可以使用ribbon。

<dependency>
    <groupld>org.springframework.cloud</groupld>
    <artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>

这是因为spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用,使用时就不用在引用

32.3 RestTemplate的使用

RestTemplate Java Doc
getForObject() / getForEntity() - GET请求方法
getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json。
getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
示例展示

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getForEntity(@PathVariable("id") String id){
        ResponseEntity<CommonResult> result = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        if(result.getStatusCode().is2xxSuccessful()){
            CommonResult body = result.getBody();
            return body;
        }else {
            return new CommonResult<>(444,"操作失败");
        }
    }
    @PostMapping("/consumer/payment/createForEntity")
    public CommonResult<Payment> createForEntity(@RequestBody Payment payment){
        ResponseEntity<CommonResult> result = restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
        if(result.getStatusCode().is2xxSuccessful()){
            return result.getBody();
        }else {
            return new CommonResult<>(444,"保存失败");
        }
    }

33.Ribbon默认自带的负载规则

lRule:根据特定算法中从服务列表中选取一个要访问的服务
在这里插入图片描述

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

34. Ribbon负载规则替换

  1. 修改cloud-consumer-order80
  2. 注意配置细节
    官方文档明确给出了警告:
    这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
    否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
    (也就是说不要将Ribbon配置类与主启动类同包)
  3. 新建package - com.atguigu.myrule
  4. com.atguigu.myrule下新建MySelfRule规则类
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;

@Configuration
public class MySelfRule {

    @Bean
    public IRule myRule(){
        return new RandomRule();
    }
}

  1. 主启动类添加@RibbonClient
package com.atguigu.springcloud;

import com.atguigu.myrule.MySelfRule;
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;

@SpringBootApplication
@EnableEurekaClient
//添加在这里
@RibbonClient(name = "http://CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain81 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain81.class,args);
    }
}

  1. 测试
    开启cloud-eureka-server7001,cloud-consumer-order80,cloud-provider-payment8001,cloud-provider-payment8002
    浏览器-输入http://localhost:81/consumer/payment/get/1
    返回结果中的serverPort在8001与8002两种间反复横跳。

35.Ribbon默认负载轮询算法原理

默认负载轮训算法: rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
List<Servicelnstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:
当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
如此类推…

36.RoundRobinRule源码分析

public interface IRule{
    /*
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */

    //重点关注这方法
    public Server choose(Object key);
    
    public void setLoadBalancer(ILoadBalancer lb);
    
    public ILoadBalancer getLoadBalancer();    
}

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The most well known and basic load balancing strategy, i.e. Round Robin Rule.
 *
 * @author stonse
 * @author Nikos Michalakis <nikos@netflix.com>
 *
 */
public class RoundRobinRule extends AbstractLoadBalancerRule {

    private AtomicInteger nextServerCyclicCounter;
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;

    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
        nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        setLoadBalancer(lb);
    }

    //重点关注这方法。
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (server == null && count++ < 10) {
            List<Server> reachableServers = lb.getReachableServers();
            List<Server> allServers = lb.getAllServers();
            int upCount = reachableServers.size();
            int serverCount = allServers.size();

            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;//求余法
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

37.Ribbon之手写轮询算法

自己试着写一个类似RoundRobinRule的本地负载均衡器。

  1. 7001/7002集群启动
  2. 8001/8002微服务改造- controller
        @GetMapping("/payment/lb")
    public String getPaymentLb(){
        return serverPort;
    }
  1. 80订单微服务改造
  • ApplicationContextConfig去掉注解@LoadBalanced,OrderMain80去掉注解@RibbonClient
@SpringBootApplication
@EnableEurekaClient
//添加在这里
//@RibbonClient(name = "http://CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain81 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain81.class,args);
    }
}

  • 创建LoadBalancer接口
package com.atguigu.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalance {
    ServiceInstance instance(List<ServiceInstance> serviceInstances);
}

  • MyLB:实现LoadBalancer接口
package com.atguigu.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Component
public class MyLB implements LoadBalance {


   private AtomicInteger atomicInteger = new AtomicInteger(0);

   public final int getAndIncrement(){
       int current;
       int next;
       do {
           current = this.atomicInteger.get();
           next =current>2147483647 ? 0 :current+1;
       }while (!this.atomicInteger.compareAndSet(current,next));
       System.out.println("********第几次访问next*****"+next);
       return current;
   }

   @Override
   public ServiceInstance instance(List<ServiceInstance> serviceInstances) {
       int index =  getAndIncrement()%serviceInstances.size();
       ServiceInstance instance = serviceInstances.get(index);
       return instance;
   }
}

  • OrderController
@RestController
@Slf4j
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @Resource
    private LoadBalance loadBalance;

    @Autowired
    private DiscoveryClient discoveryClient;
        @GetMapping(value = "/consumer/payment/lb")
        public String getPaymentLB(){
    //        discoveryClient.getIns
            List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
            if(instances == null || instances.size() == 0){
                return null;
            }
            ServiceInstance instance = loadBalance.instance(instances);
            URI uri = instance.getUri();
            return restTemplate.getForObject(uri+"/payment/lb",String.class);
        }
}

38. OpenFeign是什么

官网
Github地址
GitHub解释:
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

38.1 Feign能干什么

Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

38.2 Feign和OpenFeign两者区别

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

39. OpenFeign服务调用

口诀: 接口+注解:微服务调用接口 + @FeignClient

  1. 新建cloud-consumer-feign-order80
  2. pom
<?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-openfeign-80</artifactId>
    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--web-->
        <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>
  1. yml
server:
  port: 81

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

  1. 主启动
package com.atguigu.springcloud;

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

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

  1. 业务类

业务逻辑接口+@FeignClient配置调用provider服务
新建PaymentFeignService接口并新增注解@FeignClient

package com.atguigu.springcloud.sevice;

import com.atguigu.springcloud.entities.CommonResult;
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
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
    @GetMapping("/payment/get/{id}")
    public CommonResult get(@PathVariable("id") Long id);
}

controller

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.sevice.PaymentFeignService;
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;

@RestController
public class OrderFeignController {

    @Autowired
    private PaymentFeignService paymentFeignService;

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        CommonResult result = paymentFeignService.get(id);
        return result;
    }
}
  1. 测试
    先启动2个eureka集群7001/7002
    再启动2个微服务8001/8002
    启动OpenFeign启动
    http://localhost/consumer/payment/get/1
    Feign自带负载均衡配置项

40. 超时设置

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

  1. 服务提供方8001/8002故意写暂停程序
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeOut(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return serverPort;
    }
  1. 服务消费方80添加超时方法PaymentFeignService
    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeOut();
  1. 服务消费方80添加超时方法OrderFeignController
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.sevice.PaymentFeignService;
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;

@RestController
public class OrderFeignController {

    @Autowired
    private PaymentFeignService paymentFeignService;
		...

    @GetMapping("/consumer/payment/feign/timeout")
    public String paymentFeignTimeOut(){
        //openfeign 客户端默认等待一秒钟
        return paymentFeignService.paymentFeignTimeOut();
    }
}

  1. 测试:
    多次刷新http://localhost/consumer/payment/feign/timeout
    将会跳出错误Spring Boot默认错误页面,主要异常:feign.RetryableException:Read timed out executing GET http://CLOUD-PAYMENT-SERVCE/payment/feign/timeout。

OpenFeign默认等待1秒钟,超过后报错
YML文件里需要开启OpenFeign客户端超时控制

#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

41.OpenFeign日志增强

41.1 日志打印功能

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

41.2 日志级别

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

41.3配置日志bean

package com.atguigu.springcloud.config;

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

@Configuration
public class FeignConfig
{
    @Bean
    Logger.Level feignLoggerLevel()
    {
        return Logger.Level.FULL;
    }
}

41.4YML文件里需要开启日志的Feign客户端

logging:
  level:
    # feign日志以什么级别监控哪个接口
    com.lun.springcloud.service.PaymentFeignService: debug

42.Hystrix是什么

42.1概述

42.1.1分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

42.1.2 服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后避依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

42.2. Hystrix是什么

Hystrix是一个用于处理分布式系统的延迟容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

43.Hystrix停更进维

43.1 能干嘛

  1. 服务降级
  2. 服务熔断
  3. 接近实对的监控

43.2Hystrix官宣,停更进维

如何使用
官网

  • 被动修bugs
  • 不再接受合并请求
  • 不再发布新版本

44.Hystrix的服务降级熔断限流概念初讲

44.1服务降级

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会出发降级

  • 程序运行导常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

44.2服务熔断

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务的降级 -> 进而熔断 -> 恢复调用链路

44.3 服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

45.Hystrix支付微服务构建

将cloud-eureka-server7001改配置成单机版

  1. 新建cloud-provider-hygtrix-payment8001
  2. pom
<?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-payment-8001</artifactId>
    <dependencies>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--web-->
        <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><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>
  1. yml
server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  1. 主启动
package com.atguigu.springcloud;


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

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystiixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystiixMain8001.class,args);
    }
}
  1. 业务类
    service
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    /**
     * 正常访问 肯定
     * @param id
     * @return
     */
    public String paymentInfo_ok(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"Payment_ok,id"+id+"哈哈";
    }

    public String paymentInfo_timeOut(Integer id){
        int timeNum = 3;
        try {
            TimeUnit.SECONDS.sleep(timeNum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+"Payment_timeOut,id"+id+"哈哈,需要等待"+timeNum+"秒钟";

    }
}

controller

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;

@RestController
@Slf4j
public class PaymentController {

    @Autowired
    private PaymentService paymentService;

    @Value("${server.port}")
    private int serverPort;

    @GetMapping("/payment/hystirx/ok/{id}")
    public String paymentInfoOk(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_ok(id);
        log.debug("------result:{}-------",result);
        return result;
    }

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentTimeOut(@PathVariable("id") Integer id){
        String result = paymentService.paymentInfo_timeOut(id);
        log.debug("------result:{}-------",result);
        return result;
    }
}

  1. 正常测试

启动eureka7001
启动cloud-provider-hystrix-payment8001
访问
success的方法 - http://localhost:8001/payment/hystrix/ok/1
每次调用耗费5秒钟 - http://localhost:8001/payment/hystrix/timeout/1
上述module均OK

以上述为根基平台,从正确 -> 错误 -> 降级熔断 -> 恢复。

46. JMeter高并发压测后卡顿

上述在非高并发情形下,还能勉强满足
Jmeter压测测试
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

  1. 测试计划中右键添加-》线程-》线程组(线程组202102,线程数:200,线程数:100,其他参数默认)
  2. 刚刚新建线程组202102,右键它-》添加-》取样器-》Http请求-》基本 输入http://localhost:8001/payment/hystrix/ok/1
  3. 点击绿色三角形图标启动。
    看演示结果:拖慢,原因:tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
    Jmeter压测结论
    上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖慢。

47. 订单微服务调用支付服务出现卡顿

看热闹不嫌弃事大,80新建加入

  1. 新建 - cloud-consumer-feign-hystrix-order80
  2. pom
<?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-hystris-order-80</artifactId>
    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--web-->
        <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>
  1. yml
server:
  port: 81

eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  1. 主启动
package com.atguigu.springcloud;

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

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

  1. 业务类
    service
package com.atguigu.springcloud.service;

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

@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfoOk(@PathVariable("id") Integer id);

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

controller

package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentHystrixService;
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;

@RestController
public class OrderHystrixController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("consumer/payment/hystrix/timeout/{id}")
    public String paymentTimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentTimeOut(id);
        return result;
    }
}

  1. 正常测试
    http://localhost/consumer/payment/hystrix/ok/1
  2. 高并发测试
    2W个线程压8001
    消费端80微服务再去访问正常的Ok微服务8001地址
    http://localhost/consumer/payment/hystrix/ok/32
    消费者80被拖慢

原因:8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕。
正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生。

降级容错解决的维度要求

超时导致服务器变慢(转圈) - 超时不再等待
出错(宕机或程序运行出错) - 出错要有兜底

解决:

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级。
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级。
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级。

49. Hystrix之服务降级支付侧fallback

降级配置 -```@HystrixCommand``
8001先从自身找问题
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处埋,作服务降级fallback。
8001fallback
业务类启用 - @HystrixCommand报异常后如何处理
—旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

package com.atguigu.springcloud.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    /**
     * 正常访问 肯定
     * @param id
     * @return
     */
    public String paymentInfo_ok(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"Payment_ok,id"+id+"哈哈";
    }

    @HystrixCommand(fallbackMethod = "PaymentInfo_timeOutHandler",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_timeOut(Integer id){
//            int timeNum = 5;
//            try {
//                TimeUnit.SECONDS.sleep(timeNum);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            return "线程池:"+Thread.currentThread().getName()+"Payment_timeOut,id"+id+"哈哈,需要等待"+timeNum+"秒钟";

        int x=5/0;
        return "线程池:"+Thread.currentThread().getName()+"Payment_timeOut,id"+id+"哈哈,需要等待";

    }

    public String PaymentInfo_timeOutHandler(Integer id){
        return "线程池"+Thread.currentThread().getName()+"系统繁忙或运行报错请重新试id"+id+"\t"+"o(┭┮﹏┭┮)o";
    }
}

上面故意制造两种异常:

  1. int age = 10/0,计算异常
  2. 我们能接受3秒钟,它运行5秒钟,超时异常。

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_timeOutHandler

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

package com.atguigu.springcloud;


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

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker//添加到此处
public class PaymentHystiixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystiixMain8001.class,args);
    }
}

50.Hystrix之服务降级订单侧fallback

80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护
题外话,切记 - 我们自己配置过的热部署方式对java代码的改动明显
但对@HystrixCommand内属性的修改建议重启微服务

  1. yml
server:
  port: 80
eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

feign:
  hystrix:
    enabled: true
  1. 主启动
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

  1. 业务类
    @GetMapping("consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(
            fallbackMethod = "paymentTimeoutFallbackMethod",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "4000")
            }
    )
    public String paymentInfoTimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentTimeOut(id);
        return result;
    }

    public String paymentTimeoutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对方支付系统繁忙请10秒钟后在试或者自己运行出差自己检查";
    }

51.Hystrix之全局服务降级DefaultProperties

目前问题1 每个业务方法对应一个兜底的方法,代码膨胀
解决方法

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

步骤

  1. 写统一处理异常的方法
  2. 加@DefaultProperties(defaultFallback = “”)

@RestController
@DefaultProperties(defaultFallback ="paymentGlobalFallbackMethod" )
public class OrderHystrixController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;
   @GetMapping("consumer/payment/hystrix/timeout/{id}")
//    @HystrixCommand(
//            fallbackMethod = "paymentTimeoutFallbackMethod",
//            commandProperties = {
//                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "4000")
//            }
//    )
    @HystrixCommand
    public String paymentInfoTimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentTimeOut(id);
        return result;
    }
    public String paymentGlobalFallbackMethod(){
        return "Global异常处理信息,请稍后在试";
    }
}

52.Hystrix之通配服务降级FeignFallback

目前问题2: 统一和自定义的分开,代码混乱
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
未来我们要面对的异常

  • 运行
  • 超时
  • 宕机

处理方式:修改cloud-consumer-feign-hystrix-order80

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

package com.atguigu.springcloud.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfoOk(Integer id) {
        return "---------PaymentFallbackService fail paymentInfoOk------";
    }

    @Override
    public String paymentTimeOut(Integer id) {
        return "---------PaymentFallbackService fail paymentTimeOut------";

    }
}

yml(开启feign的调用功能)

server:
  port: 81

eureka:
  client:
    register-with-eureka: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka

#开启
feign:
  hystrix:
    enabled: true

service指定类

package com.atguigu.springcloud.service;

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

@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)//指定类
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfoOk(@PathVariable("id") Integer id);

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

53.Hystrix之服务熔断理论

断路器,相当于保险丝。
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
在这里插入图片描述

54. Hystrix之服务熔断案例(上)

  1. 修改cloud-provider-hystrix-payment8001
package com.atguigu.springcloud.service;

import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    ...

    // ==服务熔断
    @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"),// 失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if(id < 0){
            throw new RuntimeException("****id 不能为负数");
        }
        String simpleUUID = IdUtil.fastSimpleUUID();
        return Thread.currentThread().getName()+"\t"+"调用成功,流水号"+simpleUUID;
    }

    public String paymentCircuitBreakerFallback(@PathVariable("id") Integer id){
        return "id不能为负数,请稍后在试,id="+id;
    }
}

  1. 官网
    The precise way that the circuit opening and closing occurs is as follows:

    1. Assuming the volume across a circuit meets a certain threshold : HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
    2. And assuming that the error percentage, as defined above exceeds the error percentage defined in : HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
    3. Then the circuit-breaker transitions from CLOSED to OPEN.
    4. While it is open, it short-circuits all requests made against that circuit-breaker.
    5. After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next request is let through. If it fails, the command stays OPEN for the sleep window. If it succeeds, it transitions to CLOSED and the logic in 1) takes over again.
  2. HystrixCommandProperties配置类

package com.netflix.hystrix;

...

public abstract class HystrixCommandProperties {
    private static final Logger logger = LoggerFactory.getLogger(HystrixCommandProperties.class);

    /* defaults */
    /* package */ static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second)
    private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second
    private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;// default => statisticalWindowVolumeThreshold: 20 requests in 10 seconds must occur before statistics matter
    private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;// default => sleepWindow: 5000 = 5 seconds that we will sleep before trying again after tripping the circuit
    private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;// default => errorThresholdPercentage = 50 = if 50%+ of requests in 10 seconds are failures or latent then we will trip the circuit
    private static final Boolean default_circuitBreakerForceOpen = false;// default => forceCircuitOpen = false (we want to allow traffic)
    /* package */ static final Boolean default_circuitBreakerForceClosed = false;// default => ignoreErrors = false 
    private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second
    private static final Boolean default_executionTimeoutEnabled = true;

    ...
}

55.Hystrix之服务熔断案例(下)

@RestController
@Slf4j
public class PaymentController
{
    @Resource
    private PaymentService paymentService;

    ...
    
    //====服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id)
    {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: "+result);
        return result;
    }
}

测试
自测cloud-provider-hystrix-payment8001
正确 - http://localhost:8001/payment/circuit/1
错误 - http://localhost:8001/payment/circuit/-1
多次错误,再来次正确,但错误得显示
重点测试 - 多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行

56.Hystrix之服务熔断总结

大神论文
Martin Fowler的相关论文
在这里插入图片描述
熔断类型

  • 熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态。
  • 熔断关闭:熔断关闭不会对服务进行熔断。
  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断。

官网断路器流程图
在这里插入图片描述
断路器在什么情况下开始起作用

//=====服务熔断
@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"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    ...
}

涉及到断路器的三个重要参数:

  1. 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  2. 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  3. 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

断路器开启或者关闭的条件

  • 到达以下阀值,断路器将会开启:
    • 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
    • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
  • 当开启的时候,所有请求都不会进行转发
  • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。

断路器打开之后

  1. 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
  2. 原来的主逻辑要如何恢复呢?
    对于这一问题,hystrix也为我们实现了自动恢复功能。
    当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

所有配置

@HystrixCommand(fallbackMethod = "fallbackMethod", 
                groupKey = "strGroupCommand", 
                commandKey = "strCommand", 
                threadPoolKey = "strThreadPool",
                
                commandProperties = {
                    // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
                    @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
                    // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
                    @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
                    // 配置命令执行的超时时间
                    @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
                    // 是否启用超时时间
                    @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                    // 执行超时的时候是否中断
                    @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
                    
                    // 执行被取消的时候是否中断
                    @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
                    // 允许回调方法执行的最大并发数
                    @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
                    // 服务降级是否启用,是否执行回调函数
                    @HystrixProperty(name = "fallback.enabled", value = "true"),
                    // 是否启用断路器
                    @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                    // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
                    
                    // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                    // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
                    @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
                    // 断路器强制打开
                    @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
                    // 断路器强制关闭
                    @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
                    // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
                    @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
                    
                    // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
                    // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
                    // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
                    @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
                    // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
                    @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
                    // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
                    @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
                    // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
                    // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
                    // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
                    @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
                    
                    // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
                    @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
                    // 是否开启请求缓存
                    @HystrixProperty(name = "requestCache.enabled", value = "true"),
                    // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
                    @HystrixProperty(name = "requestLog.enabled", value = "true"),

                },
                threadPoolProperties = {
                    // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
                    @HystrixProperty(name = "coreSize", value = "10"),
                    // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
                    @HystrixProperty(name = "maxQueueSize", value = "-1"),
                    // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
                    // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
                }
               )
public String doSomething() {
	...
}

57.Hystrix工作流程最后总结

服务限流 - 后面高级篇讲解alibaba的Sentinel说明

1.在这里插入图片描述

  1. 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。
  2. 命令执行。
  3. 其中 HystrixCommand实现了下面前两种执行方式
    1. execute():同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常。
    2. queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
  4. 而 HystrixObservableCommand实现了后两种执行方式:
    1. obseve():返回Observable对象,它代表了操作的多个统
      果,它是一个Hot Observable (不论“事件源”是否有“订阅者”,都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程)。
    2. toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有“订间者”的时候并不会发布事件,而是进行等待,直到有“订阅者"之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
  5. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
  6. 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
  7. 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。
  8. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
    1. HystrixCommand.run():返回一个单一的结果,或者抛出异常。
    2. HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
  9. Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。
  10. 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
    1. 第4步∶当前命令处于“熔断/短路”状态,断洛器是打开的时候。
    2. 第5步∶当前命令的钱程池、请求队列或者信号量被占满的时候。
    3. 第6步∶HystrixObsevableCommand.construct()或HytrixCommand.run()抛出异常的时候。
      当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。
      **tips:**如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常,Hystrix依然会运回一个Obsevable对象,但是它不会发射任结果数惯,而是通过onError方法通知命令立即中断请求,并通过onError方法将引起命令失败的异常发送给调用者

58.Hystrix图形化Dashboard搭建

概述
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。

Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

仪表盘9001

  1. 新建cloud-consumer-hystrix-dashboard9001
  2. pom
<?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>
        <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>
  1. yml
server:
  port: 9001
  1. 主启动加@EnableHystrixDashboard
package com.atguigu.springcloud;

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

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

  1. 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001
    浏览器输入http://localhost:9001/hystrix
    在这里插入图片描述

59.Hystrix图形化Dashboard监控实战

修改cloud-provider-hystrix-payment8001

注意:新版本Hystrix需要在主启动类PaymentHystrixMain8001中指定监控路径

package com.atguigu.springcloud;


import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableEurekaClient
//@EnableCircuitBreaker
@EnableHystrix
public class PaymentHystiixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystiixMain8001.class,args);
    }
    /**
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     *只要在自己的项目里配置上下面的servlet就可以了
     *否则,Unable to connect to Command Metric Stream 404
     */
    @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个eureka同时启动8001,9001

观察监控窗口
9001监控8001 - 填写监控地址 - http://localhost:8001/hystrix.stream 到 http://localhost:9001/hystrix页面的输入框。
测试地址

  • http://localhost:8001/payment/circuit/1
  • http://localhost:8001/payment/circuit/-1
    测试通过

先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。
在这里插入图片描述
如何看?

  • 7色
    在这里插入图片描述

  • 1圈
    实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
    该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

  • 1线
    曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。- 整图说明1:
    在这里插入图片描述

  • 整图说明2
    在这里插入图片描述

60.GateWay是什么

上一代zuul 1.x官网
Gateway官网

概述
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway—句话:gateway是原zuul1.x版的替代
在这里插入图片描述

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

SpringCloud Gateway是Spring Cloud的一个全新项目,基于Spring 5.0+Spring Boot 2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的API路由管理方式

SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

作用

  • 方向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

微服务架构中网关的位置
在这里插入图片描述

61.GateWay非阻塞异步模型

有Zuull了怎么又出来Gateway?我们为什么选择Gateway?

  1. netflix不太靠谱,zuul2.0一直跳票,迟迟不发布。

    1. 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。
    2. Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但Spring Cloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
    3. 多方面综合考虑Gateway是很理想的网关选择。
  2. SpringCloud Gateway具有如下特性

    1. 基于Spring Framework 5,Project Reactor和Spring Boot 2.0进行构建;
    2. 动态路由:能够匹配任何请求属性;
    3. 可以对路由指定Predicate (断言)和Filter(过滤器);
    4. 集成Hystrix的断路器功能;
    5. 集成Spring Cloud 服务发现功能;
    6. 易于编写的Predicate (断言)和Filter (过滤器);
    7. 请求限流功能;
    8. 支持路径重写。
  3. SpringCloud Gateway与Zuul的区别

    1. 在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul。
    2. Zuul 1.x,是一个基于阻塞I/O的API Gateway。
    3. Zuul 1.x基于Servlet 2.5使用阻塞架构它不支持任何长连接(如WebSocket)Zuul的设计模式和Nginx较像,每次I/О操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第-次加载较慢的情况,使得Zuul的性能相对较差。
    4. Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul .x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍。
    5. Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
    6. Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验

Zuul1.x模型
Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Serviet IO处理模型。

Servlet的生命周期?servlet由servlet container进行生命周期管理。

  • container启动时构造servlet对象并调用servlet init()进行初始化;
  • container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service);
  • container关闭时调用servlet destory()销毁servlet。
    在这里插入图片描述

上述模式的缺点

Servlet是一个简单的网络IO模型,当请求进入Servlet container时,Servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(如抽风用Jmeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。

所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即Spring实现了处理所有request请求的一个servlet (DispatcherServlet)并由该servlet阻塞式处理处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端。

Gateway模型
WebFlux是什么?官方文档
传统的Web框架,比如说: Struts2,SpringMVC等都是基于Servlet APl与Servlet容器基础之上运行的。

但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring 5必须让你使用Java 8)。

Spring WebFlux是Spring 5.0 引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet APl,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

Gateway工作流程

三大核心概念

  1. Route(路由) - 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
  2. Predicate(断言) - 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
  3. Filter(过滤) - 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

在这里插入图片描述
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。

predicate就是我们的匹配条件;而fliter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

工作流程

官网总结
在这里插入图片描述
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post")执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发 + 执行过滤器链。

63.GateWay9527搭建

  1. 新建Module - cloud-gateway-gateway9527
  2. POM
<?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>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <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-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>
  1. yml
server:
  port: 9527

spring:
  application:
    name: cloud-getway
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  1. 业务类
  2. 主启动
package com.atguigu.springcloud;

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

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

  1. 9527网关如何做路由映射?
    cloud-provider-payment8001看看controller的访问地址
  • get
  • lb
    我们目前不想暴露8001端口,希望在8001外面套一层9527
  1. YML新增网关配置
server:
  port: 9527

spring:
  application:
    name: cloud-getway
  cloud:
    #  新增网关配置
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由



eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  1. 测试
    启动7001
    启动8001-cloud-provider-payment8001
    启动9527网关
    访问说明
  • 添加网关前 - http://localhost:8001/payment/get/1
  • 添加网关后 - http://localhost:9527/payment/get/1
  • 两者访问成功,返回相同结果
    在这里插入图片描述
    在这里插入图片描述

63.Gateway配置路由的两种方式

  1. 在配置文件yml中配置,见上一章节
  2. 代码中注入RouteLocator的Bean
    官方案例
package com.atguigu.springcloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateConfig {

    @Bean
    public RouteLocator customerLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("psth_route_atguigu",
                r -> r.path("/guonei")
                        .uri("https://news.baidu.com/guonei"));

        return routes.build();
    }
}

百度国内新闻网址,需要外网 - http://news.baidu.com/guonei
浏览器输入http://localhost:9527/guonei,返回http://news.baidu.com/guonei相同的页面。

64.GateWay配置动态路由

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能(不写死一个地址)。

启动 eureka7001和payment8001/8002

pom

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

YML
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
#############################新增网关配置###########################
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**         # 断言,路径相匹配的进行路由
####################################################################

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试
浏览器输入 - http://localhost:9527/payment/lb
结果
不停刷新页面,8001/8002两个端口切换。

65.GateWay常用的Predicat

官网地址
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合。
Spring Cloud Gateway创建Route 对象时,使用RoutePredicateFactory 创建 Predicate对象,Predicate 对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

常用的Route Predicate Factory

  1. The After Route Predicate Factory
  2. The Before Route Predicate Factory
  3. The Between Route Predicate Factory
  4. The Cookie Route Predicate Factory
  5. The Header Route Predicate Factory
  6. The Host Route Predicate Factory
  7. The Method Route Predicate Factory
  8. The Path Route Predicate Factory
  9. The Query Route Predicate Factory
  10. The RemoteAddr Route Predicate Factory
  11. The weight Route Predicate Factory

66.GateWay的Filter

官方文档
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
Spring Cloud Gateway的Filter:

  • 生命周期:
    • pre
    • post
  • 种类(具体看官方文档):
    • GatewayFilter - 有31种
    • GlobalFilter - 有10种
      常用的GatewayFilter:AddRequestParameter GatewayFilter

自定义全局GlobalFilter:
两个主要接口介绍:

  1. GlobalFilter
  2. Ordered

能干什么:

  1. 全局日志记录
  2. 统一网关鉴权

    代码案例:

GateWay9527项目添加MyLogGateWayFilter类:

package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered
{

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        log.info("***********come in MyLogGateWayFilter:  "+new Date());

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");

        if(uname == null)
        {
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder()
    {
        return 0;
    }
}

测试:
启动:EurekaMain7001、PaymentMain8001、GateWayMain9527、PaymentMain8002
浏览器输入:
http://localhost:9527/payment/lb - 访问异常
在这里插入图片描述

http://localhost:9527/payment/lb?uname=11 - 正常访问
在这里插入图片描述

67.Stream为什么被引入

常见MQ(消息中间件):

  • ActiveMQ
  • RabbitMQ
  • RocketMQ
  • Kafka

有没有一种新的技术诞生,让我们不再关注具体MQ的细节,我们只需要用一种适配绑定的方式,自动的给我们在各种MQ内切换。(类似于Hibernate)

Cloud Stream是什么?屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。

68.Stream是什么及Binder介绍

官方文档1
官方文档2
Cloud Stream中文指导手册

什么是Spring Cloud Stream?

官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。

应用程序通过inputs或者 outputs 来与Spring Cloud Stream中binder对象交互。

通过我们配置来binding(绑定),而Spring Cloud Stream 的binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。

通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。

目前仅支持RabbitMQ、 Kafka

69.Stream的设计思想

标准MQ
在这里插入图片描述

  • 生产者/消费者之间靠消息媒介传递信息内容
  • 消息必须走特定的通道 - 消息通道 Message Channel
  • 消息通道里的消息如何被消费呢,谁负责收发处理 - 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅。

为什么用Cloud Stream?

比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区。
在这里插入图片描述

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候Spring Cloud Stream给我们提供了—种解耦合的方式。

Stream凭什么可以统一底层差异?

在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

Binder:

  • INPUT对应于消费者
  • OUTPUT对应于生产者
    在这里插入图片描述

Stream中的消息通信方式遵循了发布-订阅模式

Topic主题进行广播

  • 在RabbitMQ就是Exchange
  • 在Kakfa中就是Topic

70.Stream编码常用注解简介

Spring Cloud Stream标准流程套路

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

  • Binder - 很方便的连接中间件,屏蔽差异。
  • Channel - 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。
  • Source和Sink - 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。

编码API和常用注解

组成说明
Middleware中间件,目前只支持RabbitMQ和Kafka
BinderBinder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现
@Input注解标识输入通道,通过该输乎通道接收到的消息进入应用程序
@Output注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener监听队列,用于消费者的队列的消息接收
@EnableBinding指信道channel和exchange绑定在一起

71Stream消息驱动之生产者

  1. 新建Module:cloud-stream-rabbitmq-provider8801
<?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-stream-rabbitmq-provider8801</artifactId>
    <dependencies>
        <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.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</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>
  1. yml
server:
  port: 8801

spring:
  application:
    name: cloud-stream-provider
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 127.0.0.1
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        output: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
          binder: {defaultRabbit} # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: send-8801.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址
  1. 主启动
package com.atguigu.springcloud;

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

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

  1. 业务类
  • 发送消息的接口
package com.atguigu.springcloud.service;

public interface IMessageProvider {

    public String send();
}

  • 发送消息的实现类
package com.atguigu.springcloud.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;

import java.util.UUID;

@EnableBinding(Source.class)//定义消息推送管道
public class IMessageProviderImpl implements IMessageProvider {

    @Autowired
    private MessageChannel output;

    @Override
    public String send() {
        String message = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(message).build());
        System.out.println("******serial"+message);
        return message;
    }
}

  • controller
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SendMessageController {

    @Autowired
    private IMessageProvider iMessageProvider;

    @GetMapping("/send")
    public String send(){
        String send = iMessageProvider.send();
        return send;
    }
}

  1. 测试
  • 启动 7001eureka
  • 启动 RabpitMq:http://localhost:15672/
  • 启动 8801
  • 访问 - http://localhost:8801/sendMessage,后台将打印serial: UUID字符串

72 Stream消息驱动之消费者

  1. 新建Module:cloud-stream-rabbitmq-consumer880
  2. pom
<?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-stream-rabbitmq-consumer8802</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</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>
  1. yml
server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 47.93.21.100
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: {defaultRabbit} # 设置要绑定的消息服务的具体设置

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址
  1. 主启动
package com.atguigu.springcloud;

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

@SpringBootApplication
public class StreamMain8802 {

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

  1. 业务类
package com.atguigu.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message){
        System.out.println("消费者1号,-------->接收消息:"+message.getPayload()+"\t port"+serverPort);
    }
}

  1. 测试
  • 启动EurekaMain7001
  • 启动StreamMQMain8801
  • 启动StreamMQMain8802
  • 8801发送8802接收消息
    在这里插入图片描述
    8801
    在这里插入图片描述

8802
在这里插入图片描述

73.Stream之消息重复消费

依照8802,克隆出来一份运行8803 - cloud-stream-rabbitmq-consumer8803。

启动

RabbitMQ
服务注册 - 8801
消息生产 - 8801
消息消费 - 8802
消息消费 - 8803

运行后有两个问题

  1. 有重复消费问题
  2. 消息持久化问题

消费

http://localhost:8801/sendMessage
目前是8802/8803同时都收到了,存在重复消费问题
如何解决:分组和持久化属性group(重要)

生产实际案例

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。这时我们就可以使用Stream中的消息分组来解决。
在这里插入图片描述
注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。不同组是可以全面消费的(重复消费)。

74Stream之group解决消息重复消费

原理

微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。
不同的组是可以重复消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

8802/8803都变成不同组,group两个不同
group: groupA、groupB

8802修改YML

server:
  port: 8802

spring:
  application:
    name: cloud-stream-consumer
  cloud:
    stream:
      binders: # 在此处配置要绑定的rabbitmq的服务信息;
        defaultRabbit: # 表示定义的名称,用于于binding整合
          type: rabbit # 消息组件类型
          environment: # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 47.93.21.100
                port: 5672
                username: guest
                password: guest
      bindings: # 服务的整合处理
        input: # 这个名字是一个通道的名称
          destination: studyExchange # 表示要使用的Exchange名称定义
          content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
          binder: {defaultRabbit} # 设置要绑定的消息服务的具体设置
          group: groupA

eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
    lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
    instance-id: receive-8802.com  # 在信息列表时显示主机名称
    prefer-ip-address: true     # 访问的路径变为IP地址

8803修改YML(与8802的类似位置 groupA)
结论:还是重复消费

8802/8803实现了轮询分组,每次只有一个消费者,8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费。
8802/8803都变成相同组,group两个相同
group: A_Group
8802修改YMLgroup: groupA
8803修改YMLgroup: groupA
结论:同一个组的多个微服务实例,每次只会有一个拿到

75.Stream之消息持久化

通过上述,解决了重复消费问题,再看看持久化。
停止8802/8803并去除掉8802的分组group: A_Group,8803的分组group: A_Group没有去掉。
8801先发送4条消息到RabbitMq。
先启动8802,无分组属性配置,后台没有打出来消息。
再启动8803,有分组属性配置,后台打出来了MQ上的消息。(消息持久化体现)

76.Sleuth是什么

为什么会出现这个技术?要解决哪些问题?

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
在这里插入图片描述
在这里插入图片描述

是什么

https://github.com/spring-cloud/spring-cloud-sleuth
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
在分布式系统中提供追踪解决方案并且兼容支持了zipkin

解决

在这里插入图片描述

77.Sleuth之zipkin搭建安装

下载

  1. SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可
  2. https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/2.12.9/
  3. zipkin-server-2.12.9-exec.jar

运行jar

java -jar zipkin-server-2.12.9-exec.jar

运行控制台

http://localhost:9411/zipkin/
在这里插入图片描述

术语

完整的调用链路
表示一请求链路,一条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来
在这里插入图片描述
—条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来。
在这里插入图片描述
整个链路的依赖关系如下:

在这里插入图片描述

名词解释

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
  • span:表示调用链路来源,通俗的理解span就是一次请求信息
    Sleuth链路监控展现

78.服务提供者

cloud-provider-payment8001(服务的提供方)

  1. 添加依赖
        <!--包含了sleuth+zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
  1. 改ym
spring:
  zipkin: #<-------------------------------------关键
    base-url: http://localhost:9411
    sleuth: #<-------------------------------------关键
      sampler:
      #采样率值介于 0 到 1 之间,1 则表示全部采集
      probability: 1

  1. 业务类 PaymentController
    @GetMapping("/payment/zipkin")
    public String paymentZipkin() {
        return "hi ,i'am paymentzipkin server fall back,welcome to here, O(∩_∩)O哈哈~";
    }

cloue-consumer-order80(服务的消费方)

  1. 添加依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
  1. yml
spring:
    zipkin:
      base-url: http://localhost:9411
    sleuth:
      sampler:
        probability: 1
  1. orderController
    @GetMapping("/consumer/payment/zipkin")
    public String paymentZipkin()
    {
        String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
        return result;
    }

测试

  1. 依次启动eureka7001/8001/80 - 80调用8001几次测试下
  2. 打开浏览器访问: http://localhost:9411
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值