SpringCloudAlibaba

文章目录


Spring Cloud Netflix项目进入维护模式,意味着SpringCloud Netflix 将不再开发新的组件。 Spring Cloud Netflix博客

诞生:

​ 2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。

​ Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
  • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
  • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
  • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
    在这里插入图片描述

官网地址:

版本对应:

  • 2020.0 分支对应的是 Spring Cloud 2020,最低支持 JDK 1.8。
  • master 分支对应的是 Spring Cloud Hoxton,最低支持 JDK 1.8。
  • greenwich 分支对应的是 Spring Cloud Greenwich,最低支持 JDK 1.8。
  • finchley 分支对应的是 Spring Cloud Finchley,最低支持 JDK 1.8。
  • 1.x 分支对应的是 Spring Cloud Edgware,最低支持 JDK 1.7。

一、Nacos [注册中心/配置中心]

Nacos 是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos: Dynamic Naming and Configuration Service,Nacos就是注册中心 + 配置中心的组合。等价于:Nacos = Eureka+Config +Bus

请添加图片描述

1.1、安装并运行Nacos


1.1.1、Docker部署Nacos

这里直接在服务器上Docker部署的Nacos, 并开启相应端口8848

  1. 拉取 nacos 镜像

    docker pull nacos/nacos-server
    
  2. 启动 nacos

    docker run -d --name nacos -p 8848:8848 -e PREFER_HOST_MODE=hostname -e MODE=standalone nacos/nacos-server
    

请添加图片描述

至此,我们已经可以使用nacos服务,UI地址:http://:8848/nacos 账号:nacos 密码:nacos

请添加图片描述

1.1.2、本机部署

本机部署 , 本机存放路径: /Users/hgw/Documents/Software/nacos/bin

  1. 下载nacos的压缩包,并解压

    https://github.com/alibaba/nacos/releases
    
  2. 进入解压目录的bin目录下,打开终端,输入命令启动,输出nacos is starting with standalone即为成功 /Users/hgw/Documents/Software/nacos/bin

    sh startup.sh -m standalone
    

在这里插入图片描述

  1. 进入可视化页面,账号密码都是nacos,进行登录即可,nacos的端口为8848 (账号:nacos 密码:nacos)

    http://localhost:8848/nacos/#/login
    http://127.0.0.1:8848/nacos/#/login
    
  2. 关闭nacos

    sh shutdown.sh
    
  3. 但发现关闭后,仍然能在可视化页面连接nacos,所以需要杀死8848端口的进程

    # 查询8848端口的进程,获取到进程id,例如是45025
    lsof -i:8848
    # 杀死45025进程
    kill -9 45025
    

注意:mac环境 mac 启动nacos失败 日志报“nohup: /Library/Internet: No such file or directory”

1.2、Nacos [作为注册中心]


1.2.1、Nacos之服务提供者注册

这里方便以下做演示,我们新建一个空项目 SpringCloudAlibaba。并创建一个微服务 cloudalibaba-provider-payment9001,以下将 cloudalibaba-provider-payment9001 微服务加入注册中心中去。

第一步、依赖管理

在pom.xml中加入

# 下面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理
<dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-alibaba-dependencies</artifactId>
             <version>2.1.0.RELEASE</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
     </dependencies>
</dependencyManagement>

第二步、修改pom.xml 文件,引入 Nacos Discovery Starter。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第三步、配置 Nacos Server 地址和微服务名称

server:
  port: 9001
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # 配置Nacos地址

第四步、使用 @EnableDiscoveryClient 注解开启服务注册与发现功能

在这里插入图片描述

启动微服务测试一下 :

在这里插入图片描述

总结:

  1. 导入依赖
  2. 在各微服务中的application.yml 配置文件中配置 Nacos Server 地址微服务名称
  3. 在各微服务的主类加上 @EnableDiscoveryClient 注解开启服务注册与发现功能

我们编写一个Controller 方便一会儿测试:

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
        return "nacos registry,serverPort: " + serverPort + "\t id:"+ id;
    }
}

按照以上步骤,我们再创建一个cloudalibaba-provider-payment9002微服务,并加入到Nacos注册中心去。

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

1.2.2、Nacos之服务消费者注册 和 负载均衡

创建我们的服务消费者(cloudalibaba-consumer-nacos-order83)并注册进Nacos

第一步、依赖管理

在pom.xml中加入

# 下面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理
<dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-alibaba-dependencies</artifactId>
             <version>2.1.0.RELEASE</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
     </dependencies>
</dependencyManagement>

第二步、修改pom.xml 文件,引入 Nacos Discovery Starter。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第三步、配置 Nacos Server 地址和微服务名称

server:
  port: 83
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # 配置Nacos地址

# 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
server-url:
  nacos-user-service: http://nacos-payment-provider

第四步、使用 @EnableDiscoveryClient 注解开启服务注册与发现功能

@EnableDiscoveryClient
@SpringBootApplication
public class CloudalibabaConsumerNacosOrder83Application {

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

}

第五步、编写配置类 ApplicationContextBean

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

第六步、编写业务类 OrderNacosController

@RestController
@Slf4j
public class OrderNacosController {
    @Resource
    private RestTemplate restTemplate;

    @Value("${server-url.nacos-user-service}")
    private String serverURL;

    @GetMapping("/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id) {
        return restTemplate.getForObject(serverURL+"/payment/nacos/"+id, String.class);
    }
}

nacos整合了 ribbon

在这里插入图片描述

测试:当我们访问 http://localhost:83/consumer/payment/nacos/1

在这里插入图片描述

1.2.3、Nacos 支持AP和CP模式的切换

C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。

何时选择使用何种模式?

一般来说,

  • 如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud 和 Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
  • 如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。 CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP' 


