当前位置: 首页 > news >正文

java学习之springcloud之服务调用+服务降级+服务网关篇

尚硅谷springcloud学习笔记

  • 1. Ribbon负载均衡服务调用
    • 1.1Ribbon的简介
    • 1.2ribbon负载均衡的调用和RestTemplate的二次理解
    • 1.3Ribbon核心组件IRule(负载规则)
    • 1.4负载均衡算法的原理(以轮询为例)
  • 2.OpenFeign服务接口调用(用在消费端80)
    • 2.1OpenFeign简介
    • 2.2OpenFeign的使用步骤
    • 2.3OpenFeign的超时控制
    • 2.4OpenFeign日志打印功能
  • 3.Hystrix断路器
    • 3.1一些重要的概念
    • 3.2Hystrix的案例
      • 3.2.1构建微服务提供者8001
      • 3.2.2JMeter高并发测试(引出降级容错解决)
      • 3.2.3解决高并发问题之服务降级
      • 3.2.4解决高并发问题之服务熔断
    • 3.3Hystrix图形化Dashboard构建与实战
  • 4.Gateway新一代网关
    • 4.1核心概念简介
    • 4.2搭建一个Gateway
    • 4.3第二种配置路由的方式
    • 4.4配置动态路由
    • 4.5Gateway的常用Predicate
    • 4.6Gateway的Filter

这篇笔记承接之前的服务注册与调用篇
https://blog.csdn.net/qq_28356977/article/details/126454940?spm=1001.2014.3001.5501

1. Ribbon负载均衡服务调用

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

我们先把环境恢复到之前Eureka的时候,服务器是7001和7002,支付微服务提供者是8001与8002,80是微服务的消费者。

先来简单地回顾一下环境
在这里插入图片描述

1.1Ribbon的简介

注:ribbon已经进入维护状态,未来可能会由spring cloud Loadbalancer替代

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

b)能干嘛

一句话:负载均衡+RestTemplate调用

ribbon与nginx的区别:
nginx是多个终端请求微服务,ngnix把请求分散到对应的服务器。ribbon是把服务器列表获取,本地随机选择服务器做均衡

1.2ribbon负载均衡的调用和RestTemplate的二次理解

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

在这里插入图片描述

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

(1)pom文件

我们之前,使用在RestTemplate配置类上的注解@LoadBalanced时,已经实现了轮询,但是我们并没有引入ribbon相关的依赖,这是为什么呢?原来新版的Eureka已经自带了ribbon。

我们点开依赖,发现Eureka已经自带了

在这里插入图片描述
如果不放心,也可以自己引入(但没必要)

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

(2)二说RestTemplate的使用

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

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

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public  CommonResult<Payment> getPaymentById2(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);

        if (entity.getStatusCode().is2xxSuccessful()){
            //2xx在http状态码里是成功意思
            log.info(entity.getStatusCode()+"\t"+"entity中可以获取更详细的信息");
            return entity.getBody();
        }else {
            return new CommonResult<>(444,"操作失败!");
        }
    }

b)postForObject/postForEntity

前面的get开头的方法,我们的80消费者转发给微服务提供者后,会被微服务提供者的GetMapping标注的方法处理,而用post开头的方法,就会被微服务提供者被PostMapping标注的方法处理

参数一:这个参数是请求的url路径
参数二:请求的body 这个参数需要再controller类用 @RequestBody 注解接收
参数三:接收响应体的类型
在这里插入图片描述

1.3Ribbon核心组件IRule(负载规则)

IRule:根据特定算法中从服务列表中选取一个要访问的服务,默认是轮询

在这里插入图片描述
在这里插入图片描述
a)如何替换负载规则
注意细节,自定义配置类不能放在@ComponentScan(这个在springboot中学过,@SpringBootApplication是一个注解)所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

1)创建一个package
在这里插入图片描述

2)上面包下新建MySelfRule规则类

@Configuration
public class MySelfRule {
    @Bean
    public IRule myRule()
    {
        return new RandomRule();//定义为随机
    }
}

3)主启动类添加@RibbonClient
在这里插入图片描述
4)测试
发现已经从轮询变成了随机

1.4负载均衡算法的原理(以轮询为例)

刚刚我们已经学习了如何替换负载规则,那么接下来我们就要学习原理了

a)原理

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

List 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 对应下标位置为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
如此类推…

2.OpenFeign服务接口调用(用在消费端80)

