Spring全家桶--SpringCloud(高级)

前言

源码地址:
https://github.com/YuyanCai/SpringCloudDemo

https://gitee.com/cai-yuyan/SpringCloudDemo

软件包:
链接:https://pan.baidu.com/s/1ZqyOC4j_bTWPbp9cPZMZcA
提取码:kqvs

导航:
Spring全家桶–SpringCloud(初级)

Spring全家桶–SpringCloud(中级)

Spring全家桶–SpringCloud(高级)

一、SpringCloud alibaba

1.1 简介

Spring Cloud Netflix项目进入维护模式

https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now

什么是维护模式?

将模块置于维护模式,意味着Spring Cloud团队将不会再向模块添加新功能。

他们将修复block级别的 bug 以及安全问题,他们也会考虑并审查社区的小型pull request。

SpringCloud alibaba带来了什么

是什么

官网

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

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

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

1.2 主要功能

  • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
  • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

去哪下

如果需要使用已发布的版本,在 dependencyManagement 中添加如下配置。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.5.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

然后在 dependencies 中添加自己所需使用的依赖即可使用。

1.3 组件

  • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

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

  • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。

  • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。

  • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

  • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

  • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。

  • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。

1.4 资源获取

Spring Cloud Alibaba学习资料获取

  • 官网

  • https://spring.io/projects/spring-cloud-alibaba#overview

  • 英文

  • https://github.com/alibaba/spring-cloud-alibaba

  • https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

  • 中文

  • https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

二、Nacos

2.1 Nacos简介

为什么叫Nacos

  • 前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

是什么

  • 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
  • Nacos: Dynamic Naming and Configuration Service
  • Nacos就是注册中心+配置中心的组合 -> Nacos = Eureka+Config+Bus

能干嘛

  • 替代Eureka做服务注册中心
  • 替代Config做服务配置中心

去哪下

  • https://github.com/alibaba/nacos/releases

各中注册中心比较

服务注册与发现框架 CAP模型 控制台管理 社区活跃度
Eureka AP 支持 低(2.x版本闭源)
Zookeeper CP 不支持
consul CP 支持
Nacos AP 支持

据说Nacos在阿里巴巴内部有超过10万的实例运行,已经过了类似双十一等各种大型流量的考验。

2.2 Nacos安装

  • 本地Java8+Maven环境已经OK先
  • 官网下载Nacos
  • 解压安装包,直接运行bin目录下的startup.cmd
  • 命令运行成功后直接访问http://localhost:8848/nacos,默认账号密码都是nacos

[外链图片转存中…(img-u3PMF1OI-1648868469453)]

image-20220328184038730

2.3 Nacos负载实例

2.3.1 Nacos之服务提供者注册

新建Module - cloudalibaba-provider-payment9001

父POM

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

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>cloud</artifactId>
        <groupId>com.caq.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-provider-payment9001</artifactId>

    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

YML

server:
  port: 9001

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

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

@EnableDiscoveryClient和@EnableEurekaClient共同点就是:都是能够让注册中心能够发现,扫描到改服务。

不同点:@EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient 可以是其他注册中心。

package com.caq.cloud;

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

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

测试

  • http://localhost:9001/payment/nacos/1
  • nacos控制台
  • nacos服务注册中心+服务提供者9001都OK了

image-20220328194227214

cloudalibaba-provider-payment9002

同上,只需该端口号即可

2.3.2 Nacos之服务消费者注册和负载

新建Module - cloudalibaba-consumer-nacos-order83

POM

为什么nacos支持负载均衡?因为spring-cloud-starter-alibaba-nacos-discovery内含netflix-ribbon包。

image-20220328212515444

<?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>cloud</artifactId>
        <groupId>com.caq.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-consumer-nacos-order83</artifactId>
    <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


</project>

yml

server:
  port: 83

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

主启动

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

业务类

ApplicationContextConfig

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

测试

[外链图片转存中…(img-NZ8wauog-1648868469471)]

2.3.3 Nacos服务注册中心对比提升

[外链图片转存中…(img-tGccNyxc-1648868469473)]

Nacos和CAP

Nacos与其他注册中心特性对比

[外链图片转存中…(img-PVarRw6z-1648868469474)]

Nacos服务发现实例模型

[外链图片转存中…(img-L8IHwhEO-1648868469475)]

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

