SpringCloud学习第一季-2

目录

6.服务注册与发现-Zookeeper服务注册与发现

1.Eureka停止更新了你怎么办

2.SpringCloud整合Zookeeper代替Eureka

1.注册中心Zookeeper

2.服务提供者

1.cloud-provider-payment8004

2.pom文件引入依赖

3.新增application.yml文件添加配置

4.主启动类

6.启动测试

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

3.服务消费者

1.新建cloud-consumerzk-order80

2.引入pom.xml依赖

3.新增application.yml配置文件

4.启动类

5.编写Bean配置类

6.编写控制类

7.测试

7.服务注册与发现-Consul服务注册与发现

1.Consul简介

2.安装并运行Consul(附docker版)

3.服务提供者

1.新建Module支付服务provider8006

2.POM

3.YML

4.启动类

5.业务类

6.测试

4.服务消费者

1.新建Module消费服务order80

2.POM

3.YML

4.启动类

5.业务类

6.测试

5.三个注册中心异同点

1.CAP

2.AP架构

3.CP架构

web界面

8.服务调用-Ribbon负载均衡服务调用

一、Ribbon概述

1、Ribbon能作什么

1.1 LB (负载均衡)

1.1.2 集中式 LB

1.1.3 进程内 LB

二、负载均衡演示

2.1 架构说明

2.1.1 Ribbon在工作室分成两步

2.1.2 为什么没有引入Ribbon也可以完成负载均衡?

2.2. 二说RestTemplate的使用

2.2.1 getForObject方法/getForEntity方法

2.2.2 postForObject/postForEntity

三、Ribbon核心组件:IRule接口

3.1. IRule接口的各实现类,以及对应功能

3.2. 如何替换负载均衡算法

四、负载均衡算法

4.1 原理

4.2 源码

RoundRobinRule源码

4.3 手写一个负载均衡算法

9.服务调用-OpenFeign服务接口调用

一、OpenFeign概述

1、OpenFeign是什么

小结:

2、Feign作用

3、Feign与OpenFeign的区别

二、OpenFeign使用步骤

1、创建cloud-consumer-openfeign-order80 模块

2、写pom

3、写yaml

4、主启动类

5、业务类

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

5.2 consumer的控制层Controller

5.3 测试

三、OpenFeign超时控制

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

1.1 服务提供方8001,8002故意写暂停程序

1.2 服务消费方80在PaymentFeignService添加超时方法

1.3 消费方80添加超时方法到controller

1.4 测试

2、超时报错

2.1 OpenFeign默认等待时间为1秒钟,超过后报错

2.2 配置超时时间

2.3 重启服务测试

四、OpenFeign日志打印功能

1、日志级别

2、配置日志

2.1 配置日志bean

2.2 配置消费端的yml文件

2.3 查看后台日志

10.服务熔断/降级-Hystrix断路器

一、概述

1、分布式系统面临的问题

1.1 服务雪崩

2、Hystrix 是什么

3、Hystrix能做什么

4、Hystrix官网

二、Hystrix 重要概念

1、服务降级:fallback

2、服务熔断:break

3、服务限流:flowlimit

三、Hystrix案例

1、构建正确提供者模块

2、 高并发压测

1.安装JMeter

2、新建消费者模块:cloud-consumer-feign-hystrix-order80

3、Hystrix如何解决问题?

四、服务降级

1、服务降级:8001生产者服务端 (服务端)

1.1 业务类(Service)方法上添加@HystrixCommand

1.2 主启动类激活

1.3 测试

2、服务降级:80消费者服务端(客户端)

2.1 修改yaml

2.2 主启动类:@EnableHystrix

2.3 业务类——Controller

2.4 测试

3、进一步优化

3.1 目前存在的问题——代码臃肿、耦合度高

3.2 解决代码臃肿

3.3 降低代码耦合度

五、服务熔断

1、熔断机制概述

2、Hystrix服务熔断案例

2.1 对8001服务端的Service层进行改造 增加熔断机制

2.2 修改模块cloud-provider-hystrix-payment8001的controller层

2.3 测试

2.3.1 正确跟错误情况自测

2.3.2 测试熔断机制

3、Hystrix服务熔断总结

3.1 熔断类型

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

3.3 断路器开启或关闭条件

3.4 断路器打开之后

3.5 所有的配置

//六、服务限流

七、Hystrix工作流程

八、服务监控 HystrixDashBoard

8.1 新建cloud-consumer-hystrix-dashboard9001

8.1.2 pom

8.1.3 yaml

8.1.4 主启动类

8.1.5 启动测试

8.2 断路器演示(服务监控hystrixDashboard)

8.2.1 注意事项

8.2.2 测试

8.2.3 监控页的各个地方表示什么意思?


6.服务注册与发现-Zookeeper服务注册与发现

1.Eureka停止更新了你怎么办

  • 服务注册中心:zookeeper代替eureka
  • Consul服务注册与发现

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

2.SpringCloud整合Zookeeper代替Eureka

1.注册中心Zookeeper

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

本节学习Zookeeper需要掌握linux系统,会使用虚拟机安装Zookeeper,保证你的虚拟机能够正常访问主机电脑,要能够访问外网,我这里直接使用的是在Centos7中使用docker安装Zookeeper镜像,然后启动运行Zookeeper镜像。安装启动步骤我放在这里了 写文章-优快云博客

2.服务提供者

1.cloud-provider-payment8004

2.pom文件引入依赖

本次引入的依赖里面就不再是之前的eureka了,而是Zookeeper相关的依赖
依赖是

   <!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>

        </dependency>

        <dependency>

全部依赖

<?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>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8004</artifactId>

    <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>org.xu.springcloud</groupId>

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

            <version>1.0.0</version>

        </dependency>

        <!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-zookeeper-discovery</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>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

</project>
3.新增application.yml文件添加配置

这里zookeeper我们也同样需要知道它的地址和端口号。这样才能把服务注册进去

#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
  port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
  application:
    name: cloud-provider-payment
  cloud:
    zookeeper:
      connect-string: 192.168.111.144:2181
 
4.主启动类
package com.xu.java;  
  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  
@SpringBootApplication  
@EnableDiscoveryClient  //该注解用于向使用consul或者zookeeper作为注册中心时注册服务  
public class PaymentMain8004 {  
    public static void main(String[] args) {  
        SpringApplication.run(PaymentMain8004.class,args);  
    }  
}

5.编写Controller类

package com.xu.java.controller;  
  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.util.UUID;  
  
@RestController  
@Slf4j  
public class PaymentController {  
  
  
    @Value("${server.port}")  
    private String serverPort;  
  
  
    @RequestMapping(value = "/payment/zk")  
    public String paymentzk()  
    {  
        return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();  
    }  
  
}
6.启动测试

启动8004注册进zookeeper

如何查看docker容器中zookeeper镜像的版本
docker exec <zookeeper_container> zkServer.sh version
 


zookeeper_container表示的是容器id 可以使用docker ps 查看容器id

这里视频里老师是启动报错了(我这里没有报错,因为我跟老师使用的zookeeper版本是不一样的,而且我这个是使用的docker容器创建的镜像),
 


原因是因为老师在centos7中使用的版本和本地项目使用的zookeeper版本不一样导致的,
我们先来看一下本地项目中
idea项目中报错的版本就是这个3.5.3
 


老师使用的是
 


这就启动报错了,但是我自己的是3.9.3,也没有报错,这里不废话,说一下如何解决这样的报错,因为我们可能在实际工作中会遇到生产环境用到的zookeeper版本跟项目里当前版本是不一样的,这个时候就需要向生产环境版本靠近,以它为准,怎么做呢?就是把项目里自带的zookeeper依赖给排除掉,然后额外重新引入和生产环境一致的版本。
具体做法:

<!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>

            <!--先排除自带的zookeeper3.5.3-->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>

                    <artifactId>zookeeper</artifactId>

                </exclusion>

            </exclusions>

        </dependency>

        <!--添加zookeeper3.4.9版本-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>

            <artifactId>zookeeper</artifactId>

            <version>3.4.9</version>

        </dependency>