官网:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
源码:https://github.com/spring-cloud/spring-cloud-openfeign

2.1OpenFeign简介

Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

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

b)OpenFeign与Feign
在这里插入图片描述

2.2OpenFeign的使用步骤

接下来我们要从之前的ribbon+restTemplate转换位openfeign了

1、微服务调用接口+@FeignClient

2、新建模块cloud-consumer-feign-order80

3、改pom

    <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>
            <exclusions>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey.contribs</groupId>
                    <artifactId>jersey-apache-client4</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.javalearn.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>

从依赖中也可以看出来openfeign包含了ribbon
在这里插入图片描述
4、写yml

server:
  port: 80

eureka:
  client:
    # 这个客户端我们不注册金Eureka
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

5、主启动,注意加@EnableFeignClients
在这里插入图片描述
6、业务类,注意写@FeignClient注解
回顾一下我们微服务提供者的两个方法,这里以getById为例
在这里插入图片描述

@Component
//作为feign的一个接口
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")//这里写调用微服务对外暴露的名称
public interface PaymentFeignService {
    @GetMapping("/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id);
}

这里可能有同学会有疑问,为什么service层会有GetMapping注解呢?
我们之前是使用ribbon+testTemplate来实现微服务的调用,我们在service上加的注解,其实就是代替了之前的ribbon+testTemplate,微服务调用的顺序:80的controller->80的service->8001的controller->8001的service
在这里插入图片描述

7、controller

@RestController
@Slf4j
public class OrderFeignController {
    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }
}

8、测试
启动顺序:7001->7002->8001->8002->80
经过测试,我们发现openfeign自带负载均衡的功能。

2.3OpenFeign的超时控制

a)在8001中故意写暂停程序

    @GetMapping("/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return serverPort;
    }

b)在80的service中添加超时方法
在这里插入图片描述
c)在80的controller中添加一个方法

    @GetMapping("/consumer/payment/feign/timeout")
    public String paymentFeignTimeout(){
        //客户端一般默认等待一秒钟,但支付中我们已经设定了三秒钟
        return paymentFeignService.paymentFeignTimeout();
    }

d)测试
启动顺序:7001->7002->8001->80

OpenFeign默认等待1秒钟,超过后报错.

在这里插入图片描述

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,yml文件中开启配置,feign自带ribbon,所以超时也由ribbon控制(新版本已经不是这个了,不过大体思路都是一样的,可以去文档找)

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

然后重启80,读取成功了

2.4OpenFeign日志打印功能

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

a)日志级别
在这里插入图片描述
b)写一个配置类

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

c)在yml中配置

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

d)测试
随便运行一个请求,发现控制台已经显示了我们要的日志
在这里插入图片描述

3.Hystrix断路器

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

3.1一些重要的概念

(1)目前分布式所面对的问题-服务雪崩

服务雪崩: 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

(2)Hystrix是什么
Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

(3)Hystrix能干嘛?
在这里插入图片描述

(4)Hystrix的停更
在这里插入图片描述
(5)服务降级(fallback)
在这里插入图片描述

(6)服务熔断(break)
在这里插入图片描述

(7)服务限流(flowlimit)
在这里插入图片描述

3.2Hystrix的案例

首先先搞定服务注册中心,把7001恢复成单机版

3.2.1构建微服务提供者8001

我们先新建一个正常的微服务,后面就以他为根基,从正确->错误->降级熔断->恢复

1、新建module:cloud-provider-hystrix-payment8001

2、改pom
新的内容主要是Hystrix的依赖,其他的和我们之前的很相似

    <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>
            <exclusions>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey.contribs</groupId>
                    <artifactId>jersey-apache-client4</artifactId>
                </exclusion>
            </exclusions>
        </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.javalearn.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>

3、写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 #单机版,这里写你的Eureka地址

4、主启动
没什么好说的,和上一篇笔记注意记上这个注解@EnableEurekaClient

5、业务类
service

public interface PaymentService {
    //正常访问
    public String paymentInfo_OK (Integer id);
    //超时访问
    public String paymentInfo_Time0ut(Integer id);
}

他的实现类

@Service
public class PaymentServiceImpl implements PaymentService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+"  paymentInfo_OK:id:  "+id+"\t"+"haha~";
    }

    @Override
    public String paymentInfo_Time0ut(Integer id) {
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
    }
}

6、controller

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

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

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

7、测试
启动顺序:7001->8001
经过测试,带Hystrix熔断框架的8001中的两个方法运行正常