2.4 Nacos之服务配置中心

新建cloudalibaba-config-nacos-client3377

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>cloud</artifactId>
        <groupId>com.caq.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-config-nacos-client3377</artifactId>

    <dependencies>
        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos-discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--web + actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--一般基础配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

YML

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

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

bootstrap

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置

# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml

# nacos-config-client-test.yaml   ----> config.info

application

spring:

  profiles:
    active: dev # 表示开发环境
    #active: test # 表示测试环境
    #active: info

主启动

package com.caq.cloud;

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

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

业务类

@RestController
@RefreshScope //支持Nacos的动态刷新功能。
public class ConfigClientController
{
    
    
    @Value("${config.info}")
    private String configInfo;

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

在Nacos中添加配置信息

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

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

p r e f i x − {prefix}- prefix{spring-profile.active}.${file-extension}

  • prefix默认为spring.application.name的值,也可以通过配置项spring.cloud.nacos.config.prefix来配置。
  • spring.profile.active即为当前环境对应的 profile,详情可以参考 Spring Boot文档。注意:当spring.profile.active为空时,对应的连接符 - 也将不存在,datald 的拼接格式变成 p r e f i x . {prefix}. prefix.{file-extension}
  • file-exetension为配置内容的数据格式,可以通过配置项spring .cloud.nacos.config.file-extension来配置。目前只支持properties和yaml类型。
  • 通过Spring Cloud 原生注解@RefreshScope实现配置自动更新。

也就是:

s p r i n g . a p p l i c a t i o n . n a m e ) − {spring.application.name)}- spring.application.name){spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

小总结

image-20220329104001107

测试

  • 启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件
  • 运行cloud-config-nacos-client3377的主启动类
  • 调用接口查看配置信息 - http://localhost:3377/config/info

[外链图片转存中…(img-H4GcUnNn-1648868469477)]

自带动态刷新

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

2.5 Nacos之命名空间分组和DataID三者关系

多环境多项目管理

问题1:

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

  1. dev开发环境
  2. test测试环境
  3. prod生产环境。

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

问题2:

一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境…那怎么对这些微服务配置进行管理呢?

通过Namespace+Group+Data lD进行管理

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

是什么

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

[外链图片转存中…(img-FmcDEkQe-1648868469478)]

默认情况:Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

Nacos默认的Namespace是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去
Service就是微服务:一个Service可以包含多个Cluster (集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。

比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是Instance,就是微服务的实例。

2.5.1 Nacos之DataID配置

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

默认空间+默认分组+新建dev和test两个DatalD

  • 新建dev配置DatalD

image-20220329104615864

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

[外链图片转存中…(img-sAPvV6GN-1648868469480)]

2.5.2 Nacos之Group分组方案

通过Group实现环境区分 - 新建Group

image-20220329113609211

image-20220329113620781

2.5.3 Nacos之Namespace空间方案

image-20220329113852384

选择命名空间

image-20220329140926949

测试

[外链图片转存中…(img-8B8f3ibT-1648868469488)]

2.6 Nacos集群

学前食用

Nginx快速入门_小蜗牛耶的博客-CSDN博客

2.6.1 Nacos持久化配置

官网说明:

Nacos支持三种部署模式

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

derby到mysql切换配置步骤

  • 1.安装数据库,版本要求:5.6.5+
  • 2.初始化mysql数据库,数据库初始化文件:nacos-mysql.sql
  • 3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。

再以单机模式启动nacos,nacos所有写嵌入式数据库的数据都写到了mysql

image-20220329150750699

[外链图片转存中…(img-IaR50ycF-1648868469491)]

再次访问,会发现之前写的数据已经被清空,在写数据将会被写到mysql数据库中

image-20220329151219150

image-20220329151515335

2.6.3 Nacos集群部署

1.Linux服务器上mysql数据库配置

这里我用mariadb代替mysql了

image-20220330093222724

复制nacos中conf文件夹下sql语句,粘贴建表

[外链图片转存中…(img-Rgxk001z-1648868469498)]

2.application.properties配置

image-20220329234343127

3.Linux服务器上nacos的集群配置cluster.conf

梳理出3台nacos集器的不同服务端口号,设置3个端口:

  • 3333
  • 4444
  • 5555

image-20220330100927696

4.编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口

[外链图片转存中…(img-OjCiUtWh-1648868469504)]

image-20220329161639713

location 指令说明

nginx回顾:

location 指令说明 :

该指令用于匹配 URL。 语法如下:

location [= | ~ | ~* | ^~] uri {

}

执行方式 - startup.sh - p 端口号

image-20220329183604762

5.Nginx的配置,由它作为负载均衡器

编辑nginx配置文件

因为我是nacos和nginx部署在不同服务器上,所以IP填对应的

image-20220330100137512

按照指定启动

image-20220329231216842

6.截止到此处,1个Nginx+3个nacos注册中心+1个mysql

测试

http://http://192.168.10.10:1111/nacos/#/login

[外链图片转存中…(img-QGQiRxiu-1648868469520)]

创建数据测试

image-20220330102954669

生产者进centos中部署的nacos服务

[外链图片转存中…(img-Ssh9VXmC-1648868469522)]

部署完成

[外链图片转存中…(img-qlSnaCWO-1648868469523)]

三、Sentinel

3.1 Sentinel 简介

主页 · alibaba/Sentinel Wiki (github.com)

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

和我们前面学的豪猪哥理念很像,学过前面的豪猪之后就会很好理解了!

流量控制

顾名思义,就是对流量的控制,它用于调整网络包的发送数据。由于系统的处理能力是有限的,我们需要根据系统的处理能力对流量进行控制,从而保证系统的稳定运行。

image-20220330162903044

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

熔断降级

熔断降级就是说由于微服务调用关系的复杂,如果某个调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,从而导致级联错误

image-20220330163552308

Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。

熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。

Hystrix 通过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应 资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制

和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

  • 通过响应时间对资源进行降级

除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

系统自适应保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

Hystrix与Sentinel比较

Hystrix

需要我们程序员自己手工搭建监控平台

没有一套web界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级

Sentinel
单独一个组件,可以独立出来。
直接界面化的细粒度统一配置。
约定 > 配置 > 编码

3.2 Sentinel流控

安装运行

  • 下载jar包,在windows上运行

  • java -jar sentinel-dashboard-1.7.0.jar

  • 访问Sentinel管理界面

    • localhost:8080
    • 登录账号密码均为sentinel

初始化监控

启动Nacos8848成功

新建工程 - cloudalibaba-sentinel-service8401

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>cloud</artifactId>
        <groupId>com.caq.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloudalibaba-sentinel-service8401</artifactId>

    <dependencies>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.caq.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件+actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

YML

server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动

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

业务类FlowLimitController

@RestController
@Slf4j
public class FlowLimitController {
    
    
    @GetMapping("/testA")
    public String testA()
    {
    
    
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB()
    {
    
    
        log.info(Thread.currentThread().getName()+"\t"+"...testB");
        return "------testB";
    }
}

启动Sentinel8080 - java -jar sentinel-dashboard-1.7.0.jar

启动微服务8401

启动8401微服务后查看sentienl控制台

image-20220330105413323

  • Sentinel采用的懒加载说明
    • 执行一次访问即可
      • http://localhost:8401/testA
      • http://localhost:8401/testB
    • 效果 - sentinel8080正在监控微服务8401

访问测试之后,sentinel会监控到微服务8401

[外链图片转存中…(img-kS05wEcz-1648868469529)]

流控规则简介

界面介绍

[外链图片转存中…(img-U0s19bOx-1648868469531)]

[外链图片转存中…(img-8KcOZsKG-1648868469533)]

资源名:唯一名称,默认请求路径。

针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。

阈值类型/单机阈值:

QPS(每秒钟的请求数量)︰当调用该API的QPS达到阈值的时候,进行限流。

线程数:当调用该API的线程数达到阈值的时候,进行限流。

是否集群:不需要集群。

流控模式:

直接:API达到限流条件时,直接限流。

关联:当关联的资源达到阈值时,就限流自己。

链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】。

流控效果:

快速失败:直接失败,抛异常。

Warm up:根据Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。

排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。

3.2.1 QPS直接失败

image-20220330165258678

image-20220330112046677

返回页面 Blocked by Sentinel (flow limiting)

3.2.2 线程数直接失败

线程数:当调用该API的线程数达到阈值的时候,进行限流。

[外链图片转存中…(img-8UpEnVyN-1648868469537)]

开两个窗口,模拟多线程

[外链图片转存中…(img-xSo9s8xB-1648868469538)]

3.2.3 关联

是什么?

  • 当自己关联的资源达到阈值时,就限流自己
  • 当与A关联的资源B达到阀值后,就限流A自己(B惹事,A挂了)
  • 当支付系统达到阈值后,就让订单系统挂掉

设置testA

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

[外链图片转存中…(img-NAvL08Mk-1648868469539)]

下面用postman来模拟并发请求

每0.3秒发送一个请求,一共20个请求

image-20220330185138914

[外链图片转存中…(img-mRgyyHeB-1648868469541)]

3.2.4 预热

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

就是让它慢慢提升流量

image-20220330185651838

测试

多次快速点击http://localhost:8401/testB - 刚开始不行,后续慢慢OK

这就是预热嘛,就跟你去跑步 一样,刚开始跑的太快你指定扛不住啊。所以呢,我们先热身打开身体后再去快跑,这样不就跑起来了嘛!

应用场景

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

3.2.5 排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

image-20220330191029727

[外链图片转存中…(img-QEr7WzF0-1648868469545)]

3.3 Sentienl降级

RT

image-20220331140320427

image-20220331140926911

异常比例

image-20220331141301949

[外链图片转存中…(img-VzGyPjq1-1648868469552)]

异常数

@GetMapping("/testE")
public String testE()
{
    log.info("testE测试异常数");
    int a = 10/0;
    return "------testE";
}

image-20220331142741151

多次访问都是异常,超过设置的阈值直接熔断降级保护系统

image-20220331142852363

3.4 Sentinel热点

何为热点?

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

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

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

[外链图片转存中…(img-cdYtvYjn-1648868469555)]

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

热点参数限流

承上启下复习start

兜底方法,分为系统默认和客户自定义,两种

之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)

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

结论 - 从HystrixCommand到@SentinelResource

代码

  • @SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
  • 方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理
  • 异常用了我们自己定义的兜底方法
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler/*兜底方法*/ = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2) {
    
    
    //int age = 10/0;
    return "------testHotKey";
}

/*兜底方法*/
public String deal_testHotKey (String p1, String p2, BlockException exception) {
    
    
    return "------testHotKey,o(╥﹏╥)o";  //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
}

配置

image-20220331160712297

测试

[外链图片转存中…(img-RtuJBwtw-1648868469557)]

1s内狂点多次,便会提示自定义内容

image-20220331161550847

参数例外项

  • 普通 - 超过1秒钟一个后,达到阈值1后马上被限流
  • 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
  • 特例 - 假如当p1的值等于5时,它的阈值可以达到200

[外链图片转存中…(img-jJLr3xf4-1648868469559)]

测试

  • 当p1等于5的时候,阈值变为200

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

其它

在方法体抛异常

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler/*兜底方法*/ = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
    
    
int age = 10/0;//<----------------------------会抛异常的地方
return "------testHotKey";
}