这样看着跟清晰一点

这样就解决了。然后重新启动项目,我们这里没有报错启动以后直接去zookeeper注册中心查看一下有没有将我们自己的服务注册进来。

我本地是已经启动完zookeeper了,所以直接看下。
 


首先是进入到zookeeper容器
docker exec -it my-zookeeper /bin/bash
然后ls进入到bin目录,执行./zkCli.sh

  • 当执行./zkCli.sh(在 Unix - like 系统中)时,如果没有指定服务器地址参数,它会默认尝试连接本地的 Zookeeper 服务器(通常是localhost:2181)。例如,如果你的 Zookeeper 服务运行在本地机器上,并且监听端口为 2181,那么执行这个脚本就能连接到该服务。
    连接完以后输入ls /


    我们可以看到有一个services


    或者直接复制出来找一个json格式化工具

可以看到服务是成功注册进zookeeper里面了。

然后我们本地主机浏览器访问 http://localhost:8004/payment/zk

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

zookeeper也是有心跳机制,在一定时间能如果一直没心跳返回,Zookeeper就会把服务节点剔除掉。所以在Zookeeper上的服务节点是临时节点。

3.服务消费者

1.新建cloud-consumerzk-order80
2.引入pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>

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


    <dependencies>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>org.xu.springcloud</groupId>

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

            <version>1.0.0</version>

        </dependency>

        <!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-zookeeper-discovery</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>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

</project>
3.新增application.yml配置文件
    server:  
      port: 80  
      
    spring:  
      application:  
        name: cloud-consumerzk-order80  
      cloud:  
        zookeeper:   
            connect-string: 192.168.71.131:2181 #zookeeper地址
4.启动类
package com.xu.java;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  
@SpringBootApplication  
@EnableDiscoveryClient  
public class ConsumerZkMainApp80 {  
    public static void main(String[] args) {  
        SpringApplication.run(ConsumerZkMainApp80.class,args);  
    }  
}
5.编写Bean配置类
package com.xu.java.util;  
  
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 ApplicationContextBean {  
  
    @Bean  
    @LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力 因为我们controller类里访问的是服务名  
    public RestTemplate getRestTemplate(){  
        return new RestTemplate();  
    }  
}
6.编写控制类
package com.xu.java.controller;  
  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.client.RestTemplate;  
  
@RestController  
@Slf4j  
public class ConsumerZkController {  
  
  
    private static final String INVOKE_URL = "http://cloud-provider-payment";  
  
    @Autowired  
    RestTemplate restTemplate;  
  
    @GetMapping("/consumer/payment/zk")  
    public String paymentInfo(){  
  
        String forObject = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);  
        System.out.println("消费者调用支付服务(zookeeper)--->result:" + forObject);  
        return forObject;  
  
    }  
  
}
7.测试

启动服务以后看下是否成功注册进去

http://localhost/consumer/payment/zk

7.服务注册与发现-Consul服务注册与发现

1.Consul简介

Consul官网:Consul | HashiCorp Developer
Consul中文文档:Spring Cloud Consul 中文文档 参考手册 中文版
简介
Consul是一种服务网格解决方案,提供具有服务发现,配置和分段功能的全功能控制平面。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建完整的服务网格。Consul需要一个数据平面,并支持代理和本机集成模型。Consul附带了一个简单的内置代理,因此一切都可以直接使用,还支持Envoy等第三方代理集成。
 


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

健康检测:领事客户端可以提供任意数量的运行状况检查,这些检查可以与给定服务(“ Web服务器是否返回200 OK”)或本地节点(“内存利用率低于90%”)相关。操作员可以使用此信息来监视群集的运行状况,服务发现组件可以使用此信息将流量从不正常的主机发送出去。

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

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

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

可视化Web界面

Consul旨在对DevOps社区和应用程序开发人员友好,使其非常适合现代,灵活的基础架构。

2.安装并运行Consul(附docker版)

官网安装说明
Deploy Consul on VMs | Consul | HashiCorp Developer

下载完成后只有一个consul.exe文件
硬盘路径下双击运行,查看版本号信息

使用开发模式启动
consul agent -dev

可视化界面
通过以下地址可以访问Consul的首页:http://localhost:8500 不同版本的界面可能不同

以上是windows系统版本,如果是linux版本,这里我们使用docker来操作

#拉取consul镜像
docker pull consul

#启动consul
docker run -d  -p 8500:8500/tcp --name myConsul  consul agent -server -ui -bootstrap-expect=1 -client=0.0.0.0

然后在浏览器输入http://http://10.211.55.17/:8500(linux的IP地址加上冒号8500)

我这里网络不太行,一直没有拉取下来镜像,就不展示了

3.服务提供者

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>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</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>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

</project>

3.YML

server:  
  port: 8006  
spring:  
  application:  
    name: cloud-providerconsul-payment8006  
    # consul注册中心地址  
  cloud:  
    consul: #这是cloud下的一个子配置项,表示配置的是Consul相关的设置。  
      host: localhost #这是Consul服务的主机地址,设置为localhost,表示Consul服务运行在本机。  
      port: 8500  #这是Consul服务的端口号,设置为8500,这是Consul的默认端口。  
      discovery: #这是consul下的一个子配置项,用于配置服务发现相关的设置。  
        service-name: ${spring.application.name}  #这是Consul服务发现中使用的服务名称,设置为${spring.application.name}。${}表示这是一个占位符,它会被Spring Boot应用的实际服务名称替换。

4.启动类

package com.xu.java;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  
@SpringBootApplication  
@EnableDiscoveryClient  
public class CloudPaymentConsulApp {  
    public static void main(String[] args) {  
        SpringApplication.run(CloudPaymentConsulApp.class,args);  
    }  
}

5.业务类

package com.xu.java.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;  
  
@RestController  
@Slf4j  
public class Controller {  
  
    @Value("${server.port}")  
    private String serverPort;  
  
    @GetMapping("/payment/consul")  
    public String paymentConsul(){  
  
        return "springcloud with consul: " + serverPort;  
  
    }  
  
  
}

6.测试

http://localhost:8006/payment/consul

4.服务消费者

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>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</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>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

</project>

3.YML

server:  
  port: 80  
  
spring:  
  application:  
    name: cloud-consumer-order  
    #注册到consul  
  cloud:  
    consul:  
      host: localhost  
      port: 8500  
      discovery:  
        service-name: ${spring.application.name}

4.启动类

package com.xu.java;  
  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
  
@SpringBootApplication  
@EnableDiscoveryClient  
public class OrderConsulMain80 {  
    public static void main(String[] args) {  
        SpringApplication.run(OrderConsulMain80.class,args);  
    }  
}

5.业务类

配置类

package com.xu.java.bean;  
  
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 AppConfiguartion {  
  
  
    @Bean  
    @LoadBalanced    
    public RestTemplate restTemplate() {  
        return new RestTemplate();  
    }  
  
  
}
package com.xu.java.controller;  
  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  
import org.springframework.web.client.RestTemplate;  
  
import javax.annotation.Resource;  
  
@RestController  
@Slf4j  
public class OrderConsulController {  
  
    private static final String INVOKE_URL = "http://consul-provider-payment";  
  
    @Resource  
    private RestTemplate restTemplate;  
  
    @GetMapping("/consumer/payment/consul")  
    public String paymentInfo(){  
        return restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);  
    }  
  
  
  
}

6.测试

查看consul注册中心

访问地址: http://localhost/consumer/payment/consul

5.三个注册中心异同点

1.CAP

  • Consistency(一致性):即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
  • Avaliability(可用性):即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
  • Partition Tolerance(分区容错性):即分布式系统在遇到某节点或网络故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求应用虽然是一个分布式系统,但看上去切好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统要求,对于用户而言并没有什么体验上的影响。
  • CP和AP:分布式中,**分区容错(P)是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性(C)可用性(A)**只能2选1

CAP理论关注粒度是数据,而不是整体系统设计的策略

2.AP架构

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