![请添加图片描述](https://img-blog.csdnimg.cn/869d9360bf9a4829ac34ac432a8c01e5.png)

1.3、Nacos [作为配置中心]


1.3.1、Nacos作为配置中心相关概念

我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties 等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。

如何使用 Nacos 作为配置中心统一管理配置

  1. 引入配置中心依赖
  2. resources 目录下创建一个 bootstrap.properties文件并配置
  3. 需要给配置中心默认添加一个 数据集 (Data Id) 默认规则: 应用名.properties
  4. 应用名.properties 添加任何配置
    • 动态获取配置, 给控制层类加上 @RefreshScope 注解, (动态获取并刷新配置)
    • 属性加上 @Value("${配置项的名}")注解, (获取到配置)

同时加载多个配置集

  1. 微服务任何配置信息, 任何配置文件都可以放在配置中心中
  2. 只需要在 bootstrap.properties 说明加载配置中心中哪些配置文件即可
  3. @Value@ConfigurationProperties … 以前 SpringBoot 任何方法从配置文件中获取值, 都能使用.

配置中心有的优先使用配置中心的

1.3.2、配置文件命名规则

说明:之所以需要配置 spring.application.name ,是因为它是构成 Nacos 配置管理 dataId字段的一部分。

在 Nacos Spring Cloud 中,dataId 的完整格式如下:

${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。

最后公式:

${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

1.3.3、Nacos作为配置中心-基础配置

创建一个 cloudalibaba-config-nacos-client3377 微服务

第一步、引入依赖

<!--Nacos控制中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--Nacos注册中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

第二步、修改配置文件,在 bootstrap.properties 中配置 Nacos server 的地址和应用名

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取, 拉取配置之后,才能保证项目的正常启动。

springboot中配置文件的加载是存在优先级顺序的, bootstrap优先级高于application

  1. bootstrap.yaml 文件内容
# nacos 配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848   #Nacos服务注册中心地址
      config:
        server-addr: 127.0.0.1:8848   #Nacos配置注册中心地址
        file-extension: yaml          #指定yaml格式的配置
  1. application.yaml 文件内容
spring:
  profiles:
    active: dev #表示开发环境

第三步、在主启动类加上 @EnableDiscoveryClient 注解

package com.hgw.configClient3377;

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

@EnableDiscoveryClient
@SpringBootApplication
public class CloudalibabaConfigNacosClient3377Application {

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

}

第四步、在Nacos中添加配置信息

根据 ${prefix}-${spring.profiles.active}.${file-extension}公式,创建一个名为:

nacos-config-client-dev.yaml的配置文件

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

第五步、业务类

@RefreshScope : 是SpringCloud 原生注解,实现配置自动更新

@RestController
@RefreshScope   // 支持Nacos的动态刷新功能
public class ConfigClientController {

    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
        return configInfo;
    }
}

第六步、测试

在这里插入图片描述

自带动态刷新

修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

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


1.3.4、Nacos作为配置中心-分类配置

问题1:

实际开发中,通常一个系统会准备

  • dev开发环境
  • test测试环境
  • prod生产环境。

如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2:

  • 一个大型分布式微服务系统会有很多微服务子项目,

  • 每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…

那怎么对这些微服务配置进行管理呢?

1.3.4.1、配置参数相关概念

Namespace+Group+Data ID三者关系?为什么这么设计?

  1. 是什么

    • 类似Java里面的package名和类名
    • 最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
  2. 三者情况

    • 默认情况:

      Namespace=publicGroup=DEFAULT_GROUP, 默认ClusterDEFAULT

在这里插入图片描述

  • Nacos默认的命名空间是public,Namespace主要用来实现隔离。
    比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

  • Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

  • Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
    比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
    这时就可以给杭州机房的Service微服务起一个集群名称(HZ), 给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

  • 最后是==Instance==,就是微服务的实例。

命名空间 :

用作配置隔离 . (一般每个微服务一个命名空间)

​ 不同的命名空间下, 可以存在相同的 GroupData ID 的配置. Namespace 的常用场景之一是不同环境的配置区分隔离, 例如 开发测试环境和生产环境的资源 (如配置、服务)隔离等.

默认: public(保留空间); 默认新增的所有配置都在 public 空间

  • 可以在 开发、测试、生产 利用命名空间来做环境隔离
  • 每一个微服务之间互相隔离配置, 每一个微服务都创建自己的命名空间, 只加载自己命名空间下的所有配置
  1. 创建命名空间

在这里插入图片描述

  1. 在 dev 命名空间下创建gulimall-coupon.properties 配置文件

  2. 修改本地微服务的 bootstrap.properties 配置

在这里插入图片描述

也可以为每个微服务配置一个命名空间,微服务互相隔离

配置分组 :

默认所有的配置集都属于 DEFAULT_GROUP . 自己可以创建分组, 比如双十一、618、双十二

  1. 新建配置指定分组

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

  1. 修改配置文件

请添加图片描述

配置集ID :

类似于配置文件名,即Data ID

Data Id 通常用于组织 划分系统的配置集. 一个系统或者应用可以包含多个配置集, 每个配置集都可以被一个有意义的名称标识. Data ID 通常采用类 Java 包 (如 com.hgw.tc.refund.log.level) 的命名规则保证全局唯一性. 此命名非强制 .

配置集 :

一组相关或不相关的配置项的集合.

​ 在系统中, 一个配置文件通常就是一个配置集, 包含了系统各个方面的配置. 例如, 一个配置集可以包含了数据源、线程池、日志级别等配置项


1.3.4.2、Nacos之DataID配置

指定 spring.profile.active配置文件的DataID 来使不同环境下读取不同的配置

测试:默认空间+默认分组+新建dev和test两个DataID

**第一步、**在同一个空间同一个分组创建两个DataID,分别是 dev和test

  1. nacos-config-client-dev.yaml

在这里插入图片描述

  1. nacos-config-client-test.yaml
    在这里插入图片描述

第二步、通过spring.profile.active属性就能进行多环境下配置文件的读取

  • spring.profile.active = dev
    请添加图片描述

  • spring.profile.active = test
    请添加图片描述

1.3.4.3、Nacos之Group配置

通过 Group 实现环境区分

第一步、创建两个分组并在分组内新建配置文件DataID

  • DEV_GROUP
    在这里插入图片描述

  • TEST_GROUP
    在这里插入图片描述

总览:

请添加图片描述

第二步、修改配置文件

在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
请添加图片描述
请添加图片描述

1.3.4.4、Nacos之Namespace(命名空间)配置

模拟:不同命名空间读取不同的组内容

第一步、新建HZ/BJ的Namespace

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

回到服务管理-服务列表查看

在这里插入图片描述

第二步、在 HZ、BJ命名空间中创建不同分组和配置文件DataID

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

第三步、修改配置文件

  1. 访问 HZ命名空间下 DEV_GROUP 组的下 DataID 为 dev 的配置文件内容

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

  1. 访问 BJ命名空间下 TST_GROUP 组的下 DataID 为 test 的配置文件内容
    在这里插入图片描述



1.4、Nacos持久化配置


Nacos默认自带的是嵌入式数据库derby

derby到mysql切换配置步骤

1、在mysql中执行 /nacos/conf/nacos-mysql.sql 脚本

请添加图片描述

2、修改配置文件

\nacos\conf目录下找到application.properties

[root@hgwtencent conf]# vim application.properties

请添加图片描述

spring.datasource.platform=mysql 
db.num=1 
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true 
db.user=root 
db.password=hgw6721224 

3、启动服务,创建一个配置文件

请添加图片描述





二、Gateway [网关]

SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。

Gateway 是在Spring生态系统之上构建的API网关服务,基于 Spring5、SpringBoot 和 Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等 。
在这里插入图片描述

2.1、概述


2.1.1、网关的定义 :

网关的角色是作为一个 API 架构,用来保护、增强和控制对于 API 服务的访问

API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。因此,隐藏在 API 网关后面的业务系统就可以专注于创建和管理服务,而不用去处理这些策略性的基础设施。

网关的职责 :

  • 请求接入 : 作为所有API接口服务请求的接入点
  • 业务聚合 : 作为所有后端业务服务的聚合点
  • 中介策略 : 实现安全、验证、路由、过滤、控制等策略
  • 统一管理 : 对所有API服务和策略进行统一管理
2.1.2、Gateway 概述

SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

​ 发送请求需要知道商品服务的地址,如果商品服务器有100服务器,1号掉线后,还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上线还是下线。

​ 请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。所以我们使用spring cloud的gateway组件做网关功能。

网关是请求浏览的入口,常用功能包括路由转发,权限校验,限流控制等

2.1.3、Gateway 能干嘛?
  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控
2.1.4、微服务架构中网关在哪里?

在这里插入图片描述

2.1.5、有Zuul了怎么又出来了gateway

  1. neflix不太靠谱,zuul2.0一直跳票,迟迟不发布
  2. SpringCloud Gateway具有如下特性
  3. SpringCloud Gateway 与 Zuul的区别

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

一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。

而且很多功能Zuul都没有用起来也非常的简单便捷。

Gateway是基于 异步非阻塞模型上 进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x, 但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?

多方面综合考虑Gateway是很理想的网关选择。

2、SpringCloud Gateway具有如下特性:

基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建

  • 动态路由:能够匹配任何请求属性;

  • 可以对路由指定 Predicate(断言)和 Filter(过滤器);

  • 集成Hystrix的断路器功能;

  • 集成 Spring Cloud 服务发现功能;

  • 易于编写的 Predicate(断言)和 Filter(过滤器);

  • 请求限流功能;

  • 支持路径重写。

3、Spring Cloud Gateway 与 Zuul的区别

在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:

  1. Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway

  2. Zuul 1.x 基于Servlet 2. 5使用阻塞架构 它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。

  3. Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍。

  4. Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API。

  5. Spring Cloud Gateway 还支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验



2.2、三大核心概念


  • Route (路由)
    这是网关的基本构件块. 它由一个ID, 一个目标 URI, 一组断言和一组过滤器定义. 如果断言为真, 路由匹配

  • Predicate (断言)

    输入类型是一个 ServerWebExchange . 我们可以使用它来匹配来自 HTTP 请求的任何内容, 例如 headers 或 参数.

  • Filter (过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
    Gateway 中的 Filter 分为两种类型的Filter, 分别是 Gateway Filter 和 Global Filter. 过滤器Filter 将会对请求和响应进行修改处理 .



2.3、Gateway 工作流程

  1. 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

  2. Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

  3. 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

    • Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

    • 在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。



2.4、入门配置


2.4.1、创建测试模块

第一步、创建模块 并调整版本一致

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

创建完项目进行springboot降级 . 并加入到主项目

请添加图片描述

第二步、引入依赖

<!--SpringCloud-nacos 注册中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

<!--SpringCloud-nacos 配置中心-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2.1.0.RELEASE</version>
</dependency>

第三步、开启 服务注册发现

/*
* 1.开启服务注册发现
* */
@EnableDiscoveryClient
@SpringBootApplication
public class GuilmallGatewayApplication {

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

}

第四步、配置nacos注册中心地址 applicaion.properties

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88

第五步、在 nacos 中为gateway模块创建名称空间 . 并创建配置

请添加图片描述

spring:
    application:
        name: gulimall-gateway

第六步、创建 bootstrap.properties 并填写配置中心地址

spring.application.name=gulimall-gateway
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=064c2e8d-15a5-499a-83c1-1691ccb740d3

测试: http://localhost:88/
请添加图片描述



2.4.2、配置API网关

测试:

1、引入依赖

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

2、配置API网关

  • 官网教程 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2P3uRVRr-1652607118361)(SpringCloudAlibaba.assets/image-20220309213615119.png)]

  • 在项目里创建application.yml , 并配置

    spring:
      cloud:
        gateway:
          routes:
            - id: query_route
              uri: https://www.baidu.com
              predicates:
                - Query=url,baidu
    
            - id: test_route
              uri: https://www.qq.com
              predicates:
                - Query=url,qq
    

    run 起来, 成功啦!

在这里插入图片描述

2.5、Gateway网关路由有两种配置方式:


2.5.1、在配置文件yml中配置

见前面的步骤

在这里插入图片描述

2.5.2、代码中注入RouteLocator的Bean

配置了一个 idpath_route_atguigu 的路由规则,当访问地址 http://localhost:9527/guonei 时会自动转发到地址: http://news.baidu.com/guonei

package com.atguigu.springcloud.config;

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


@Configuration
public class GateWayConfig
{
  
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
    {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_atguigu",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}


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


默认情况下Gateway会根据注册中心注册的服务列表,

注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。

  • lb://serviceName 是springcloud gateway在微服务中自动为我们创建的负载均衡uri

如以下配置:

在这里插入图片描述

2.7、Predicate的使用


在这里插入图片描述

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

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。

说白了,Predicate就是为了实现一组匹配规则,

让请求过来找到对应的Route进行处理。

2.7.1、常用的Route Predicate

1、After Route Predicate

在这里插入图片描述

在这里插入图片描述

2、Before Route Predicate

在这里插入图片描述

3、Between Route Predicate

在这里插入图片描述

4、Cookie Route Predicate

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

在这里插入图片描述

5、Header Route Predicate

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

在这里插入图片描述

6、Host Route Predicate

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。

它通过参数中的主机地址作为匹配规则。

在这里插入图片描述

7、Method Route Predicate

在这里插入图片描述

8、Path Route Predicate

在这里插入图片描述

9、Query Route Predicate

在这里插入图片描述



2.8、Filter的使用

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。

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

  • 生命周期

    • pre
    • post
  • 种类

    • GatewayFilter
    • GlobalFilter

自定义过滤器

编写一个类实现 implements GlobalFilter,Ordered ,并加入Spring容器中。

package com.atguigu.springcloud.filter;

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

import java.util.Date;

/**
 * @auther zzyy
 * @create 2020-02-21 16:40
 */
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{

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

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

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

        return chain.filter(exchange);
    }

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

2.9、gateway和nginx网关的区别

网关是介于nignx以及业务应用之间的中间层,主要负责将请求路由到不同的微服务中以及对请求的合法性进行校验。

我们目前网关的选型是spring cloud gateway,位置在nignx之后,各个微服务应用之前。为何在已经有nignx的情况下,中间还需要一道负责负载均衡功能的网关呢?

  • 一个原因是spring cloud gateway天然集成了注册中心eureka,能实现应用的自动注册与发现,而nginx每增加一个服务应用都需要手动去设置配置文件。

  • 另一个原因是,在spring cloud gateway中我们可以很方便的进行功能的扩展,比如我们现在的用户登录权限校验,就是放在网关中实现的。

gateway和nginx网关的区别

网关可以看做系统与外界联通的入口,我们可以在网关进行处理一些非业务逻辑的逻辑,比如权限验证,监控,缓存,请求路由等等。

  • ==nginx==用户前端工程 之间的网关,对外网关

  • gateway前端工程后台服务器 之间的一个 对内网关

Nginx在其中扮演的角色是什么?

  • 反向代理
  • 负载均衡

SpringGateway在其中扮演的角色是什么?

  • 统一鉴权


三、Sentinel服务流控、熔断和降级

3.1、Sentinel概述

3.1.1、服务流控、熔断和降级

熔断

  • 当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,**进而熔断该节点微服务的调用,快速返回错误的响应信息。**检测到该节点微服务调用响应正常后恢复调用链路。

  • A服务调用 B服务的某个功能,由于网络不稳定问题,或者 B服务卡机,导致功能时间超长。如果这样的次数很多。我们就可以直接将 B服务段路了(A不再请求 B接口),凡是调用 B得直接返回降级数据,不必等待 B的超长执行。这样 B的故障问题,就不会级联影响到 A服务。

降级

  • 服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理,或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务
  • 整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行由策略的降级[停止服务,所有的调用直接返回降级数据]。以此缓解服务器资源的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户得到正确的对应。

限流

  • 对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力

熔断降级异同:

  • 相同点:
    1. 为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我
    2. 用户最终都是体验到某个功能不可用
  • 不同点:
    1. 熔断时被调用方故障,触发的系统主动规则
    2. 降级是基于全局的考虑,通知一些正常服务,释放资源

3.1.2、Sentinel 简介

简介:

​ 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。


3.2、安装 Sentinel 控制台

1、去官网下载项目里sentinel对应的版本的控制台官网下载

在这里插入图片描述

2、在路径下执行

$ java -jar sentinel-dashboard-1.7.0.jar --server.port=8833

在这里插入图片描述

3、访问http://localhost:8833/

  • 用户名:sentinel

    密码:sentinel

在这里插入图片描述

3.3、初始化演示工程


  1. 启动Nacos
  2. 启动Sentinel8080
  3. 创建一个服务 cloudalibaba-sentinel-service8401 进行演示

首先、按照之前创建一个 cloudalibaba-sentinel-service8401 微服务,并注入我们的Nacos注册中心

第一步、导入依赖

<!--Sentinel 服务熔断、降级、限流-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

第二步、配置Sentinel

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置 dashboard的地址
        dashboard: 127.0.0.1:8833
        # 默认 8719端口,加入被占用会自动从8719开始依次+1,直至找到未被占用的端口
        port: 8719

第三步、编写业务类FlowLimitController进行流控检测

package com.hgw.sentinelService8401.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Description: 测试类,用来进行流控检测
 */
@RestController
public class FlowLimitController {
    
    @GetMapping("/testA")
    public String testA() {
        return "---testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "---testB";
    }
}

第四步、测试

启动8401微服务后查看sentienl控制台,此时发现空空如也。

由于Sentinel采用的懒加载,执行一次访问即可:http://localhost:8401/testA

在这里插入图片描述

结论:sentinel8080正在监控微服务8401


3.4、流控规则


3.4.1、流控规则基本介绍
  • 资源名:唯一名称,默认请求路径。
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名称,默认default(不区分来源)
  • 阈值类型/单机阈值
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流。
    • 线程数:当调用该api的线程数达到阈值的时候,进行限流。
  • 是否集群:不需要集群
  • 流控模式
    • 直接:api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)[api级别的针对来源]
  • 流控效果
    • 快速失败:直接失败,抛异常
    • Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置QPS,否则无效

3.4.2、流控模式

  • 直接(默认)
  • 关联
  • 链路
3.4.2.1、 直接(默认)

直接->快速失败

添加以下流控,表示一秒钟内查询1次就是OK,若超过次数1,就直接快速失败,报默认错误

在这里插入图片描述

测试当我们在一分钟多次访问 http://localhost:8401/testA 时候:

在这里插入图片描述

3.4.2.2、关联

当关联的资源达到阈值时,就限流自己,
比如 当与A关联的资源B达到阀值后,就限流A自己,即B惹事,A挂了

1、配置A

设置效果:当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址, 当关联资源到阈值后限制配置好的资源名

在这里插入图片描述

2、postman模拟并发密集访问testB

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

3、大批量线程高并发访问B,导致A失效了

在这里插入图片描述

3.4.2.3、链路

链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值

需要注意sentinel流控模式中的关联类型和链路类型的区别:

  • 关联 ,假设A规则关联B,那么A资源是受限制的
  • 链路 ,假设A规则绑定了一个链路,关联B,那么受限制的是B

例如有两条请求链路:

/testA      /common
/testB      /common

如果只希望统计从/testA进入到/common的请求,对/testA 进行限流,则可以这样配置:

请添加图片描述

案例:流控模式-链路

案例:流控模式-链路

需求:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流。

步骤:
1.在OrderService中添加一个queryGoods方法,不用实现业务

Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解,示例:

@Service
public class OrderService {
    @SentinelResource("goods")
    public String queryGoods(){
        return "查询商品";
    }
}
  1. 在OrderController中,改造/order/query端点,调用OrderService中的queryGoods方法(/order/query -> queryGoods),开启一个上游链路1
  2. 在OrderController中添加一个/order/save的端点,调用OrderService的queryGoods方法(/order/save -> queryGoods),开启一个上游链路2
package com.hgw.sentinelService8401.controller;

@RestController
public class OrderController {

    @Autowired
    OrderService orderService;

    @GetMapping("/order/query")
    public String query() {
        return orderService.queryGoods()+"并展示";
    }
    @GetMapping("/order/save")
    public String save() {
        return orderService.queryGoods()+"并保存";
    }
}
  1. 给queryGoods设置限流规则,从/order/query进入queryGoods的方法限制QPS必须小于2(设置/order/query qqs<2)

请添加图片描述

聚合导致链路不生效

聚合导致链路不生效

  • 从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,导致链路限流不生效。
  • 1.7.0版本开始,官方在CommonFilter引入了WEB_CONTEXT_UNIFY参数,用于控制是否收敛context,将其配置为false即可根据不同的URL进行链路限流。

有2个方法:

方法一 ,添加配置类,配置CommonFilter过滤器,指定WEB_CONTEXT_UNIFY=false,禁止收敛URL的入口context

@Configuration
public class SentinelConfig {
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合   解决流控链路不生效的问题
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);
        return registration;
    }
}