/*兜底方法*/
public String deal_testHotKey (String p1, String p2, BlockException exception) {
    
    
return "------deal_testHotKey,o(╥﹏╥)o";  //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
}

将会抛出Spring Boot 2的默认异常页面,而不是兜底方法。

@SentinelResource - 处理的是sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结 - @SentinelResource主管配置出错,运行出错该走异常走异常

3.5 Sentinel系统规则

官方文档

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:

Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

3.6 SentinelResource配置

按资源名称限流 + 后续处理

启动Nacos成功

启动Sentinel成功

Module - cloudalibaba-sentinel-service8401

引入自定义的类

<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>com.caq.cloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>

设置流控

[外链图片转存中…(img-BNz4laxp-1648868469560)]

额外问题

此时关闭服务8401 -> Sentinel控制台,流控规则消失了

[外链图片转存中…(img-wmnXLcCh-1648868469561)]

按照Url地址限流 + 后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

业务类RateLimitController

image-20220331194141026

上面兜底方案面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加—个兜底的,那代码膨胀加剧。
  4. 全局统—的处理方法没有体现。

客户自定义限流处理逻辑

客户自定义限流处理逻辑

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

package com.caq.cloud.handle;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.caq.cloud.entities.CommonResult;

public class CustomerBlockHandler {
    
    
    public static CommonResult handlerException(BlockException exception) {
    
    
        return new CommonResult(4444,"按客戶自定义,global handlerException----1");
    }