3.CP架构

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

  1. 最多只能同时较好的满足两个
    CAP理论的核心:一个分布式系统不可能同时很好的满足三个需求。因此根据CAP原理将 NOSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类。
  • CA:单点集群,满足一致性,可用性的系统,通常扩展性不强大。
  • CP:满足一致性,分区容错性的系统,对数据一致性要求高,所以性能负担大。 Zookeeper/Consul 要求数据必须一致性。
  • AP:满足可用性,分许容错性的系统,通常可能对一致性要求低一些。 Eureka 场景:商场,暂时不一致错一点没关系,只要能正常访问下单即可。 Eureka通过设置一些属性,也可以通过牺牲高可用性实现一致性的要求。
  1. 分布式必须满足:P: Partition tolerance 分区容错性
  • Eureka主要保证高可用:AP
  • Zookeeper/Consul主要保证数据的一致:CP

web界面

  • Eureka/Consul都有一个web界面。
  • Zookeeper只有一个linux客户端。

8.服务调用-Ribbon负载均衡服务调用

一、Ribbon概述

SpringCloud Ribbon 是基于Netflix Ribbon实现的一套客户端,负载均衡的工具。

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

官网: https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon已经进入维护模式!未来的替换方案:Load Banlancer

1、Ribbon能作什么

1.1 LB (负载均衡)

LB 负载均衡 Load balance 是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
常见的负载均衡有软件 Nginx LVS 硬件F5等

常见的负载均衡算法
●轮询:为第一个请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。
●最小连接:优先选择连接数最少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。
●散列:根据请求源的 IP 的散列(hash)来选择要转发的服务器。这种方式可以一定程度上保证特定用户能连接到相同的服务器。如果你的应用需要处理状态而要求用户能连接到和之前相同的服务器,可以考虑采取这种方式。

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

1.1.2 集中式 LB

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

1.1.3 进程内 LB

●将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个何时的服务器。
●Ribbon就属于是进程内LB, 它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

一句话总结Ribbon:Ribbon是实现负载均衡的一套客户端工具结合RestTemplate实现调用。

二、负载均衡演示

2.1 架构说明

总结:Ribbon其实就是一个软负载均衡的客户端组件。
它可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例

2.1.1 Ribbon在工作室分成两步

●第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server
●第二部再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如 轮询,随机和根据响应时间加权。

2.1.2 为什么没有引入Ribbon也可以完成负载均衡?

之前我们写样例的时候没有引入spring-cloud-start-ribbon也可以使用ribbon我们通过一个注解:@LoadBalanced赋予了RestTemplate负载均衡的能力;
原因是spring-boot-netfix-eureka-client自带了spring-starter-ribbon引用

因此我们不需要再次引入Ribbon坐标,因为我们已经引入了spring-boot-netfix-eureka-client,里面自带Ribbon包。

<dependency>
    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>

</dependency>

2.2. 二说RestTemplate的使用

官网地址: RestTemplate (Spring Framework 5.2.2.RELEASE API)
为什么说它: 所谓的负载均衡,就是Ribbon结合RestTemplate实现调用微服务之间的调用。

2.2.1 getForObject方法/getForEntity方法

getForObject() // 返回对象为响应体中数据转化成的对象,基本上可以理解为JSON
getForEntity()  // 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。

  
/**  
 * 根据id获取支付信息  
 * 注意请求路径最前面要/开头  
 * @param id  
 * @return  
 */  
@GetMapping(value = "/consumer/payment/get/{id}")  
public CommonResult<Payment> getPaymentById(@PathVariable Long id){  
    //这里url地址拼接的时候一定要保证和提供者接口的地址一致,否则就请求不到正确的地址了。  
    return restTemplate.getForObject(URL+"/payment/getPaymentById/"+id,CommonResult.class);  
  
}  
  
  
public CommonResult<Payment> getPaymentById2(@PathVariable Long id){  
    ResponseEntity<CommonResult> forEntity = restTemplate.getForEntity(URL + "/payment/getPaymentById/" + id, CommonResult.class);  
    if (forEntity.getStatusCode().is2xxSuccessful()) {  
        return forEntity.getBody();  
    } else {  
        return new CommonResult<>(444,"操作失败");  
    }  
}

这些方法对应着各种HTTP状态码,可以用来判断返回的entity对象包含的是哪种状态码。

可以跑通。可以用entity获取更详细的信息,比如头信息,响应体,状态码等。
log.info(entity.getStatusCode() + "\t" + entity.getHeaders());

2.2.2 postForObject/postForEntity

    @GetMapping("/consumer/payment/createEntity")
    public CommonResult<Payment> create2(Payment payment){
        log.info("********插入的数据:" + payment);
        //postForObject分别有三个参数:请求地址,请求参数,返回的对象类型
//        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
        ResponseEntity<CommonResult> entity = restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
        if(entity.getStatusCode().is2xxSuccessful()){
            return entity.getBody();
        }else{
            return new CommonResult<>(444, "操作失败");
        }
    }

使用postman测试

测试成功

数据库中也增加了相应数据

三、Ribbon核心组件:IRule接口

3.1. IRule接口的各实现类,以及对应功能

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

IRule接口主要的实现类:


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

3.2. 如何替换负载均衡算法

①修改cloud-consumer-order80

②负载均衡配置类注意事项
注意配置类细节:官方文档明确给出了警告:这个自定义负载均衡配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。 (因为Ribbon是客户端(消费者)这边的,所以Ribbon的自定义配置类是在客户端(消费者)添加,不需要在提供者或注册中心添加)

什么是@ComponentScan
@SpringBootApplication注解中包含了@ComponentScan注解

也就是说@SpringBootApplication标注的主配置类所在的包,以及所有子包都会被扫描到。
 


所以我们自定义的负载均衡配置类不能跟主启动类在同一个包下。

③新建com.atguigu.configRule包,将自定义负载均衡配置类MySelfRule放在改包下
写MySelfRule配置类

package com.configRule;  
  
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 {  
  
    /**  
     * 自定义负载均衡算法  
     * IRule:负载均衡接口  
     * RandomRule:随机算法  
     * 如果是其他算法,看看都有哪些具体实现类在IRule接口里  
     * @return  
     */  
    @Bean  
    public IRule myRule()  
    {  
        return new RandomRule();//定义为随机  
    }  
  
  
}

④主启动类添加@RibbonCilent注解
name里面的内容表示指明访问xxx服务时使用复杂均衡,configuration表示指明负载均衡策略

package com.java;  
  
import com.configRule.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 = "CLOUD-PAYMENT-SERVICE",configuration= MySelfRule.class) //自定义负载均衡算法  
public class MainApp80 {  
  
    public static void main(String[] args) {  
        SpringApplication.run(MainApp80.class,args);  
    }  
  
  
}

⑤启动cloud-consumer-order80测试
测试成功,访问http://localhost/consumer/payment/get/3

四、负载均衡算法

4.1 原理

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

List<ServiceInstance> instances = doscoveryClient.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,对应下标为 0,则获得服务地址为 127.0.0.1:8002
当总请求数为3时:3 % 2 = 1,对应下标为 1,则获得服务地址为 127.0.0.1:8001
当总请求数为4时:4 % 2 = 0,对应下标为 0,则获得服务地址为 127.0.0.1:8002
如此类推

4.2 源码

原理+JUC(CAS+自旋锁的复习)

RoundRobinRule源码

RoundRobinRule的核心为choose方法:

public class RoundRobinRule extends AbstractLoadBalancerRule {
    //AtomicInteger原子整形类
    private AtomicInteger nextServerCyclicCounter;
    ...
    public RoundRobinRule() {
        //此时nextServerCyclicCounter是一个原子整形类,并且value为0
        nextServerCyclicCounter = new AtomicInteger(0);
    }
    ...
    //ILoadBalancer选择的负载均衡机制,这里lb为轮询
    public Server choose(ILoadBalancer lb, Object key) {
        //如果传入的lb没有负载均衡,为空
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        //还没选到执行的server,并且选择的次数没超过10次,进行选择server
        while (server == null && count++ < 10) {
            //lb.getReachableServers获取所有状态是up的服务实例
            List<Server> reachableServers = lb.getReachableServers();
            //lb.getAllServers获取所有服务实例
            List<Server> allServers = lb.getAllServers();
            //状态为up的服务实例的数量
            int upCount = reachableServers.size();
            //所有服务实例的数量
            int serverCount = allServers.size();
            
            //如果up的服务实例数量为0或者服务实例为0,打印日志log.warn并返回server=null
            if ((upCount == 0) || (serverCount == 0)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }
            
            //获取到接下来server的下标
            int nextServerIndex = incrementAndGetModulo(serverCount);
            //获取下一个server
            server = allServers.get(nextServerIndex);

            //如果
            if (server == null) {
                //线程让步,线程会让出CPU执行权,让自己或者其它的线程运行。(让步后,CPU的执行权也有可能又是当前线程)
                Thread.yield();
                //进入下次循环
                continue;
            }
            
            //获取的server还活着并且还能工作,则返回该server
            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            //否则server改为空
            server = null;
        }

        //选择次数超过10次,打印日志log.warn并返回server=null
        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }


    private int incrementAndGetModulo(int modulo) {
        //CAS加自旋锁
        //CAS(Conmpare And Swap):是用于实现多线程同步的原子指令。CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
        //自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 
        for (;;) {
            //获取value,即0
            int current = nextServerCyclicCounter.get();
            //取余,为1
            int next = (current + 1) % modulo;
            //进行CAS判断,如果此时在value的内存地址中,如果value和current相同,则为true,返回next的值,否则就一直循环,直到结果为true
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }
    ...
}

AtomicInteger的compareAndSet方法:

public class AtomicInteger extends Number implements java.io.Serializable {
    ...
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    ...
}

4.3 手写一个负载均衡算法

①8001/8001微服务改造:controller

    @GetMapping(value = "/payment/lb")  
    public String getPaymentLB()  
    {  
        return serverPort;  
    }

②80微服务改造
ApplicationContextConfig去掉@LoadBalance注解

③创建LoadBalancer接口
新建com.atguigu.springcloud.lb包

package com.java.lb;  
  
import org.springframework.cloud.client.ServiceInstance;  
  
import java.util.List;  
  
public interface LoadBalancer {  
  
  
    /**  
     * 获取注册中心服务实例,负载均衡,获取当前调用的是具体哪里服务  
     */  
    ServiceInstance instances(List<ServiceInstance> instances);  
  
}

④创建LoadBalancer接口的实现类MyLoadBalancer

@Component
public class MyLoadBalancer implements LoadBalancer{
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int current;
        int next;
        //从下面的分析中得知:该循环主要是得到next值,单机是每循环一次返回一次next
        //高并发时:就不是这种情况了。因为数字会被抢占。
        //自旋锁
        do {
            current = this.atomicInteger.get();
            //考虑到极端情况,atomicInteger最大值为2147483647
            //当请求次数超过这个值的时候,从0开始
            next = current >= 2147483647 ? 0 : current + 1;
            //compareAndSet方法使用了CAS机制,判断current是否变化,如果变了自旋,反之更新current
            /**
             * 当atomicInteger=0时,current=0, next=1
             * atomicInteger和current进行比较,相等时返回true将atomicInteger更新为next,1,取反跳出循环,再返回next
             * 当atomicInteger=2147483647时,current=2147483647,next=0
             * atomicInteger和current进行比较,相等时返回true将atomicInteger更新为next, 0,取反跳出循环,再返回next
             *
             * 如果是高并发时,则需要判断当前线程获得的current值跟atomicInteger的值是否相等,
             * 如果不相等则表示其他线程已经操作了atomicInteger,自旋。
             * 这就是乐观锁的一个实现
             */
        }while (!this.atomicInteger.compareAndSet(current, next));
        System.out.println("************第几次访问次数:" + next);
        return next;
    }
    
    //负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
    // 每次服务重启动后rest接口计数从1开始。
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        //计算当前访问应该分配给哪台服务器处理
        int index = getAndIncrement() % serviceInstances.size();
        //返回目标服务器
        return serviceInstances.get(index);
    }
}

⑤修改OrderController

    @Resource  //自动注入自定义的MyLoadBalancer类
    private LoadBalancer loadBalancer;
    @Resource
    private DiscoveryClient discoveryClient;
    
    /**
     * 测试自己的轮循算法
     */
    @GetMapping(value = "/consumer/payment/lb")
    public String getPaymentLB() {
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        if (instances == null || instances.size() <= 0) {
            return null;
        }
        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();
        return restTemplate.getForObject(uri + "/payment/lb", String.class);
    }

⑥测试
http://localhost/consumer/payment/discovery

9.服务调用-OpenFeign服务接口调用

一、OpenFeign概述

1、OpenFeign是什么

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

小结:

●Feign是一个声明式Rest/WebService客户端,使用Feign能让WebService客户端更加简单。
●只需创建一个接口并在接口上添加注解即可。

2、Feign作用

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

3、Feign与OpenFeign的区别

二、OpenFeign使用步骤

以前我们在消费者服务调用生产者服务时,采用Ribbon+restTemplate进行客户端服务调用和负载均衡。现在采用OpenFeign绑定服务接口。
Feign 是使用在消费端!
接口+注解 ------ 微服务调用接口+@FeignClient 注解

1、创建cloud-consumer-openfeign-order80 模块

Feign在消费端使用

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>springcloud2020</artifactId>  
        <groupId>org.xu.springcloud</groupId>  
        <version>1.0.0</version>  
    </parent>    <modelVersion>4.0.0</modelVersion>  
  
    <artifactId>cloud-consumer-openfeign-order80</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>org.xu.springcloud</groupId>  
            <artifactId>cloud-api-commons</artifactId>  
            <version>1.0.0</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>  
    <properties>        <maven.compiler.source>8</maven.compiler.source>  
        <maven.compiler.target>8</maven.compiler.target>  
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
    </properties>  
</project>

OpenFeign整合了Ribbon,所以具有负载均衡的功能

3、写yaml

不将其注册到Eureka作为微服务,而是作为一个Feign客户端

server:
  port: 80

eureka:
  client:
    # 表示不将其注入Eureka作为微服务,不作为Eureak客户端了,而是作为Feign客户端
    register-with-eureka: false
    service-url:
      # 集群版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka

4、主启动类

主启动类上添加 @EableFeignClients注解

package com.java.springcloud;  
  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.openfeign.EnableFeignClients;  
  
@SpringBootApplication  
@EnableFeignClients //开启Feign客户端 不是eureka客户端了  
public class OrderFeignMain80 {  
    public static void main(String[] args) {  
        SpringApplication.run(OrderFeignMain80.class,args);  
    }  
}

5、业务类

声明一个远程调用服务接口,不需要能被Springboot扫描到

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

新建PaymentFeignService接口并新增注解@FeignClient
@FeignClient("provider微服务名字")
注意:
●这里声明的方法签名,必须和provider微服务(服务提供者微服务)中的controller中方法的签名一致
●如果需要传递参数,那么@RequestParam 和@RequestBody @PathVariable 不能省 必加

package com.java.springcloud.service;  
  
import com.java.cloud.common.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(name = "CLOUD-PAYMENT-SERVICE") //指定调用的服务名称  
public interface PaymentFeignService {  
  
    @GetMapping("/payment/getPaymentById/{id}")  
    public CommonResult getPaymentById(@PathVariable Long id) ;  
}

这样就可以找到CLOUD-PAYMENT-SERVICE微服务下面的/payment/get/{id}这个地址。
这也就说明:
●PaymentFeignService接口+@FeignClient注解,完成Feign的包装调用
●指明找哪个微服务上面的地址
主启动类开启Feign(增加@EnableFeignClients注解),接口上使用@FeignClient,组合使用

5.2 consumer的控制层Controller

通过自己的80 Service接口层,去调用服务提供者中的接口

package com.java.springcloud.controller;  
  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import com.java.springcloud.service.PaymentFeignService;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
  
import javax.annotation.Resource;  
  