方案二 需要修改application.yml,添加配置 web-context-unify: false ,表示关闭:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080 # sentinel控制台地址
      web-context-unify: false # 关闭context整合


3.5、流控效果


3.5.1、直接->快速失败(默认的流控处理)

直接失败,抛出异常,Blocked by Sentinel (flow limiting)

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

3.5.2、预热

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

即:在预热时长内 阈值被设置为 阈值除以coldFactor(默认值为3),预热时长之后恢复设置阈值。

在这里插入图片描述

默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

案例:阈值为10 + 预热时长设置5秒

系统初始化的阈值为 10/3 约等于3,即阈值刚开始为3;然后过了预热时长5秒之后阈值才慢慢升高恢复10

请添加图片描述

测试:

3.5.3、排队等待

匀速排队,让请求以均匀的速度通过,匀速排队,阈值必须设置为QPS

请添加图片描述

案例:设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒

请添加图片描述

通过postman进行测试:

请添加图片描述

所有的请求都排队进行执行。


3.6、Sentinel-自定义流控响应

package com.hgw.sentinelService8401.config;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import org.bouncycastle.asn1.cms.SCVPReqRes;
import org.springframework.context.annotation.Configuration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Data time:2022/5/14 10:55
 * StudentID:2019112118
 * Author:hgw
 * Description: Sentinel-自定义流控响应
 */