3.2.2JMeter高并发测试(引出降级容错解决)

我们接下来用JMeter发送两万个请求给TimeOut

在这里插入图片描述

测试结果:本来应该没有延迟的ok方法也开始转圈了
原因:tomcat的默认的工作线程数被打满 了,没有多余的线程来分解压力和处理。

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

那么接下来我们再加入80消费者
a)创建module:cloud-consumer-feign-hystrix-order80

b)改pom

    <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>
            <exclusions>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey.contribs</groupId>
                    <artifactId>jersey-apache-client4</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.javalearn.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>

c)写yml

server:
  port: 80

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

d)主启动
注意要加上@EnableFeignClients注解

e)业务类
首先是service,具体写法和上面第二章的openfeign的消费者写法一样,我们的这个消费者,要调用我们刚刚写的8001的微服务。

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

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

然后是controller

@RestController
@Slf4j
public class PaymentHystrixController {
    @Resource
    private PayMentHystrixService payMentHystrixService;

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

    @GetMapping("/comsumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = payMentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
}

f)测试
我们也是先对8001进行高并发测试,然后再使用80消费者去访问,最后发现80消费者访问本来没有延迟的方法,不是转圈圈就是报超时错误

那么接下来就是我们针对这一高并发测试,所要解决的问题了
1、 超时导致服务器变慢(转圈):超时不再等待
2、出错(宕机或程序运行出错):出错要有兜底
解决:
1、对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
2、对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
3、对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

3.2.3解决高并发问题之服务降级

在这里我们将使用一个新的注解@HystrixCommand去配置

(1)先从8001自身寻找问题

我们的8001中,不是有一个需要等待三秒的方法吗?
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback

那么接下来,我们规定三秒以内是正常的业务逻辑,超过三秒,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
在这里插入图片描述
需要在主启动类上加上@EnableCircuitBreaker注解
在这里插入图片描述

经过测试,发现达到了我们预期的效果,只要是服务不可用了,都会去调用我们的兜底方法

(2)在消费者80端中也做修改

接下来我们假设,我们的8001设置的是5秒超时,但是我们的80设置的是2秒超时,注意Hystrix虽然即可以用在消费端,也可以用在客户端,但是一般都是用在消费端的。

首先先修改yml

feign:
  hystrix:
    enabled: true

在主启动类上加上注释@EnableHystrix
在这里插入图片描述
然后修改80的controller,其实方法和之前的8001基本一致
在这里插入图片描述

如果说我们想让这个程序正常运行,我们只需要在80中的设置的时间小于8001中的等待时间即可,同时不要忘了在80中设置Hystrix的默认超时时间

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: Hystrix的默认超时时间

(3)全局降级DefaultProperties

我们之前虽然已经完成了服务降级,但是我们业务逻辑的方法和兜底的方法放在一起,代码就会非常冗余,所以接下来我们就需要使用全局的fallback来解决代码膨胀的功能
使用注解:@DefaultProperties(defaultFallback = “”), 通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

我们以80来演示,在80的controller中加上一个全局兜底的方法

    public String payment_Global_FallbackMethod()
    {
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
    }

然后再类上加上@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)
在这里插入图片描述
说明:
在这里插入图片描述
(4)通配服务降级

上面我们解决了代码冗余的问题,现在还有一个问题,就是我们的兜底方法和业务逻辑混在一块导致代码混乱。

回顾一下我们的80,是使用feign接口去调用8001微服务的方法,那么我们可以为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦,下图是我们80 的feign接口
在这里插入图片描述
根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

@Component //必须加 //必须加 //必须加
public class PaymentFallbackService implements PayMentHystrixService{
    @Override
    public String paymentInfo_OK(Integer id) {
        return "----payment fall back-method_ok";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "----payment fall back-method_timeout";
    }
}

注意配置文件中要加上

feign:
  hystrix:
    enabled: true

最后一步,我们把我们的这个实现类,配置到我们的接口中
在这里插入图片描述
经过测试,我们达到了目的

3.2.4解决高并发问题之服务熔断

服务的降级->进而熔断->恢复调用链路
注意降级和熔断的区别:调用失败会触发降级,而降级会调用fallback方法,但无论如何降级的流程一定会调用正常方法再调用fallbcak方法,假如单位时间内调用失败次数过多,也就是降级次数过多,则触发熔断,熔断以后就会跳过正常的方法直接执行fallback方法,所谓熔断后服务不可用就是因为跳过了正常方法直接执行fallback方法
那么接下来就来实操了,同样也是使用@HystrixCommand这个注解