@RestController  
public class OrderFeignController {  
  
    @Resource  
    private PaymentFeignService paymentFeignService;  
  
  
    @GetMapping(value = "/consumer/payment/get/{id}")  
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)  
    {  
        return paymentFeignService.getPaymentById(id);  
    }  
  
  
  
  
}
5.3 测试

先启动两个Eureka集群 7001/7002
再启动两个paymentprovider微服务 8001/8002
启动使用OpenFeign的OrderFeign80
测试成功:
消费者微服务没有注册在Eureka中;通过消费者本身的接口地址,去调用生产者微服务对应的接口,并且可以实现负载均衡
 


比较符合我们的编程习惯,在80中还是controller调用service,service再去调用8001的controller。

三、OpenFeign超时控制

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

1.1 服务提供方8001,8002故意写暂停程序
@GetMapping(value = "/payment/timeOutTest")  
public String timeOutTest()  
{  
    try {  
        TimeUnit.SECONDS.sleep(3);  
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    }  
    return serverPort;  
}

注意:假如你8002也启动的话 里面没有写paymenttimeout方法,会报404错误;我这里在8002也写了该方法。

1.2 服务消费方80在PaymentFeignService添加超时方法
@Component  
@FeignClient(name = "CLOUD-PAYMENT-SERVICE") //指定调用的服务名称  
public interface PaymentFeignService {  
  
    @GetMapping("/payment/getPaymentById/{id}")  
    CommonResult getPaymentById(@PathVariable("id") Long id) ;  
  
    /**  
     * 演示超时  
     * @return  
     */  
    @GetMapping(value = "/payment/timeOutTest")  
    String timeOutTest();  
  
}
1.3 消费方80添加超时方法到controller
@GetMapping(value = "/consumer/payment/getTimeOutTest")  
public CommonResult<String> getTimeOutTest(){  
    CommonResult<String> result = new CommonResult<>();  
    String s = paymentFeignService.timeOutTest();  
    result.setData(s);  
    return result;  
}
1.4 测试

8001服务方自测:没有问题(3s后出结果)
 


80消费方测试:超时报错 OpenFeign默认等待时间为1s,超过后报错

2、超时报错

2.1 OpenFeign默认等待时间为1秒钟,超过后报错

默认Feign客户端只等待一秒钟,但是服务段处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这种请况,有时候我们需要设置Feign客户端的超时控制。
Feign 默认是支持Ribbon ,Feign依赖里自己带了Ribbon;Feign客户端的负载均衡和超时控制都由Ribbon控制

2.2 配置超时时间

为了避免上述情况,我们需要设置Feign客户端的超时等待时间。
yml文件中开启配置:
注意:我配置ReadTimeout/ConnectTimeout时yml没有提示。

server:  
  port: 80  
  
eureka:  
  client:  
    register-with-eureka: false  
    service-url:  
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/  
  
      #设置feign客户端超时时间(OpenFeign默认支持ribbon)  
      ribbon:  
        #指的是建立连接后从服务器读取到可用资源所用的时间  
        ReadTimeout: 5000  
        #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间  
        ConnectTimeout: 5000

我这样配置不行,必须使用下面的方式,这是新版本用的
3.0.3版本
feign.client.config.default.connect-timeout=5000 feign.client.config.default.read-timeout=5000
 

2.3 重启服务测试

四、OpenFeign日志打印功能

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

1、日志级别

NONE

默认的,不显示任何日志

BASIC

仅记录请求方法、URL、响应状态码及执行时间

HEADERS

除了BASIC中定义的信息之外,还有请求和响应的头信息

FULL

除了HEADERS中定义的信息外,还有请求和响应的正文及元数据。

2、配置日志

配置在消费端

2.1 配置日志bean
package com.java.springcloud.config;  
  
import feign.Logger;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
@Configuration  
public class FeignConfig {  
  
  
    /**  
     * 配置feign的日志级别  
     * @return  
     */  
    @Bean  
    Logger.Level feignLoggerLevel(){  
        return Logger.Level.FULL;  
    }  
  
}
2.2 配置消费端的yml文件
    server:  
      port: 80  
      
    eureka:  
      client:  
        register-with-eureka: false  
        service-url:  
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/  
      
    feign:  
      client:  
        config:  
          default: #FeignClient接口调用超时时间  
            connectTimeout: 5000  
            readTimeout: 5000  
            logging:  
      level:  
        # feign日志以什么级别监控哪个接口  
        com.atguigu.springcloud.service.PaymentFeignService: debug

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

2.3 查看后台日志

10.服务熔断/降级-Hystrix断路器

一、概述

1、分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
几个微服务之间的相互调用,一定会导致链路越来越长。一个出事了,就会导致整条链路上的服务都出事。

1.1 服务雪崩

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

2、Hystrix 是什么

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

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

3、Hystrix能做什么

●服务降级
●服务熔断
●接近实时的监控
●。。。。

4、Hystrix官网

https://github.com/Netflix/Hystrix/wiki/How-To-Use

二、Hystrix 重要概念

1、服务降级:fallback

假如对方的系统不可用了,需要一个兜底的解决方案或备选响应;向调用方返回一个符合预期的、可处理的备选响应。
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个好友提示,fallback
哪些情况会触发降级:
●程序运行异常
●超时
●服务熔断触发服务降级
●线程池/信号量打满也会导致服务降级

2、服务熔断:break

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

3、服务限流:flowlimit

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

三、Hystrix案例

1、构建正确提供者模块

这里视频里面首先将eureka7001从集群版改成了单机版,我没有改。
搭建基础平台:从正确--->错误--->降级熔断--->恢复
以此平台 演示 Hystrix 服务降级 服务熔断 服务限流

1.1 创建cloud-provider-hystrix-payment8001模块

1.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>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</version>

    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>
        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>

        </dependency>

        <!--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>org.xu.springcloud</groupId>

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

            <version>1.0.0</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>


    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>

</project>

1.2 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,http://eureka7002.com:7002/eureka  
      #单机版  
#      defaultZone: http://eureka7001.com:7001/eureka

1.3 主启动类

package com.java.springcloud;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;  
  
@SpringBootApplication  
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中  
public class PaymentHystrixMain8001 {  
    public static void main(String[] args) {  
        SpringApplication.run(PaymentHystrixMain8001.class,args);  
    }  
}

1.4 业务类

1.4.1 Service和实现类

package com.java.springcloud.service;  
  
  
public interface PaymentService {  
  
    String paymentInfo_OK(Long id);  
  
    String paymentInfo_Error(Long id);  
  
}

实现类

package com.java.springcloud.service.impl;  
  
import com.java.springcloud.service.PaymentService;  
import org.springframework.stereotype.Service;  
  
import java.util.concurrent.TimeUnit;  
  
@Service  
public class PaymentServiceImpl implements PaymentService {  
  
  
    @Override  
    public String paymentInfo_OK(Long id) {  
  
        return "线程池"+Thread.currentThread().getName() + " paymentInfo_OK, id: " + id + "(●'◡'●)jaja";  
    }  
  
    @Override  
    public String paymentInfo_Error(Long id) {  
        int timeNum = 3;  
        try {  
            TimeUnit.SECONDS.sleep(timeNum);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        return "线程池"+Thread.currentThread().getName() + " paymentInfo_Error, id: " + id + " ʅ(‾◡◝)ʃ";  
    }  
}

1.4.2 Controller

package com.java.springcloud.controller;  
  
import com.java.springcloud.service.PaymentService;  
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;  
  
@RestController  
@Slf4j  
public class PaymentController {  
  
    @Autowired  
    private PaymentService paymentService;  
  
    @GetMapping("/payment/hystrix/ok/{id}")  
    public String paymentInfo_OK(@PathVariable("id") Long id){  
  
        String result = paymentService.paymentInfo_OK(id);  
        log.info("****result: "+result);  
        return result;  
    }  
  