@Configuration
public class SeckillSentinelConfig {

    public SeckillSentinelConfig() {
        WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
            @Override
            public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
                Map<String,String> error = new HashMap<>();
                error.put("code", "1001");
                error.put("msg", "请求流量过大");
                httpServletResponse.setCharacterEncoding("UTF-8");
                httpServletResponse.setContentType("application/json");
                httpServletResponse.getWriter().write(JSON.toJSONString(error));
            }
        });
    }
}

在这里插入图片描述

3.7、降级规则


降级规则官网

Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制, 让请求快速失败,避免影响到其它的资源而导致级联错误。

Sentinel的断路器是没有半开状态的

  • 半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix

Sentinel 提供以下几种熔断策略:

  • RT(平均响应时间,秒级)
    平均响应时间超出阈值在时间窗口内通过的请求>=5 ,两个条件同时满足后触发降级。窗口期过后关闭断路器
    RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

  • 异常比列(秒级)
    QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

  • 异常数(分钟级)

    异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:

3.7.1、RT

平均响应时间(DEGRADE_GRADE_RT):

  • 触发条件:1秒内持续进入5个请求对应时刻的平均响应时间(秒级)均超过阈值

在接下的时间窗口(DegradeRule中的timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。

注意 Sentinel 默认统计的 RT 上限是 4900 ms,超过此阈值的都会作 4900ms,若需要变更此上线可以通过启动配置项 -Dcsp.sentinel,statistic.max.rt=xxx 来配置。

案例:测试RT

1、业务代码

package com.hgw.sentinelService8401.controller;

/**
 * Description: 测试类,用来进行流控检测
 */
@Slf4j
@RestController
public class FlowLimitController {

    @GetMapping("/testC")
    public String testC() {
        // 暂停几秒钟线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("testD 测试RT");
        return "---testB";
    }
}

2、配置降级规则

在这里插入图片描述

上述配置:

  • 当我们一秒内进入的请求大于5个的时候 且 没在200毫秒之内处理完,在未来的1秒中的时间敞口内,断路器打开,微服务不可用!
3.7.2、异常比例

异常比例(DEGRADE_GRADE_EXCEPTION_RATIO

  • 触发条件:当资源的每秒请求量>=5 并且 每秒异常总数占通过量的比值超过阈值

在接下的时间窗口(DegradeRule中的timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0],代表 0%-100%。

案例:异常比例

1、业务代码

@GetMapping("/testC")
public String testC() {
    log.info("testC 测试异常比例");
    // 制造一个异常
    int num = 10/0;
    return "---testC";
}

2、配置降级规则

请添加图片描述

单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;

开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。

断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。


3.7.3、异常数

异常数(DEGRADE_GRADE_EXCEPTION_COUNT

  • 触发条件:当接近1分钟的异常数目超过阈值

之后进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindo 小于60s,则结束熔断状态后可能再进入熔断状态。

异常数是按照分钟统计的

在这里插入图片描述

案例:异常比例

1、业务代码

@GetMapping("/testC")
public String testC() {
    log.info("testC 测试异常");
    // 制造一个异常
    int num = 10/0;
    return "---testC";
}

2、配置降级规则

在这里插入图片描述

http://localhost:8401/testC,第一次访问绝对报错,因为除数不能为零,

我们看到error窗口,但是达到6次报错后,进入熔断后降级。


3.8、@SentinelResource


在实际应用过程中,我们可能需要限流的层面不仅限于接口。可能对于某个方法的调用限流,对于某个外部资源的调用限流等都希望做到控制。那么,这个时候我们就不得不手工定义需要限流的资源点,并配置相关的限流策略等内容了。

3.8.1、自定义资源点

自定义资源点

在需要通过Sentinel来控制流量的地方使用@SentinelResource注解,比如下面以控制Service逻辑层的某个方法为例:

@Service
public class OrderService {

    @SentinelResource("goods")
    public String queryGoods(){
        return "查询商品";
    }

}

到这里一个需要被保护的方法就定义完成了。下面我们分别说说,定义了资源点之后,我们如何实现不同的保护策略,包括:限流、降级等。

3.8.2、如何实现限流与熔断降级

在定义了资源点之后,我们就可以通过Dashboard来设置限流和降级策略来对资源点进行保护了。同时,也可以通过@SentinelResource来指定出现限流和降级时候的异常处理策略。下面,就来一起分别看看限流和降级都是如何实现的。

第一步:在Web层调用这个被保护的方法:

@RestController
public class OrderController {

    @Autowired
    OrderService orderService;


    @GetMapping("/order/query")
    public String query() {
        return orderService.queryGoods()+"并展示";
    }
}

第二步:启动测试应用,启动Sentinel-Dashboard。发一个请求到/order/query接口上,使得Sentinel-Dashboard上可以看到如下图所示的几个控制点:

在这里插入图片描述

可以看到,除了如之前入门实例中那样有/order/query资源点之外,多了一个goods资源点。可以通过界面为这个资源点设置限流规则,比如将其QPS设置为2。由于/order/query资源不设置限流规则,所以只要请求/order/query接口,就可以直接模拟调用goods资源,来观察限流规则是否生效。

下面可以通过任何你喜欢的工具来调用/order/query接口,只要QPS超过2,那么就会出现如下的错误返回,代表限流策略生效了。

3.8.3、实现限流的异常处理

默认情况下,Sentinel对控制资源的限流处理是直接抛出异常。在没有合理的业务承接或者前端对接情况下可以这样,但是正常情况为了更好的用户业务,都会实现一些被限流之后的特殊处理,我们不希望展示一个生硬的报错。那么只需要基于上面的例子做一些加工,比如:

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
                         @RequestParam(value = "uID",required = false) String uID) {
    return  "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}
public String dealHandler_testHotKey(String cId, String uID, BlockException exception) {
    return  "----dealHandler_testHotKey";
}

主要做了两件事:

  • 通过@SentinelResource注解的blockHandler属性制定具体的处理函数
  • 实现处理函数,该函数的传参必须与资源点的传参一样,并且最后加上BlockException异常参数;同时,返回类型也必须一样。
3.8.4、客户自定义限流处理逻辑

以上:

  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  • 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  • 全局统一的处理方法没有体现。

1、创建CustomerBlockHandler类用于自定义限流处理逻辑

public class CustomerBlockHandler {
    public static String handleException(BlockException exception) {
        return  "自定义的限流处理信息 ......CustomerBlockHandler";
    }
}

2、指定异常处理类和方法

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
                         @RequestParam(value = "uID",required = false) String uID) {
    return  "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}

3.9、热点key限流


何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

在这里插入图片描述

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

热点key限流官网介绍

案例:对商品id,和用户ID进行限制

1、业务逻辑代码

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "cID",required = false) String cId,
                         @RequestParam(value = "uID",required = false) String uID) {
    return  "--testHotKey;cID:" +(StringUtils.isNotEmpty(cId) ? cId : "")+";uID:"+(StringUtils.isNotEmpty(uID)?uID:"");
}
public String dealHandler_testHotKey(String cId, String uID, BlockException exception) {
    return  "----dealHandler_testHotKey";
}

在这里插入图片描述

兜底方法

分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)