    public static CommonResult handlerException2(BlockException exception) {
    
    
        return new CommonResult(4444,"按客戶自定义,global handlerException----2");
    }
}

控制层代码

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,//<-------- 自定义限流处理类
        blockHandler = "handlerException2")//<-----------
public CommonResult customerBlockHandler()
{
    
    
    return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}

Sentinel控制台配置

image-20220331194458542

测试

启动微服务后先调用一次 - http://localhost:8401/rateLimit/customerBlockHandler。然后,多次快速刷新http://localhost:8401/rateLimit/customerBlockHandler。刷新后,我们自定义兜底方法的字符串信息就返回到前端。

image-20220331194943570

[外链图片转存中…(img-86A81mFy-1648868469565)]

@SentinelResource 注解

注意:注解方式埋点不支持 private 方法

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

注解支持

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • image-20220331195452944

Sentinel主要有三个核心Api:

  1. SphU定义资源
  2. Tracer定义统计
  3. ContextUtil定义了上下文

3.7 Sentinel整合Ribbon

3.7.1 Sentinel服务熔断Ribbon环境预说

新建84,9003,9004和前一样,这里不在赘述

导包的时候多导一个cloud-api-commons

[外链图片转存中…(img-lkw6eKgw-1648868469567)]

84消费者控制层代码