    @GetMapping("/payment/hystrix/error/{id}")  
    public String paymentInfo_Error(@PathVariable("id") Long id){  
  
        String result = paymentService.paymentInfo_Error(id);  
        log.info("****result: "+result);  
        return result;  
    }  
  
} 

1.5 正常测试
启动eireka7001/7002;启动cloud-provider-hystrix-payment8001
访问模拟健康的方法: ok的方法

访问模拟复杂业务逻辑的方法:timeout的方法,耗时3s

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

2、 高并发压测

上述在非高并发情况下,还勉强可以满足。使用jmeter,设置20000个并发压死8001,20000个请求都去访问http://localhost:8001/payment/hystrix/timeout/31

1.安装JMeter

JMeter下载地址:Apache JMeter - Download Apache JMeter
下载tgz和zip都可以:
 


进入解压后的目录的bin目录,找到jmeter.properties文件,修改语言zh_CN。
 


从终端进入bin目录,输入jmeter.bat运行jmeter,修改成中文版。
 


进行高并发测试
测试http://localhost:8001/payment/hystrix/timeout/1
创建线程组
 


添加http请求
 


点击上边的绿色三角开始运行
然后去访问http://localhost:8001/payment/hystrix/ok/1,访问速度变慢了。
结果发现原来可以迅速响应的http://localhost:8001/payment/hystrix/ok/31 变得卡顿。两个都在转圈。
原因:
因为ok跟timeout都在一个微服务里,现在大量的资源被timeout占用,微服务必须集中资源去处理这些 高并发的请求。那么ok方法,得到的资源就少,进而被影响。SpringBoot默认集成的是tomcat容器,里面有一个tomcat的线程池,高并发下没有多余的 线程来分解压力和处理ok方法。 每个线程都要等待3秒钟,才能拿到timeout的结果响应。那么2000个线程一起过来, Tomcat池子里的线程,马上就会被抢占完。
Jmeter压测结论:
上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等, 最终导致消费端80不满意,服务端8001直接被拖死。

2、新建消费者模块:cloud-consumer-feign-hystrix-order80

2.1 pom

<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>${project.version}</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>

2.2 yml 声明端口和要注册的eureka

server:
  port: 80

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

2.3 主启动类

package com.java.springcloud.controller;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.openfeign.EnableFeignClients;  
  
@SpringBootApplication  
@EnableFeignClients    // 开启Feign客户端
public class OrderHystrixMain80 {  
    public static void main(String[] args)  
    {  
        SpringApplication.run(OrderHystrixMain80.class,args);  
    }  
}

2.4 业务类

2.4.1 PaymentHystrixService

package com.java.springcloud.service;  
  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.stereotype.Component;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
  
@Component  
@FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")  
public interface PaymentHystrixService {  
  
    @GetMapping("/payment/hystrix/ok/{id}")  
    String paymentInfo_OK(@PathVariable("id") Long id);  
  
    @GetMapping("/payment/hystrix/error/{id}")  
    String paymentInfo_Error(@PathVariable("id") Long id);  
  
}

2.4.2 OrderHystirxController

package com.java.springcloud.controller;  
  
import com.java.springcloud.service.PaymentHystrixService;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
  
import javax.annotation.Resource;  
  
@RestController  
@Slf4j  
public class OrderHystirxController {  
  
  
    @Resource  
    private PaymentHystrixService paymentHystrixService;  
  
  
    @GetMapping("/consumer/payment/hystrix/ok/{id}")  
    String paymentInfo_OK(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_OK(id);  
    };  
  
    @GetMapping("/consumer/payment/hystrix/error/{id}")  
    String paymentInfo_Error(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_Error(id);  
    };  
  
}

2.5 正常测试
http://localhost/consumer/payment/hystrix/timeout/31
由于使用的是Feign作为客户端,默认1s没有得到响应就会报超时错误。这是正常的,这里我们不进行改动。

http://localhost/consumer/payment/hystrix/ok/31
 


2.6 高并发测试
2W个线程压8001 消费端80微服务再去访问正常的OK微服务8001地址http://localhost/consumer/payment/hystrix/ok/31: 要么转圈圈等待,要么消费端报超时错误。
原因: 8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕 所以80此时调用8001,客户端访问响应缓慢,转圈圈。 正因为有上述故障或不佳表现,才有了我们的降级/容错/限流等技术诞生。

3、Hystrix如何解决问题?

如何解决?解决的要求?
●超时导致服务器变慢(转圈):超时不再等待
●出错(宕机或程序运行出错):出错要有兜底
●解决
○对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
○对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
○对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

四、服务降级

不管是在消费者,还是提供者,都可以进行服务降级,使用过@HystrixCommand注解指定降级后的方法
一般服务降级 是放在 客户端

1、服务降级:8001生产者服务端 (服务端)

cloud-provider-hystrix-payment8001
服务降级:payment8001支付侧(生产者服务)
8001先从自身找问题,设置自身调用超时时间的峰值,峰值内可以正常运行。超过了就需要有兜底的方法,做服务降级fallback。向调用方法返回一个符合预期的,可以处理的备选响应(FallBack)

1.1 业务类(Service)方法上添加@HystrixCommand

一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法,当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler。

package com.java.springcloud.service.impl;  
  
import com.java.springcloud.service.PaymentService;  
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 PaymentServiceImpl implements PaymentService {  
  
  
    @Override  
    public String paymentInfo_OK(Long id) {  
  
        return "线程池"+Thread.currentThread().getName() + " paymentInfo_OK, id: " + id + "(●'◡'●)jaja";  
    }  
  
    /**  
     * 服务降级  
     * HystrixCommand注解就是表示启用服务降级的,fallbackMethod属性就是指定服务降级的方法·然后我们需要在这个类中指定  
     * fallbackMethod = "paymentInfo_TimeOutHandler" 服务降级的方法,当超时的时候就会去调用这个方法。  
     * HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")代码的作用就是  
     * 表示,当执行时间超过3秒的时候就会触发服务降级,执行fallbackMethod = "paymentInfo_TimeOutHandler"方法  
     * @param id  
     * @return  
     */  
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {  
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")  
    })  
    @Override  
    public String paymentInfo_Error(Long id) {  
        int timeNum = 5; //当我们使用服务降级的时候,我们这里模拟一下假设业务逻辑需要执行5秒中时间,但是我们的服务降级设置的是3秒,所以就会触发服务降级  、
        //        int a = 10/0; //测试运行报错会不会走兜底方法
        try {  
            TimeUnit.SECONDS.sleep(timeNum);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        return "线程池"+Thread.currentThread().getName() + " paymentInfo_Error, id: " + id + " ʅ(‾◡◝)ʃ";  
    }  
  
    /**  
     * 触发服务降级就会走这个方法  
     * @param id  
     * @return  
     */  
    public String paymentInfo_TimeOutHandler(Long id){  
        return "线程池"+ Thread.currentThread().getName() + "paymentInfo_TimeOutHandler,id : " + id + "/(ㄒoㄒ)/~~";  
    }  
}
1.2 主启动类激活

主启动类添加新注解@EnableCircuitBreaker

package com.java.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  
@EnableCircuitBreaker  
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中  
public class PaymentHystrixMain8001 {  
    public static void main(String[] args) {  
        SpringApplication.run(PaymentHystrixMain8001.class,args);  
    }  
}
1.3 测试

启动我们的eruka服务,并且启动生产者服务后开始调用地址开始测试

我们故意制造的两个异常: 1. int age = 10/0 计算异常 2. 我们定义超时时间3s,服务运行5s的超时异常 这些都会造成当前服务不可用了,就会进行服务降级,执行兜底的方案。

2、服务降级:80消费者服务端(客户端)

cloud-consumer-feign-hystrix-order80
我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

2.1 修改yaml
2.2 主启动类:@EnableHystrix

开启Hystrix支持

package com.java.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 // 开启Feign客户端  
@EnableHystrix  //开启Hystrix 
public class OrderHystrixMain80 {  
    public static void main(String[] args)  
    {  
        SpringApplication.run(OrderHystrixMain80.class,args);  
    }  
}

2.3 业务类——Controller

package com.java.springcloud.controller;  
  
import com.java.springcloud.service.PaymentHystrixService;  
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;  
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
  
import javax.annotation.Resource;  
  
@RestController  
@Slf4j  
public class OrderHystirxController {  
  
  
    @Resource  
    private PaymentHystrixService paymentHystrixService;  
  
  
    @GetMapping("/consumer/payment/hystrix/ok/{id}")  
    String paymentInfo_OK(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_OK(id);  
    };  
  