我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

结论

从@HystrixCommand到 @SentinelResource

2、配置热点规则

本次配置:

  • 第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用 dealHandler_testHotKey支持方法。

在这里插入图片描述

限流模式只支持QPS模式,固定写死了。(这才叫热点)

@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推

单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。

特殊情况

我们期望cID 参数当它是某个特殊值时,它的限流值和平时不一样

加入:假如当cID的值等于5时,它的阈值可以达到200

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

当cID不等于5的时候,阈值就是平常的1

热点参数的注意点,参数必须是基本类型或者String


3.10、服务熔断[调用方]


使用Sentinel来保护feign远程调用:熔断。举一个案例:
1、在gulimall-product类配置文件添加配置

#sentinel是不会对feign进行监控的,需要开启配置
feign.sentinel.enabled=true

2、编写 熔断回调方法

package com.atguigu.gulimall.product.feign.fallback;

@Slf4j
@Component
public class SeckillFeignServiceFallBack implements SeckillFeignService {
    @Override
    public R getSkuSeckillInfo(Long skuId) {
        log.error("熔断方法调用...getSkuSeckillInfo");
        return R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(),BizCodeEnume.TO_MANY_REQUEST.getMsg());
    }
}

3、指定 服务熔断回调方法@FeignClient(fallback = 指定的熔断回调方法)