(1)修改8001,在service的实现类中加上

    //=========服务熔断
    @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 不能负数");
        }
        //这个是导入的依赖hutool中的,就是生成一个uuid并且toString
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }
    //兜底方法
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
    {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
    }

这里顺带提一句,想要查看@HystrixCommand注解中有多少参数,可以去HystrixCommandProperties这个类或者去官网查看

(2)修改8001的controller

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

(3)测试
启动顺序:7001->8001

还记得我们的方法吗?参数为正数成功,为负数,那么就会触发兜底

当在浏览器中一直进行复参数的浏览,错误率达到60%后,就会触发熔断,那么我们再输入正参数,那么也会调用兜底方法,当达到我们的窗口期时间后,才会慢慢的尝试恢复链路,至此,服务熔断结束

接下来做一个总结:断路器的工作流程
在这里插入图片描述
下面是官网给出的Hystrix的运行流程,看着吓人,其实很容易懂
在这里插入图片描述

3.3Hystrix图形化Dashboard构建与实战

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

(1)新建module:cloud-consumer-hystrix-dashboard9001

(2)改pom

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

(3)写yml

server:
  port: 9001

(4)主启动+新注解@EnableHystrixDashboard
在这里插入图片描述

(5)所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
这个一般我们都是和web的starter一起引入的

   <!-- actuator监控信息完善 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

(6)启动9001后续将监控微服务8001
浏览器输入:http://localhost:9001/hystrix,如果有下图的图标,那么说明你搭建成功了!
在这里插入图片描述
(7)实战第一步:修改8001
注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径,下面的配置,直接粘贴到8001的配置类即可