    @HystrixCommand(fallbackMethod = "consumerInfo_TimeOutHandler",commandProperties = {  
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")  
    })  
    @GetMapping("/consumer/payment/hystrix/error/{id}")  
    String paymentInfo_Error(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_Error(id);  
    };  
  
    /**  
     * 服务降级兜底方法  
     * @param id  
     * @return  
     */  
    public String consumerInfo_TimeOutHandler(Long id){  
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
  
    }  
  
  
}
2.4 测试

把8001服务端的业务处理时间设置为2s,设定3s以内是正常的,保证8001服务端能够正常运行。

访问请求地址:http://localhost/consumer/payment/hystrix/error/22

3、进一步优化

3.1 目前存在的问题——代码臃肿、耦合度高

●我们现在服务降级的方法和业务处理的方法混杂在了一块,耦合度很高。
●如果每个接口,每个方法都需要一个“兜底”方法,那么就会造成代码臃肿
所以我们需要一个全局的服务降级:global fallback;需要特殊照顾的方法,我们再进行精确的配置服务降级。

3.2 解决代码臃肿

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

下面以消费者服务端80为例:cloud-consumer-feign-hystrix-order80
●在业务类Controller上加@DefaultProperties(defaultFallback = "method_name")注解,并指明defaultFallback
●在需要服务降级的方法上标注@HystrixCommand 注解
○如果@HystrixCommand里没有指明fallbackMethod,就默认使用@DefaultProperties(defaultFallback = "method_name")中指明的降级服务


修改的代码部分:

package com.java.springcloud.controller;  
  
import com.java.springcloud.service.PaymentHystrixService;  
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;  
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;  
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  
  
import javax.annotation.Resource;  
  
@RestController  
@Slf4j  
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")  
public class OrderHystirxController {  
  
  
    @Resource  
    private PaymentHystrixService paymentHystrixService;  
  
  
    @GetMapping("/consumer/payment/hystrix/ok/{id}")  
    String paymentInfo_OK(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_OK(id);  
    };  
  
//    @HystrixCommand(fallbackMethod = "consumerInfo_TimeOutHandler",commandProperties = {  
//            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")  
//    })  
    @HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的  
    @GetMapping("/consumer/payment/hystrix/error/{id}")  
    String paymentInfo_Error(@PathVariable("id") Long id){  
        return paymentHystrixService.paymentInfo_Error(id);  
    };  
  
    /**  
     * 服务降价兜底方法  
     * @param id  
     * @return  
     */  
    public String consumerInfo_TimeOutHandler(Long id){  
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";  
  
    }  
  
    /**  
     * 注意:因为这个是公共的兜底的方法,所以在使用的时候也是又一定限制的,比如参数这块就不好设置了,因为可能每个方法需要的参数是不一样的  
     * 但是也不能说这个方法一点用没有,对于一般的情况我们直接返回固定的字符串是可以的,但是对于一些复杂的情况,可以单独的使用写好的兜底的方法  
     * 这个要根据实际的情况来定  
     *  
     * @return  
     */  
    public String payment_Global_FallbackMethod(){  
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";  
    }  
  
  
}

测试

3.3 降低代码耦合度

在80微服务中,它没有注册进eureka注册中心成为eureka客户端。


而是通过openFeign,成为一个Feign客户端,通过Feign来进行微服务的调用和负载均衡。
直接定义Service层的接口通过@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") 这个注解找CLOUD-PROVIDER-HYSTRIX-PAYMENT这个微服务中的方法进行调用。
 


即:
只要是用Feign进行微服务的调用,那么一定有标注@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")的接口,这个接口就能实现调用其他微服务的操作,因此我们将这个接口中的全部方法进行统一的fallback服务降级。

解耦案例——服务降级:客户端调用服务端,遇到服务端宕机或关闭等极端情况
本次案例服务降级是在客户端80实现完成,与服务端8001没有关系。
只需要为Feign客户端定义的接口添加一个服务降级实现类即可实现解耦
未来我们遇到的异常有:运行时异常、超时、宕机

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

package com.java.springcloud.service;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class PaymentFallbackService implements PaymentHystrixService{  
    @Override  
    public String paymentInfo_OK(Long id) {  
        return "服务调用失败,提示来自:cloud-consumer-feign-order80-paymentInfo_OK";  
    }  
  
    @Override  
    public String paymentInfo_Error(Long id) {  
        return "服务调用失败,提示来自:cloud-consumer-feign-order80-paymentInfo_Error";  
    }  
  
    @Override  
    public String paymentInfo_Error1(Long id) {  
        return "还会走这里吗????";  
    }  
}

修改PaymentHystrixService接口,指定其服务异常处理类:

package com.java.springcloud.service;  
  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.stereotype.Component;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
  
@Component  
@FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)  
public interface PaymentHystrixService {  
  
    @GetMapping("/payment/hystrix/ok/{id}")  
    String paymentInfo_OK(@PathVariable("id") Long id);  
  
    @GetMapping("/payment/hystrix/error/{id}")  
    String paymentInfo_Error(@PathVariable("id") Long id);  
  
    @GetMapping("/payment/hystrix/error1/{id}")  
    String paymentInfo_Error1(@PathVariable("id") Long id);  
}

确认yml配置文件中开启了hystrix ,这个我们使用注解的方式开启,添加到启动类上面。
针对上卖弄注解代码解释一下:
 


 


 


 


目的是为了实现以下效果:
●正常运行的话就找指定的微服务中的方法执行;
●异常的话就找PaymentFallbackService,由它来统一进行服务降级的处理
这样的话客户端的OrderHystirxController中就不需要再进行降级处理,降级处理只针对要调用的微服务,因此降低了服务降级和客户端的耦合度。

测试
启动7001、7002(我这里用了集群)、8001、80
8001端口正常运行,是可以正常访问的:

有时候浏览器请求地址可能会因为缓存的原因导致刷新失败,今天晚上遇到了好几次了,最好换浏览器或者是重新打开一次浏览器。
8001端口宕机:如果此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

五、服务熔断

熔断也会出发服务降级。

1、熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,自动恢复调用链路。
熔断状态: 开启 关闭 半开启


在SpringCloud框架中,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况。
当失败的调用到一定阈值,缺省时5秒内20次调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand
大神论文:Circuit Breaker
半开状态: 比如说一个微服能承受100的并发量,某一时刻有500人同时访问。该服务就会崩掉,熔断,直接当掉。 外部此时不能访问了,过了一会,发现没有那么高的并发量了。感觉并发量在我的承受之内了(100)。 比如1s/72次,那么就试着这放开这些并发请求。 放着放着发现能够适应当前的并发量了,我再把闸道合上。 放着放着的状态就是半开状态,然后再把断路器关闭变成关闭的状态。

2、Hystrix服务熔断案例

2.1 对8001服务端的Service层进行改造 增加熔断机制

●circuitBreaker.enabled:是否开启断路器
●circuitBreaker.requestVolumeThreshold:该属性设置滚动窗口(快照时间窗口,默认10s)中将使断路器跳闸的最小请求数量(默认是20),如果10s内请求数小于设定值,就算请求全部失败也不会触发断路器。
●circuitBreaker.sleepWindowInMilliseconds:短路多久以后开始尝试是否恢复,默认5s
●circuitBreaker.errorThresholdPercentage:失败率达到多少后跳闸
●metrics.rollingStats.timeInMilliseconds:快照时间窗、滚动窗口
总的意思就是在10s的时间窗口期内,m次请求中有p%的请求失败了,那么断路器启动,随后短路n秒后开始尝试恢复