package com.atguigu.gulimall.product.feign;

@FeignClient(value = "gulimall-seckill", fallback = SeckillFeignServiceFallBack.class)
public interface SeckillFeignService {
    @GetMapping("/sku/seckill/{skuId}")
    R getSkuSeckillInfo(@PathVariable("skuId") Long skuId);
}

3.11、Sentinel 持久化规则


一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

修改 cloudalibaba-sentinel-service8401 服务进行持久化

第一步、导入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

第二步、添加Nacos数据源配置

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 配置 dashboard的地址
        dashboard: 127.0.0.1:8833
        # 默认 8719端口,加入被占用会自动从8719开始依次+1,直至找到未被占用的端口
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

第三步、添加Nacos业务规则配置

在这里插入图片描述

[ 
    { 
        "resource": "/rateLimit/byUrl", 
        "limitApp": "default", 
        "grade": 1, 
        "count": 1, 
        "strategy": 0, 
        "controlBehavior": 0, 
        "clusterMode": false 
    } 
]  

resource:资源名称;

limitApp:来源应用;

grade:阈值类型,0表示线程数,1表示QPS;

count:单机阈值;

strategy:流控模式,0表示直接,1表示关联,2表示链路;

controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;

clusterMode:是否集群。



四、Seata

4.1、分布式事务