/**
 *此配置是为了服务监控而配置,与服务容错本身无关,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;
}

并且注意主启动类要加上注解:@EnableCircuitBreaker
(2)实战第二步:启动8001,在9001的web界面中监控8001
填写监控地址:http://localhost:8001/hystrix.stream
在这里插入图片描述
我们先发几个正确的请求
在这里插入图片描述

再发几个异常的请求,使断路器打开
在这里插入图片描述
下面说一下这个图怎么看
在这里插入图片描述
在这里插入图片描述

4.Gateway新一代网关

官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

4.1核心概念简介

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

用一句话概括:SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
在这里插入图片描述
(2)微服务架构中网关在哪?
在这里插入图片描述
(3)为什么使用Gateway
1、Gateway有以下特性
基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
动态路由:能够匹配任何请求属性;
可以对路由指定 Predicate(断言)和 Filter(过滤器);
集成Hystrix的断路器功能;
集成 Spring Cloud 服务发现功能;
易于编写的 Predicate(断言)和 Filter(过滤器);
请求限流功能;
支持路径重写。

2、neflix不太靠谱,zuul2.0一直跳票,迟迟不发布

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

(5)三大个核心概念
在这里插入图片描述

(6)工作流程

核心逻辑:路由转发+执行过滤器链

在这里插入图片描述
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

4.2搭建一个Gateway

(1)新建一个module:cloud-gateway-gateway9527

(2)改pom

    <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>
            <exclusions>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey</groupId>
                    <artifactId>jersey-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jersey.contribs</groupId>
                    <artifactId>jersey-apache-client4</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.javalearn.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>

(3)写yml

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

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

(4)主启动
加上注解:@EnableEurekaClient使其成为一个客户端

(5)映射到微服务提供者8001
8001中有两个方法,就以这两个方法为例
在这里插入图片描述
在9527的yml中配置,这段配置在spring:下

  cloud:
    gateway:
      #负数,代表多个路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001          #匹配后提供服务的路由地址(真实路径)
          predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由(加在真实路径后的)

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

(6)测试
启动9527,7001,8001
注意,9527是网关,不需要web,不然会导致jar包冲突
启动后,我们发现我们的网关和微服务都已经注册进了Eureka
在这里插入图片描述
我们之前访问8001get方法,都是用http://localhost:8001/payment/get/31这个网址
现在用http://localhost:9527/payment/get/31这个网址也可以访问了,这就是网关的初步体验了,我们淡化了我们的真实地址

匹配的规则:
在这里插入图片描述

4.3第二种配置路由的方式

第一种配置方式,就是在yml中配置,可以参考上文的方式,接下来是第二种代码中注入RouteLocator的Bean

接下来,我们就自己写一个,需求:通过网关9527访问到外网的百度新闻网址

//需求:当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
@Configuration
public class GatewayConfig {
    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        //第一个参数就是我们的id,可以自拟
        routes.route("path_route_javalearn",r->r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }
}

启动9527,浏览器中输入http://localhost:9527/guonei
在这里插入图片描述

4.4配置动态路由

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

(1)启动7001,两个服务提供者8001与8002

(2)修改9527的yml,实现动态路由
在这里插入图片描述

(3)测试
启动9527

浏览器输入:http://localhost:9527/payment/lb
经过测试,动态路由成功,且实现了负载均衡,即8001和8002轮询应答请求。

4.5Gateway的常用Predicate

我们每次启动9527的时候,都能在控制台看到一些下图的内容。
在这里插入图片描述
a)Route Predicate Factories这个是什么东东?
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
在官网文档中也可以查到
在这里插入图片描述
b)常用的常用的Route Predicate
1、After Route Predicate
在这里插入图片描述

要获取这个时间也很简单,只需要运行下面的代码

    public static void main(String[] args)
    {
        ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
        System.out.println(zbj);
//        ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
//        System.out.println(zny);
    }

同样的,before,between也是一样的用法,这里只以after为例

2、Cookie Route Predicate
Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
在这里插入图片描述

3、其他
用法都大同小异
在这里插入图片描述

4.6Gateway的Filter

Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

a)概念
过滤器生命周期有两种:在业务逻辑之前和在业务逻辑之后
类型也分为两种:单一过滤器(GatewayFilter)和全局过滤器(GlobalFilter)
官网位置:共有31个,就不一一解释了
在这里插入图片描述
一个小例子:
在这里插入图片描述
b)自定义一个过滤器

要实现两个接口:GlobalFilter,Ordered

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("***************come in MyLogGateWayFilter:"+new Date());
        //获取第一个请求参数uname
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname==null){
            //为空,结束
            log.info("用户名为null,非法用户");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        //不为空,放行到后面的filter过滤链
        return chain.filter(exchange);
    }

    //加在过滤器的优先级
    @Override
    public int getOrder() {
        return 0;
    }
}

测试后,我们的请求只要不带有uname这个参数,就报406
在这里插入图片描述

相关文章:

  • 常见的设计模式
  • 【我不熟悉的javascript】02. 使用token和refreshToken的管理用户登录状态
  • 备战秋招涵盖二十九大技术栈Java面试最新八股文来袭
  • tensorflow张量运算
  • 论文阅读笔记StyTr2: Image Style Transfer with Transformers
  • mybatis面试题及回答
  • 奔腾电力面试题
  • 【leetcode】905. 按奇偶排序数组 (简单)
  • Java--MybatisPlus入门;与Mybatis区别;简单使用(一)
  • #ubuntu# #git# repository git config --global --add safe.directory
  • 【数据结构】——二叉树oj题详解
  • 性能测试:工具篇:Jmeter实时可视化平台搭建
  • 你该用什么的美剧学英语?
  • 面试算法 二叉树的遍历,方法 :迭代 ,前序遍历: 中序遍历: 后序遍历: 层序遍历
  • Matlab常用函数(control)
  • [译] 怎样写一个基础的编译器
  • Android Studio:GIT提交项目到远程仓库
  • CSS盒模型深入
  • Gradle 5.0 正式版发布
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JS+CSS实现数字滚动
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • js写一个简单的选项卡
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • python 装饰器(一)
  • RxJS 实现摩斯密码(Morse) 【内附脑图】
  • ViewService——一种保证客户端与服务端同步的方法
  • 包装类对象
  • 简单实现一个textarea自适应高度
  • 可能是历史上最全的CC0版权可以免费商用的图片网站
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 那些被忽略的 JavaScript 数组方法细节
  • 前端攻城师
  • 前端路由实现-history
  • 让你的分享飞起来——极光推出社会化分享组件
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • 第二十章:异步和文件I/O.(二十三)
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • #laravel 通过手动安装依赖PHPExcel#
  • (定时器/计数器)中断系统(详解与使用)
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (生成器)yield与(迭代器)generator
  • (转)visual stdio 书签功能介绍
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .NET Framework杂记
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .Net的C#语言取月份数值对应的MonthName值
  • .Net组件程序设计之线程、并发管理(一)
  • @ModelAttribute注解使用