//=============================服务熔断===============================  
    /**     * 服务熔断,  
     * @param id  
     * @return  
     */  
    @HystrixCommand(fallbackMethod = "paymentCircuiBreaker_fallback",commandProperties = {  
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否开启断路器  
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//当请求数量达到10个  
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//短路多久以后开始尝试是否恢复,默认5s  
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //并且60%的请求失败,就会触发服务降级  
    })  
    public String paymentCircuiBreaker(@PathVariable("id")Integer id){  
        if(id<0){  
            throw new RuntimeException("id 不能为负数");  
        }  
        String s = IdUtil.simpleUUID();  
        return Thread.currentThread().getName()+ "调用成功,流水号是"+ s;  
    }  
  
    public String paymentCircuiBreaker_fallback(@PathVariable("id")Integer id){  
        return "id不能为负数或系统临时异常,请稍后再试";  
    }
2.2 修改模块cloud-provider-hystrix-payment8001的controller层
@RequestMapping(value = "/payment/circuit/{id}",method = RequestMethod.GET)  
public String paymentCircuitBreaker(@PathVariable("id")Integer id){  
    String s = paymentService.paymentCircuiBreaker(id);  
    log.info("*******"+s);  
    return s;  
}
2.3 测试
2.3.1 正确跟错误情况自测

启动7001、7002(我这里启动了Eureka集群)和8001

正常情况下当id不是负数的时候访问都是正常的,如果出现id为负数,代码里面就会抛出异常,首先会进行服务降级,走兜底的方法。如果请求失败的次数没有达到设定的阈值还是正常,但是当我们负数id多次请求以后,再次正数id进行请求,就会出现熔断,也就意味着这个时候正常的请求也得不到响应了,然后当异常次数较少以后,再次请求就会发现恢复正常
 


图片显示不出来效果,我们只有自己实际测试才能看出来。

2.3.2 测试熔断机制

测试一下单位时间请求达到10次/10s以上,在10s内执行了超过10*0.6 = 6次的异常请求,随后进行短路。(10个/10s 注意分母是10s。 默认20个/10s) 短路10s后尝试恢复
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10")
触发了Hystrix的断路器,即使传入正确的参数,也出现服务降级
https://gitee.com/javaxubo/work_note-images/raw/master//image/202506182158708.png

隔了一会才恢复

3、Hystrix服务熔断总结

3.1 熔断类型

●熔断打开:请求不再进行调用当前服务,再有请求调用时将不会调用主逻辑,而是直接调用降级fallback。实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果。内部设置时钟一般为MTTR(Mean time to repair,平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态。
●熔断关闭:熔断关闭不会对服务进行熔断,服务正常调用
●熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

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


●1:快照时间窗(滚动窗口):断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
○对应红框中的metrics.rollingStats.timeInMilliseconds
●2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
○对应红框中的circuitBreaker.requestVolumeThreshold

●3:窗口睡眠时间:剩下一个表示窗口睡眠时间,即断路器触发多少秒(默认5s)后尝试恢复,进入半开状态。
○对应红框中的circuitBreaker.sleepWindowInMilliseconds
●4:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
○对应红框中的circuitBreaker.errorThresholdPercentage

3.3 断路器开启或关闭条件

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

3.4 断路器打开之后

1: 再有请求调用的时候,还会调用主逻辑吗?
将不会调用主逻辑,而是直接调用降级的fallback方法,通过断路器,实现了自动的发现错误并将降级逻辑升级为主逻辑,减少响应延迟的效果。

2:原来的主逻辑要如何恢复?
●对于这一问题mhystrix也为我们实现了自动恢复功能。
●当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑。
●当休眠时间窗到期,断路器将进入半开状态,释放给一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合。
●主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

3.5 所有的配置
//========================All  
@HystrixCommand(fallbackMethod = "str_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 strConsumer() {  
    return "hello 2020";  
}  
public String str_fallbackMethod()  
{  
    return "*****fall back str_fallbackMethod";  
}

//六、服务限流

在spring cloud alibaba Sentinel时再说!

七、Hystrix工作流程

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

工作流程图

步骤说明

1

创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。

2

命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 "事件源" 是否有 "订阅者",都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 "订阅者" 都有可能是从 "事件源" 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 "订阅者" 的时候并不会发布事件,而是进行等待,直到有 "订阅者" 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。

3

若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。

4

检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。

5

线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。

6

Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。

7

Hystrix会将 "成功"、"失败"、"拒绝"、"超时" 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 "熔断/短路"。

8

当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 "服务降级"。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于"熔断/短路"状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。

9

当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。

tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。

八、服务监控 HystrixDashBoard

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard)Hystrix会持续的记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等,Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控,Spring Cloud提供了Hystrix Dashboard的整合,对监控内容转化成可视化页面。

8.1 新建cloud-consumer-hystrix-dashboard9001

8.1.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>springcloud2020</artifactId>

        <groupId>org.xu.springcloud</groupId>

        <version>1.0.0</version>

        <relativePath>../../pom.xml</relativePath>

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

    <properties>
        <maven.compiler.source>8</maven.compiler.source>

        <maven.compiler.target>8</maven.compiler.target>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    </properties>


</project>
8.1.3 yaml
server:  
  port: 9001

给个端口号就行

8.1.4 主启动类

HystrixDashboardMain9001+新注解@EnableHystrixDashboard

package com.java.springcloud;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;  
  
@SpringBootApplication  
@EnableHystrixDashboard  
public class HystrixDashboardMain9001 {  
    public static void main(String[] args) {  
        SpringApplication.run(HystrixDashboardMain9001.class,args);  
    }  
}

所有provider微服务(生产者)提供类8001/8002/8003都需要监控依赖配置
确保所有生产者微服务中均包含spring-boot-starter-actuator依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-actuator</artifactId>  
</dependency>

spring-boot-starter-actuator依赖的作用介绍

8.1.5 启动测试

http://localhost:9001/hystrix


到这里就算启动成功了

8.2 断路器演示(服务监控hystrixDashboard)

微服务想要被监控必须要有spring-boot-starter-actuator依赖

8.2.1 注意事项

注意:新版本Hystrix需要在需要监控的微服务端的主启动类中指定监控路径,不然会报错:


在被监控的服务端主启动类中添加以下代码,这里以【PaymentHystrixMain8001】为示例

package com.java.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.context.annotation.Bean;  
  
@SpringBootApplication  
@EnableCircuitBreaker  
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中  
public class PaymentHystrixMain8001 {  
    public static void main(String[] args) {  
        SpringApplication.run(PaymentHystrixMain8001.class, args);  
    }  
    /**  
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑  
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",  
     *只要在自己的项目里配置上下面的servlet就可以了  
     */  
    @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;  
    }  
    }
8.2.2 测试

●启动Eureka7001、Eureka7002(我这里用的是两个Eureka的集群,视频里是单个)、8001、9001
●9001监控8001
我们需要做的就是填写监控地址http://localhost:8001/hystrix.stream,延迟200ms,起一个名称
 

●打开监控页面


打开的监控页面:【这里一定要注意了,必须是要先请求接口,请求几次以后,我们在点击这个按钮去看监控页面,否则就会出现监控页面下面的图一直处于加载状态,如果此时你要是刷新几次,以为是自己网络或者浏览器问题,就会出现Unable to connect to Command Metric Stream.红色字体,我是返回去看了视频弹幕以后,被同学提醒必须要先请求接口然后再打开这个监控页面,这里视频老师也没有说清楚】

●自测8001端口:http://localhost:8001/payment/circuit/-5,http://localhost:8001/payment/circuit/5,注意是先请求,然后去刷新监控页面
●多次刷新http://localhost:8001/payment/circuit/5 ,首先是测试正常请求后这个界面是什么样子

○圆圈变大了,折线上升,表示请求数量变多,因为请求都是正常的,所以断路器处于关闭状态

●类似的,多次刷新http://localhost:8001/payment/circuit/-5 ,然后接着测试当接口报错以后界面长什么样子

○可以看到断路器处于开启状态

先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。

8.2.3 监控页的各个地方表示什么意思?

7色1圈

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

图片说明:
 


图片说明:
 


图片说明:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值