一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题


4.1.1、本地事务在分布式下的问题

在这里插入图片描述

问题:

  1. 远程服务假失败:
    远程服务其实成功了,由于网络故障等没有返回。
    导致:订单回滚,库存却扣减
  2. 远程服务执行完成,下面的其他方法出现问题
    导致:已执行的远程请求,肯定不能回滚。数据不一致问题

在这里插入图片描述

SpringBoot事务的坑:

在同一个类里面,编写两个方法,内部调用的的时候,会导致事务设置失效。原因没有用到代理对象的缘故。

  • 概括:同一个对象内事务方法互调默认失效(事务是加上的,但是事务的设置失效。比如说:设置超时时间),原因:绕过了代理对象
  • 解决:使用代理对象来调用事务方法
    1. 引入 spring-boot-starter-aop ,(帮我们引入了aspectj)
    2. @EnableTransactionManagement(proxyTargetClass = true) :对外暴露代理对象
    3. @EnableAspectJAutoProxy(exposeProxy = true) :开启 aspectj 动态代理功能。
    4. AopContext.currentProxy() : 调用方法

4.1.2、分布式事务
  • 分布式系统经常出现以下异常:
    • 机器宕机、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失……
4.1.2.1、分布式cap定理 和 BASE理论

cap定理

CAP 原则又称 CAP 定理,指的是在一个分布式系统中

  • 一致性 (Consistency):
    • 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性 (Availability):
    • 在集群中一部分节点故障后,集群整体是否还能享用客户端的读写请求。(对数据更新具备高可用性)
  • 分区容错性 (Partition tolerance):
    • 大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。
      分区容错的意思是:区间通信可能失败。
      比如:一台服务器放在中国,另一台服务器放在美国,这就是两个区,他们之间可能无法通信。

CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾

  • CP
  • AP

BASE理论

是对 CAP 理论的延伸,思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但可以适当的采取若一致性,即 最终一致性。

BASE 是指:

  • 基本可用(Basically Available)
    • 基本可用是指分布式系统在出现故障的时候,允许损失部分可用性(例如:响应时间、功能上的可用性),允许损失部分可用性。需要注意的是,基本可用绝不等价于系统不可用。
      • 响应时间上的损失:正常情况下搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了 1~2秒。
      • 功能上的损失:购物网站在购物高峰(如双十一时),为了保护系统的稳定性,部分消费者可能会被引导到一个降级页面。
  • 软状态(Soft State)
    • 软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。mysql replication 的异步复制也是一种体现。
  • 最终一致性(Nventual Consistency)
    • 最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。

  • 对于关系型数据库,要求更新过的数据能够被后续的访问都能看到,这是强一致性
  • 如果能容忍后续的部分或者全部访问不到,则是弱一致性
  • 如果经过一段时间后要求能够访问到更新后的数据,则是最终一致性

4.1.2.2、分布式事务几种方案
1、2PC 模式

请添加图片描述

2、柔性事务-TCC事务补偿型方案

请添加图片描述

3、柔性事务-最大努力通知方案

请添加图片描述

4、柔性事务-可靠消息+最终一致性方案(异步确保型)

请添加图片描述



4.2、Seata概述


Satia概述:

  • Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

我们只需要使用一个 @GlobalTransactional 注解在业务方法上:

@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
    ......
}

分布式事务处理过程的一ID+三组件模型

Transaction ID XID: 全局唯一的事务ID

  • TC (Transaction Coordinator) - 事务协调者
    • 维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器
    • 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器
    • 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程

在这里插入图片描述


3.4、Seata-Server安装


第一步、官网下载

请添加图片描述

第二步、seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

  1. 先备份原始file.conf文件

  2. 主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

$ cp file.conf fileInit.conf 
$ vim file.conf
  1. service模块

请添加图片描述

  1. store模块

请添加图片描述

第三步、数据库新建库seata 并 在seata库里建表

建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面

请添加图片描述

第四步、修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件

$ cp $ vim registry.conf 
$ vim registry.conf 

请添加图片描述

目的是:指明注册中心为nacos,及修改nacos连接信息

第五步、启动

  1. 先启动nacos

  2. 再启动seata-server

看到以下内容说明安装成功!

==注意:==使用高版本的数据库会报错,将 lib 下的 mysql驱动jar包删掉,加入对应自己数据库版本的mysql驱动jar包即可!



4.4、案例演示

1、导入依赖 :spring-cloud-starter-alibaba-seata seata-all-0.7.1

<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>

2、给分布式大事务的路口标注==@GlobalTransactional;== 每一个远程的小事务用 @Transactional

在 gulimall-order服务中com/atguigu/gulimall/order/service/impl/OrderServiceImpl.java 的 SubmitOrderResponseVo方法加上@GlobalTransactional 注解

@GlobalTransactional
@Transactional  // 本地事务,在分布式系统,只能控制住自己的回滚,控制不了其他服务的回滚。
@Override
public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
  //.....
}

3、配置代理数据源 使用 seata DataSourceProxy代理自己的数据源

因为 Seata 通过代理数据源实现分支事务,如果没有注入,事务无法回滚

添加“com.atguigu.gulimall.order.config.MySeataConfig”类,代码如下:

package com.atguigu.gulimall.order.config;

@Configuration
public class MySeataConfig {

    @Autowired
    DataSourceProperties dataSourceProperties;

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties){
        //得到数据源
        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        if (StringUtils.hasText(dataSourceProperties.getName())){
            dataSource.setPoolName(dataSourceProperties.getName());
        }
        return new DataSourceProxy(dataSource);
    }

}

4、每个微服务都必须要导入

  •              registry.conf
    
  •              file.conf
    

分别给gulimall-order和gulimall-ware加上file.conf和registry.conf这两个配置,并修改file.conf

在这里插入图片描述

5、给所有还不使用seata的服务排除掉,修改其pom.xml文件

<exclusion>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</exclusion>


五、Feign:负载均衡(基于服务端)

5.1、Feign简介


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

​ Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。