package com.caq.cloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.caq.cloud.entities.CommonResult;
import com.caq.cloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class CircleBreakerController {
    
    
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")//没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id)
    {
    
    
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

        if (id == 4) {
    
    
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
    
    
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

}

84消费者配置类,RestTemplate

@Configuration
public class ApplicationContextConfig {
    
    

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

9003生产者控制层代码

package cloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
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;
    }
}

9004一样

测试,通过消费者能否负载均衡访问9003,9004

因为nacos集成的有ribbon

[外链图片转存中…(img-EM16hWxl-1648868469569)]

3.7.2 无配置

什么都不配置访问错误的资源显示:

给客户显示error页面,不友好

image-20220331210138707

3.7.3 只配置fallback

@RestController
@Slf4j
public class CircleBreakerController {
    
    
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback")//没有配置
    public CommonResult<Payment> fallback(@PathVariable Long id)
    {
    
    
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

        if (id == 4) {
    
    
            throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
        }else if (result.getData() == null) {
    
    
            throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    
    
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

}

fallback只负责业务异常

image-20220331210547913

3.7.4 只配置blockHandler

image-20220331211639102

@SentinelResource(value = "fallback",blockHandler = "blockHandler")//blockHandler只负责sentinel控制台配置违规
public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {
    
    
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
}

blockHandler只负责sentinel控制台配置违规

所以当有访问资源出错(java相关错误)时,不会触发降级

但当达到异常数时,会被接管显示友好提示

image-20220331211900235

3.7.5 fallback和blockHandler都配置

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback",fallback = "handlerFallback")//fallback只负责业务异常
//    @SentinelResource(value = "fallback", blockHandler = "blockHandler")//blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
    public CommonResult<Payment> fallback(@PathVariable Long id) {
    
    
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
    
    
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
    
    
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //本例是fallback
    public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    
    
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
    }

    public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
    
    
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }

image-20220331213208793

若blockHandler和fallback 都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。

3.7.6 exceptionsToIgnore

exceptionsToIgnore,忽略指定异常,即这些异常不用兜底方法处理。

设置exceptionsToIgnore=IllegalArgumentException后,如果再有IllegalArgumentException异常则不走兜底方法,而是不处理,可以留给人工来处理

@RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback",fallback = "handlerFallback")//fallback只负责业务异常
//    @SentinelResource(value = "fallback", blockHandler = "blockHandler")//blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler",
            exceptionsToIgnore = {
    
    IllegalArgumentException.class})//exceptionsToIgnore
    public CommonResult<Payment> fallback(@PathVariable Long id) {
    
    
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
    
    
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
    
    
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

image-20220331213552278

3.8 Sentinel整合OpenFeign

  • 84消费者调用提供者9003
  • Feign组件一般是消费侧

POM

<!--SpringCloud openfeign -->

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

YML

# 激活Sentinel对Feign的支持

feign:
  sentinel:
    enabled: true

业务类

带@Feignclient注解的业务接口,fallback = PaymentFallbackService.class

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
    
    
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentFallbackService implements PaymentService {
    
    
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
    
    
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}


Controller

@Resource
    private PaymentService paymentService;

    @GetMapping(value = "/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    {
    
    
        return paymentService.paymentSQL(id);
    }

主启动

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients//<------------------------
public class OrderNacosMain84 {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}

测试 - http://localhost:84/consumer/paymentSQL/1

正常访问

image-20220331214842838

模拟9003下线,看服务会不会被耗死

image-20220331214936940

84消费侧自动降级,不会被耗死。

3.9 熔断框架比较

- Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔商/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS,支持基于调用关系的限流 有限的支持 Rate Limiter
流量整形 支持预热模式匀速器模式、预热排队模式 不支持 简单的Rate Limiter模式
系统自适应保护 支持 不支持 不支持
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控,机器发观等 简单的监控查看 不提供控制台,可对接其它监控系统

3.10 Sentinel持久化规则

是什么

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

怎么玩

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

image-20220331220223142

通过json的格式直接将限流写进了nacos中!

[{
    
    
    "resource": "/rateLimit/byUrl",
    "IimitApp": "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:是否集群。

启动8401后刷新sentinel发现业务规则有了

image-20220331221651861

image-20220331221824048

image-20220331221930272

  • 多次调用 - http://localhost:8401/rateLimit/byUrl
  • 重新配置出现了,持久化验证通过

四、Seata

4.1 Seata

Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

4.2 Seata术语

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

  1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID;
  2. XID在微服务调用链路的上下文中传播;
  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;
  4. TM向TC发起针对XID的全局提交或回滚决议;
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求。

image-20220402105053942

测试访问

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

image-20220402100505047

4.3 Seata实战

代码已全部上传至github

YuyanCai/SpringCloudDemo: 尚硅谷SpringCloud源码 (github.com)

4.4 @GlobalTransactional验证

下订单 -> 减库存 -> 扣余额 -> 改(订单)状态

数据库初始情况:

image-20220402112607953

正常下单 - http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

数据库正常下单后状况:

image-20220402100505047

超时异常,没加@GlobalTransactional

模拟AccountServiceImpl添加超时

@Service
public class AccountServiceImpl implements AccountService {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);


    @Resource
    AccountDao accountDao;

    /**
     * 扣减账户余额
     */
    @Override
    public void decrease(Long userId, BigDecimal money) {
    
    
        LOGGER.info("------->account-service中扣减账户余额开始");
        //模拟超时异常,全局事务回滚
        //暂停几秒钟线程
        try {
    
     TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
        accountDao.decrease(userId,money);
        LOGGER.info("------->account-service中扣减账户余额结束");
    }
}

另外,OpenFeign的调用默认时间是1s以内,所以最后会抛异常。

数据库情况

image-20220402112708135

故障情况

  • 当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1
  • 而且由于feign的重试机制,账户余额还有可能被多次扣减

超时异常,加了@GlobalTransactional

用@GlobalTransactional标注OrderServiceImpl的create()方法。

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    
    
    ...

    /**
     * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
     * 简单说:下订单->扣库存->减余额->改状态
     */
    @Override
    //rollbackFor = Exception.class表示对任意异常都进行回滚
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    public void create(Order order)
    {
    
    
		...
    }
}

还是模拟AccountServiceImpl添加超时,下单后数据库数据并没有任何改变,记录都添加不进来,达到出异常,数据库回滚的效果

4.5 集成Seata遇到的各种Bug

Error 2980

[外链图片转存中…(img-wnYpt2lH-1648868469587)]

检查有没有启动seata服务

image-20220402101625595

Feign调用错误

image-20220402100827874

检查AccountService

1、比较是否一致

@FeignClient(value = "seata-account-service")
public interface AccountService {
    
    
server:
  port: 2003

spring:
  application:
    name: seata-account-service

2、检查yml文件

image-20220402101200653

3、检查file文件和yml文件中组名是否一致

[外链图片转存中…(img-x6gsuxVR-1648868469592)]

image-20220402101412524

image-20220402101430663

猜你喜欢

转载自blog.csdn.net/qq_45714272/article/details/123915679