​ 它的使用方法是 定义一个服务接口然后在上面添加注解 。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端

只需要创建一个接口,然后添加注解即可~

Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法

  1. 微服务名字 【ribbon】
  2. 接口和注解 【feign】

Feign能干什么?

Feign旨在使编写Java Http客户端变得更容易。

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

Feign默认集成了Ribbon

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

5.2、OpenFeign

Feign和OpenFeign两者区别

  • Feign
    • Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
    • Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
< dependency > 
     < groupId > org.springframework.cloud </ groupId > 
     < artifactId > spring-cloud-starter- feign </ artifactId > 
</ dependency > 
  • OpenFeign
    • OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency> 
     <groupId> org.springframework.cloud </groupId> 
     <artifactId> spring-cloud-starter- openfeign </artifactId> 
</dependency>

5.3、OpenFeign的使用步骤


第一步、改造springcloud-api模块

  1. pom.xml添加feign依赖
<!--Feign的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>
  1. 新建service包,并新建DeptClientService.java接口,

    @Component
    // @FeignClient:微服务客户端注解,value:指定微服务的名字,这样就可以使Feign客户端直接找到对应的微服务
    @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
    public interface DeptClientService {
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/3ef42976420e4a45865820bd03cf9261.png#pic_center)
    
        @GetMapping("/dept/get/{id}")
        public Dept queryById(@PathVariable("id") Long id);
    
        @GetMapping("/dept/list")
        public List<Dept> queryAll();
    
        @PostMapping("/dept/add")
        public Boolean addDept(Dept dept);
    }
    

第二步、创建springcloud-consumer-fdept-feign模块

拷贝springcloud-consumer-dept-80模块下的pom.xml,resource,以及java代码到springcloud-consumer-feign模块.

第三步、添加feign依赖

<!--Feign的依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.2.6.RELEASE</version>
</dependency>

第三步、通过Feign实现:改造后controller:DeptConsumerController.java

@RestController
public class DeptConsumerController {
    
    @Autowired
    private DeptClientService service = null;

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return this.service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id) {
        return this.service.queryById(id);
    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> list() {
        return this.service.queryAll();
    }
}

Feign和Ribbon二者对比,前者显现出面向接口编程特点,代码看起来更清爽,而且Feign调用方式更符合我们之前在做SSM或者SprngBoot项目时,Controller层调用Service层的编程习惯

第四步、主配置类

@SpringBootApplication
@EnableEurekaClient     //开启Eureka 客户端
// feign客户端注解,并指定要扫描的包以及配置接口DeptClientService
@EnableFeignClients(basePackages = {"com.hgw.springcloud"})
//@ComponentScan("com.haust.springcloud") 切记不要加这个注解,不然会出现404访问不到
public class FeignDeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignDeptConsumer_80.class,args);
    }
}

5.4、OpenFeign超时控制


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

默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。

为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

yml文件中开启配置 :

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

5.5、OpenFeign 日志增强


Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。

说白了就是 对Feign接口的调用情况进行监控和输出

日志级别:

  • NONE:默认的,不显示任何日志;

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

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

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

实现步骤:

第一步、配置日志 bean

package com.atguigu.springcloud.cfgbeans;
package com.atguigu.springcloud.cfgbeans;

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

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

第二步、修改配置文件

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

5.6、Feign和Ribbon如何选择?


根据个人习惯而定,如果喜欢REST风格使用Ribbon;如果喜欢社区版的面向接口风格使用Feign.

Feign 本质上也是实现了 Ribbon,只不过后者是在调用方式上,为了满足一些开发者习惯的接口调用习惯!

下面我们关闭springcloud-consumer-dept-80 这个服务消费方,换用springcloud-consumer-dept-feign(端口还是80) 来代替:(依然可以正常访问,就是调用方式相比于Ribbon变化了)


5.7、Feign远程调用丢失请求头问题


  • 问题 :Feign远程调用的时候会丢失请求头
  • 解决:加上feign远程调用的请求拦截器。(RequestInterceptor)
    • 因为feign在远程调用之前会执行所有的RequestInterceptor拦截器

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

在 gulimall-order 服务中 com.atguigu.gulimall.order.config 路径下编写Feign配置类:GulimallFeignConfig类 并编写请求拦截器

package com.atguigu.gulimall.order.config;

@Configuration
public class GulimallFeignConfig {

    /**
     * feign在远程调用之前会执行所有的RequestInterceptor拦截器
     * @return
     */
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if (attributes!=null){
                    HttpServletRequest request = attributes.getRequest();
                    // 2、同步请求头数据,Cookie
                    String cookie = request.getHeader("Cookie");
                    // 给新请求同步了老请求的cookie
                    requestTemplate.header("Cookie",cookie);
                }
            }
        };
    }
}

5.8、Feign异步调用丢失请求头问题


此时:查询购物项、库存和收货地址都要调用远程服务,串行会浪费大量时间,因此我们进行异步编排优化

  • 问题
    由于 RequestContextHolder底层使用的是线程共享数据 ThreadLocal<RequestAttributes>,我们知道线程共享数据的域是 当前线程下,线程之间是不共享的。所以在开启异步时获取不到老请求的信息,自然也就无法共享cookie了。
  • 解决
    向 RequestContextHolder 线程域中放主线程的域。

在这里插入图片描述

修改 gulimall-order 服务中 com.atguigu.gulimall.order.service.impl 目录下的 OrderServiceImpl 类

在这里插入图片描述

@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    OrderConfirmVo confirmVo = new OrderConfirmVo();
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

    // 获取主线程的域
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    // 1、远程查询所有的地址列表
    CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
        RequestContextHolder.setRequestAttributes(requestAttributes);
        // 将主线程的域放在该线程的域中
        List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
        confirmVo.setAddressVos(address);
    }, executor);

    // 2、远程查询购物车所有选中的购物项
    CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
        // 将老请求的域放在该线程的域中
        RequestContextHolder.setRequestAttributes(requestAttributes);
        List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
        confirmVo.setItems(items);
    }, executor);


    // feign在远程调用请求之前要构造

    // 3、查询用户积分
    Integer integration = memberRespVo.getIntegration();
    confirmVo.setIntegration(integration);

    // 4、其他数据自动计算

    // TODO 5、防重令牌
    CompletableFuture.allOf(getAddressFuture,cartFuture).get();
    return confirmVo;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值