spring cloud alibaba

本文博客内容均来自:木鱼水心-
视频教程来自 b 站:尚硅谷SpringCloud第2季

文章目录

    • SpringCloud Bus消息总线
      • 简介:
      • SpringCloud Bus 动态刷新全局广播和定点通知
    • SpringCloud Stream消息驱动概述
      • 简介
      • SpringCloud Stream入门案例
      • Stream 分组消费与持久化
    • SpringCloud Sleuth分布式请求链路跟踪
      • 简介
      • Sleuth 搭建链路监控步骤
    • 初识 SpringCloud Alibaba
      • 介绍
      • 功能:
      • 学习资料:
    • Nacos
      • 简介:
      • 安装并运行Nacos
      • Nacos 服务注册与配置中心
        • 1. 服务注册
        • 2. Nacos作为服务配置中心
        • 3. Nacos作为配置中心-分类配置 介绍
      • Nacos 集群和持久化
    • Sentinel
      • 介绍
      • 安装步骤:
      • Sentinel 初始化监控
      • Sentinel 流控
      • sentinel 熔断降级
        • 1. RT
        • 2. 异常比例
        • 3. 异常数
      • Sentinel热点key
      • 系统规则
      • @SentinelResource配置
      • Sentinel服务熔断Ribbon环境
        • 1. Sentinel服务熔断只配置fallback
        • 2. Sentinel服务熔断只配置blockHandler
        • 3. Sentinel服务熔断fallback和blockHandler都配置
        • 4. Sentinel服务熔断exceptionsToIgnore(忽略属性)
        • 5. Sentinel服务熔断OpenFeign
      • Sentinel持久化规则
    • Seata
      • 分布式事务问题由来
      • Seata简介
      • Seata 下载安装
      • Seata 业务数据准备
      • Seata之 Order-Module 配置搭建
      • @GlobalTransactional
      • Seate 原理简介

spring cloud alibaba_第1张图片

SpringCloud Bus消息总线

简介:

SpringCloud Bus配合SpringCloud Config使用可以实现配置的动态刷新
spring cloud alibaba_第2张图片
SpringCloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。
SpringCloud Bus目前支持 RabbitMQ 和 Kafka。

功能
SpringCloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送,也可以当作微服务间的通信信道

什么是总线?

在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便的广播一些需要让其他连接在该主题上的实例都知道的消息。

基本原理

ConfigClient 实例都监听MQ中同一个topic(默认是springcloubus),当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其他监听统一topic的服务就能得到通知,然后去更新自身的配置。

SpringCloud Bus 动态刷新全局广播和定点通知

全局广播
前提: 先具备良好的 RabbitMQ 环境

  1. 演示广播效果,增加复杂度,再以3355为模板再制做一个3366模块
    spring cloud alibaba_第3张图片


<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>cloud2020artifactId>
        <groupId>com.atguigu.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloud-config-client-3366artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-configartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
project>

#bootstrap.yml
server:
  port: 3366

spring:
  application:
    name: config-client
  cloud:
    #Config客户端配置
    config:
      label: master #分支名称
      name: config #配置文件名称
      profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
      uri: http://localhost:3344 #配置中心地址

#服务注册到eureka地址
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka

#暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: "*"

//主启动类
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3366 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClientMain3366.class,args);
    }
}

//controller
package com.atguigu.springcloud.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope
public class ConfigClientController {
    @Value("${server.port}")
    private String serverPort;
    @Value(("${config.info}"))
    private String configInfo;

    @GetMapping("/configInfo")
    public String getConfigInfo(){
        return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
    }
}

2. 讲一下设计思想
1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
spring cloud alibaba_第4张图片
2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
spring cloud alibaba_第5张图片
图二的架构显然更加合适,图一不合适的原因如下:

  • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责
  • 破坏了微服务各节点的对等性
  • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就好增加更多的修改

3. 给cloud-config-center-3344配置中心服务端端架消息总线支持


<dependency>
    <groupId>org.springframework.cloudgroupId>
	<artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>
	<groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-actuatorartifactId>
dependency>

  #rabbit相关配置
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

#rabbitmq相关配置,暴露bus刷新配置的端点
management:
  endpoints:  #暴露bus刷新配置的端点
    web:
      exposure:
        include: 'bus-refresh'  #凡是暴露监控、刷新的都要有actuator依赖,bus-refresh就是actuator的刷新操作

4. 给cloud-config-client-3355客户端添加消息总线支持


<dependency>
	<groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>	
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-actuatorartifactId>
dependency>

  #rabbit相关配置 15672是web管理界面的端口,5672是MQ访问的端口
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest	#这是客户端,不需要刷新

5. 给cloud-config-client-3366客户端添加消息总线支持


<dependency>
	<groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>
<dependency>	
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-actuatorartifactId>
dependency>

  #rabbit相关配置 15672是web管理界面的端口,5672是MQ访问的端口
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest	#这是客户端,不需要刷新

6. 启动7001、3344、3355、3366,测试
先看一下eureka注册中心,3344,3355,3366都已经注册进来了
spring cloud alibaba_第6张图片
github上现在version 是3,访问3344,看拿到的是多少
spring cloud alibaba_第7张图片
是3,正常,访问3355
spring cloud alibaba_第8张图片

现在,在github将version改为4,如果不使用Bus消息总线通知,3344可以拿到新数据:

spring cloud alibaba_第9张图片

3355/3366要想拿到新数据需要每个都发送一个Post请求激活,但现在使用了Bus总线,我们只需给3344服务端发送一次Post请求,3355/3366就能自动的跟着更新。测试一下,给3344发送Post:

spring cloud alibaba_第10张图片
然后测试3355、3366,这时候已经可以拿到新数据了,但我们并没有重启3355/3366,或给每一个发送post请求
spring cloud alibaba_第11张图片
spring cloud alibaba_第12张图片

这样就达到了一次修改,广播通知,处处生效。

定点通知
如果现在不想全部通知,指向定点通知该如何呢?

解决:
指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心端口号/actuator/bus-refresh/{destination}

/bus/refresh请求不再发送到具体的服务实例上,而是发给 config server并通过destination参数类指定需要更新配置的服务或实例

案例: 这里我们以刷新运行在3355端口上的config-client为例,只通知3355,不通知3366

现在再来修改github配置文件的version,改为5

spring cloud alibaba_第13张图片

现在3344是可以直接拿到新数据,3355/3366不能,我们来发一条这样的Post请求
curl -X -POST “http://localhost:3344/actuator/bus-refresh/config-client:3355”
config-client就是3355的微服务名,3355即端口号

spring cloud alibaba_第14张图片
现在来刷新3355、3366,只有3355拿到了新数据,3366没被通知,没拿到新数据

spring cloud alibaba_第15张图片
通知总结

spring cloud alibaba_第16张图片

spring cloud alibaba_第17张图片

SpringCloud Stream消息驱动概述

简介

如果系统里同时存在多种MQ,可以使用使用Cloud Stream,只需要和Stream交互就可以进行管理。
一句话,屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

官网:https://spring.io/projects/spring-cloud-stream#overview
官方定义SpringCloud Stream是一个构建消息驱动微服务的框架

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

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

目前只支持RabbitMQ、Kafka

设计思想介绍

标准的MQ:
spring cloud alibaba_第18张图片

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

**问题:**比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,Kafka有Topic和Partition分区
spring cloud alibaba_第19张图片
这些消息中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列中的一种,后面的业务需求,我们想往另一种消息队列进行迁移,这时候无疑就是灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候SpringCloud Stream给我们提供了一种解耦合的方式。

如何实现?

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

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

Binder:INPUT对应于消费者,OUTPUT对应于生产者
spring cloud alibaba_第20张图片
Stream中的消息通信方式遵循了发布-订阅模式,Topic主题进行广播(在RabbitMQ就是Exchange,在Kafka是Topic)

Stream标准流程套路

spring cloud alibaba_第21张图片

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

编码API和常用注解

spring cloud alibaba_第22张图片

SpringCloud Stream入门案例

案例说明
工程中新建三个子模块
cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
cloud-stream-rabbitmq-consumer8802,作为消息接收模块
cloud-stream-rabbitmq-consumer8803,作为消息接收模块

Stream消息驱动之生产者
1.新建module cloud-stream-rabbitmq-provider8801

spring cloud alibaba_第23张图片
2. pom.xml



<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0modelVersion>

  <groupId>com.atguigu.springcloudgroupId>
  <artifactId>cloud2020artifactId>
  <version>1.0-SNAPSHOTversion>
  <packaging>pompackaging>

  <name>Mavenname>
  
  <url>http://maven.apache.org/url>
  <inceptionYear>2001inceptionYear>

  <distributionManagement>
    <site>
      <id>websiteid>
      <url>scp://webhost.company.com/www/websiteurl>
    site>
  distributionManagement>

  
  <properties>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <maven.compiler.source>1.8maven.compiler.source>
    <maven.compiler.target>1.8maven.compiler.target>
    <junit.version>4.12junit.version>
    <log4j.version>1.2.17log4j.version>
    <lombok.version>1.16.18lombok.version>
    <mysql.version>5.1.47mysql.version>
    <druid.version>1.1.16druid.version>
    <mybatis.spring.boot.version>1.3.0mybatis.spring.boot.version>
  properties>

  
  <dependencyManagement>
    <dependencies>
      
      <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-dependenciesartifactId>
        <version>2.2.2.RELEASEversion>
        <type>pomtype>
        <scope>importscope>
      dependency>
      
      <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-dependenciesartifactId>
        <version>Hoxton.SR1version>
        <type>pomtype>
        <scope>importscope>
      dependency>
      
      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-alibaba-dependenciesartifactId>
        <version>2.1.0.RELEASEversion>
        <type>pomtype>
        <scope>importscope>
      dependency>
      <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <version>${mysql.version}version>
      dependency>
      <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>druidartifactId>
        <version>${druid.version}version>
      dependency>
      <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>${mybatis.spring.boot.version}version>
      dependency>
      <dependency>
        <groupId>junitgroupId>
        <artifactId>junitartifactId>
        <version>${junit.version}version>
      dependency>
      <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>${lombok.version}version>
        <optional>trueoptional>
      dependency>
    dependencies>
  dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-maven-pluginartifactId>
        <configuration>
          <fork>truefork>
          <addResources>trueaddResources>
        configuration>
      plugin>
    plugins>
  build>
project>

  1. application.yml
server:
  port: 8801

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

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

  1. 主启动类
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQMain8801 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQMain8801.class,args);
    }
}

  1. 业务类
//发送消息接口
package com.atguigu.springcloud.service;
public interface IMessageProvider {
    public String send();
}

//发送消息接口实现
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import javax.annotation.Resource;
import org.springframework.cloud.stream.messaging.Source;
import java.util.UUID;
//这不是传统的service,这是和rabbitmq打交道的,不需要加注解@Service
//这里不掉dao,掉消息中间件的service
//信道channel和exchange绑定在一起
@EnableBinding(Source.class)    //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {
    @Resource
    private MessageChannel output;
    //官网就是这么写的,可以去官网看案例
    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("*****serial: " +serial);
        return null;
    }
}

//Controller
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class SendMessageController {
    @Resource
    private IMessageProvider messageProvider;

    @GetMapping("/sendMessage")
    public String sendMessage(){
        return messageProvider.send();
    }
}

  1. 启动7001eureka,rabbitmq,8801,测试
    登录rabbitmq监控,多次刷新发送Message
    spring cloud alibaba_第24张图片

会发现监控界面出现波峰,说明流水号发送成功

消息驱动之消费者
新建cloud-stream-rabbitmq-consumer8802模块
pom.xml


<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>cloud2020artifactId>
        <groupId>com.atguigu.springcloudgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloud-stream-rabbitmq-consumer8802artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-stream-rabbitartifactId>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
project>

  1. application.yml
server:
  port: 8802

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

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
  instance:
    lease-renewal-interval-in-seconds: 2 # 设置心跳的间隔时间,默认30
    lease-expiration-duration-in-seconds: 5 # 超过5秒间隔,默认90
    instance-id: receive-8802.com #主机名
    prefer-ip-address: true # 显示ip

  1. 主启动
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StreamMQmMain8802 {
    public static void main(String[] args) {
        SpringApplication.run(StreamMQmMain8802.class,args);
    }
}

  1. 业务类
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
    @Value("${server.port}")
    private String serverPort;

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

  1. 启动,测试8801发送,8802接收消息
    正常情况下,8801控制台打出发送流水号,8802控制台接收到流水号
    spring cloud alibaba_第25张图片
    spring cloud alibaba_第26张图片
    这样就读到了消息,rabbitmq监控平台也有了流量波峰,绑定了一个匿名,就是说已经有人关注了/。

Stream 分组消费与持久化

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况,这时我们就可以使用Stream中的消息分组来解决。

spring cloud alibaba_第27张图片
注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),同一组内会发送竞争关系,只有其中一个可以消费。

spring cloud alibaba_第28张图片
同一个组会竞争资源,轮询。不同组会重复消费。

持久化

关于自定义分组

如果8802去掉分组,而8803不去掉,当8802/8803都关闭服务,8801这时候发送消息,8802再启动的时候不会重新获得未曾获得的消息并消费,而8803重启后会获得8801之前发送的消息并消费。

所以group分组属性在消息重复消费和消息持久化消费 避免消息丢失是非常重要的属性

就是默认的分组不会保留未曾获得的消息,自定义的分组会保留。
spring cloud alibaba_第29张图片

SpringCloud Sleuth分布式请求链路跟踪

简介

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

SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案
在分布式系统中提供追踪解决方案并且兼容支持了zipkin
官网:https://cloud.spring.io/spring-cloud-sleuth/reference/html/
spring cloud alibaba_第30张图片
Sleuth 负责收集整理,Zipkin负责展现。

Sleuth 搭建链路监控步骤

1. zipkin
下载地址:
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包

2. 服务提供者8001


<dependency>
    <groupId>org.springframework.cloudgroupId>
	<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>

spring cloud alibaba_第31张图片

spring cloud alibaba_第32张图片
3. 服务消费者80(调用方)
spring cloud alibaba_第33张图片

spring cloud alibaba_第34张图片
spring cloud alibaba_第35张图片
4. 一次启动eureka 7001/8001/80
spring cloud alibaba_第36张图片
5. 打开浏览器
localhost:9411/zipkin
spring cloud alibaba_第37张图片

spring cloud alibaba_第38张图片

初识 SpringCloud Alibaba

介绍

官网地址:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

功能:

服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控。

服务注册与发现:适配SpringCloud服务注册与发现标准,默认集成了Ribbon的支持。

分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。

消息驱动能力:基于SpringCloud Stream为微服务应用构建消息驱动能力。

阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。

分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度任务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker(schedulerx-client)上执行。

在官网引入依赖即可使用:
spring cloud alibaba_第39张图片
spring cloud alibaba_第40张图片

学习资料:

官网: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
spring cloud alibaba_第41张图片

Nacos

简介:

名字的由来:
前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

Nacos介绍:

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

功能:

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

下载地址:
github:https://github.com/alibaba/Nacos

官方文档:https://nacos.io/zh-cn/index.html

学习手册:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_config
各注册中心比较:
spring cloud alibaba_第42张图片

安装并运行Nacos

  1. 本地Java8 + Maven环境已经OK
  2. 先从官网下载Nacos
  3. 解压安装包,直接运行bin目录下的startup.cmd
    spring cloud alibaba_第43张图片
  4. 运行成功后直接访问http://localhost:8848/nacos默认账号密码都是nacos
    spring cloud alibaba_第44张图片

Nacos 服务注册与配置中心

1. 服务注册

官方文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

基于Nacos的服务提供者

  1. 新建module cloudalibaba-provider-payment9001
  2. pom.xml

<dependency>
	<groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-alibaba-dependenciesartifactId>
    <version>2.1.0.RELEASEversion>
    <type>pomtype>
    <scope>importscope>
dependency>


<dependencies>
	
    <dependency>
    	<groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
    	<groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
    	<groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    <dependency>
    	<groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <optional>trueoptional>
    dependency>
    <dependency>
    	<groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-devtoolsartifactId>
        <scope>runtimescope>
        <optional>trueoptional>
    dependency>
    <dependency>
    	<groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
        <scope>testscope>
    dependency>
dependencies>

  1. application.yml
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址
        
management:
  endpoints:
    web:
      exposure:
        include: '*'  #监控

  1. 主启动
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9001.class,args);
    }
}

  1. 业务类
package com.atguigu.springcloud.alibaba.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("/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id){
        return "nacos registry,serverPort: "+ serverPort+"\t id"+id;
    }
}

  1. 测试
    启动Nacos,启动9001

spring cloud alibaba_第45张图片
spring cloud alibaba_第46张图片
这样就表示9001注册进Nacos了,为了演示Nacos的负载均衡,参照9001新建9002,启动后微服务下有两个实例:
spring cloud alibaba_第47张图片
基于Nacos的服务消费者

  1. 新建module cloudalibaba-consumer-nacos-order83
  2. pom.xml
在这里插入代码片<dependencies>
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
        <groupId>com.atguigu.springcloudgroupId>
        <artifactId>cloud-api-commonsartifactId>
    	<version>${project.version}version>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    	<optional>trueoptional>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    	<scope>testscope>
	dependency>
dependencies>

  1. Nacos自带负载均衡:
    spring cloud alibaba_第48张图片
  2. application.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
  1. 主启动
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class OrderNacosMain83 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain83.class,args);
    }
}

  1. 业务类
//Nacos自带Ribbon,引入RestTemplate
package com.atguigu.springcloud.alibaba.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced //RestTemplate结合Ribbon做负载均衡一定要加@LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

package com.atguigu.springcloud.alibaba.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;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class OrderNacosController {
    /*
    因为在yml中配置了service-url.nacos-user-service,
    这里不需要再定义要访问微服务名常量,而是通过boot直接读出来
     */
    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @Resource
    private RestTemplate restTemplate;

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

  1. 启动83 ,测试
    spring cloud alibaba_第49张图片
    现在服务提供者有两个,服务消费者有一个
    spring cloud alibaba_第50张图片
    多次刷新,9001、9002交替出现,这样消费者83访问服务提供者9001/9002,轮询负载OK。

扩展:
spring cloud alibaba_第51张图片
Nacos 支持AP 和 CP模式的转换

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

何时选择使用何种模式?

一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如SpringCloud和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. Nacos作为服务配置中心

Nacos作为配置中心-基础配置 介绍

  1. 新建module cloudalibaba-config-nacos-client3377
  2. pomx.ml
<dependencies>
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
    dependency>
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    	<optional>trueoptional>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    	<scope>testscope>
	dependency>
dependencies>

  1. bootstrap.yml 和 application.yml

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

#bootstrap.xml
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: yml  #指定yaml格式的配置

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

#application.yml
spring:
  profiles:
    active: dev #开发环境

  1. 主启动类
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConfigClientMain3377 {
    public static void main(String[] args) {
        SpringApplication.run(NacosConfigClientMain3377.class,args);
    }
}

  1. 业务类
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope   //SpringCloud原生注解 支持Nacos的动态刷新功能
public class ConfigClientController {

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

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

  1. 在Nacos中添加配置信息

理论:Nacos中的匹配规则
Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则
官网:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
spring cloud alibaba_第52张图片

  1. 配置新增
    spring cloud alibaba_第53张图片
    在这里插入图片描述
    Nacos界面配置对应:
    在这里插入图片描述
  2. 测试
    启动前需要在Nacos客户端-配置管理-配置管理栏目下有对应的yml文件
    运行cloud-config-nacos-client3377的主启动类
    调用接口查看配置信息:http://localhost:3377/config/info
    spring cloud alibaba_第54张图片
  3. 自带动态刷新
    修改下Nacos中的yml文件,再次调用查看配置的接口,就会发现配置以及刷新
    spring cloud alibaba_第55张图片
    spring cloud alibaba_第56张图片
    问题1:
    实际开发中,通常一个系统会准备dev开发环境、test测试环境、prod生产环境,如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
    问题2:
    一个大型分布式微服务系统会有很多微服务子项目,每个微服务项目又会有相应的开发环境、测试环境、预发环境、正式环境…那怎么对这些微服务配置进行管理呢?

3. Nacos作为配置中心-分类配置 介绍

Nacos的图形化管理界面
spring cloud alibaba_第57张图片
spring cloud alibaba_第58张图片
Namespace + Group + Data ID三者关系?为什么这么设计?
是什么?
类似Java中的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象
三者情况
spring cloud alibaba_第59张图片
默认情况:
Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

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

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

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

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

三种方案加载配置

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

默认空间 + 默认分组 + 新建dev和test两个DataID
spring cloud alibaba_第60张图片
通过spring.profile.active属性就能进行多环境下配置文件的读取
spring cloud alibaba_第61张图片
访问测试成功,也是支持动态刷新的
spring cloud alibaba_第62张图片

  1. Group方案:通过Group实现环境区分
    spring cloud alibaba_第63张图片
    spring cloud alibaba_第64张图片
    bootstrap 和 application配置修改
    spring cloud alibaba_第65张图片
    spring cloud alibaba_第66张图片
    测试:
    spring cloud alibaba_第67张图片
    3.Namespace方案:
    新建dev/test的Namespace
    spring cloud alibaba_第68张图片
    回到配置管理-配置列表查看
    spring cloud alibaba_第69张图片
    按照域名配置填写
    spring cloud alibaba_第70张图片
    bootstrap.yml
    spring cloud alibaba_第71张图片
    spring cloud alibaba_第72张图片
    测试:
    spring cloud alibaba_第73张图片

Nacos 集群和持久化

集群
官网:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
spring cloud alibaba_第74张图片
上图翻译一下:
spring cloud alibaba_第75张图片
默认Nacos使用嵌入式数据库(derby)实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持Mysql的存储。

Nacos支持三种部署模式
https://nacos.io/zh-cn/docs/deployment.html

  1. 单机模式:用于测试和单机试用
  2. 集群模式:用于生产环境,确保高可用
  3. 多集群模式:用于多数据中心场景

持久化
Nacos默认自带的是嵌入式数据库derby
derby到mysql切换配置步骤:

  1. nacos-server-1.1.4\nacos\conf 目录下执行sql脚本 nacos-mysql.sql
  2. nacos-server-1.1.4\nacos\conf 目录下找到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=xxx
db.password=xxx

spring cloud alibaba_第76张图片

Sentinel

介绍

代替了Hystrix

官网:https://github.com/alibaba/Sentinel

下载地址:https://github.com/alibaba/Sentinel/releases

Sentinel分为两个部分:

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

安装步骤:

  1. 下载到本地sentinel-dashboard-1.7.0.jar
  2. 运行命令:java -jar sentinel-dashboard-1.7.0.jar
    spring cloud alibaba_第77张图片
  3. 访问sentinel管理界面,登录名密码都为 sentinel
    spring cloud alibaba_第78张图片

Sentinel 初始化监控

1.启动服务注册中心 Nacos8848
spring cloud alibaba_第79张图片
2.新建module cloudalibaba-sentinel-service8401

  1. 注入依赖

<dependencies>
    
    <dependency>
    	<groupId>com.alibaba.cloudgroupId>
    	<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
    
    <dependency>
        <groupId>com.alibaba.cspgroupId>
    	<artifactId>sentinel-datasource-nacosartifactId>
    dependency>
    
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
    	<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
    	<artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-actuatorartifactId>
    dependency>
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
    	<optional>trueoptional>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-testartifactId>
    	<scope>testscope>
    dependency>
    <dependency>
        <groupId>com.atguigu.springcloudgroupId>
        <artifactId>cloud-api-commonsartifactId>
    	<version>${project.version}version>
	dependency>
dependencies>

  1. 配置 ymal 文件
#yml配置
server:
  port: 8401

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

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

  1. 主启动类
//主启动
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class,args);
    }
}

  1. 业务代码
//业务类controller
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FlowLimitController {
    @GetMapping("/testA")
    public String testA(){
        return "--------testA";
    }

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

3.启动sentinel8080
java -jar sentinel-dashboard-1.7.x.jar
spring cloud alibaba_第80张图片
4.启动8401微服务查看sentinel控制台
先执行一次请求
spring cloud alibaba_第81张图片
然后打开sentinel监控:
spring cloud alibaba_第82张图片
这样sentinel8080就在监控微服务8401了。

Sentinel 流控

流控规则基本介绍:
在这里插入图片描述

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

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

  • 阈值类型/单机阈值:

    1. QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
    2. 线程数:当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:(不)需要集群

  • 流控模式:

    1. 直接:api达到限流条件时,直接限流
    2. 关联:当关联的资源达到阈值时,就限流自己
    3. 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)
  • 流控效果:

    1. 快速失败:直接失败,抛异常
    2. Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
    3. 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS。否则无效。

流控模式——直接 快速失败
QPS:
spring cloud alibaba_第83张图片

每秒内请求超过1次报错,这就是限流了。
spring cloud alibaba_第84张图片
线程数:
spring cloud alibaba_第85张图片
当调用该api的线程数达到阈值的时候,进行限流

流控模式——关联 快速失败
spring cloud alibaba_第86张图片

流控效果——Warm Up

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

spring cloud alibaba_第88张图片默认coldFactor为3,即请求QPS从threshold / 3开始,经预热时长逐渐升至设定的QPS阈值。
秒杀系统在开启的瞬间,会有很多流量进来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。

流控效果——排队等待
spring cloud alibaba_第89张图片
spring cloud alibaba_第90张图片

sentinel 熔断降级

官网地址:https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
spring cloud alibaba_第91张图片
spring cloud alibaba_第92张图片
spring cloud alibaba_第93张图片

spring cloud alibaba_第94张图片

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

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

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

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。

进一步说明:
Sentinel的断路器是没有半开状态的

Hystrix

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

1. RT

两百毫秒之内响应一次,否则降级熔断
spring cloud alibaba_第95张图片
测试代码:

@GetMapping("/testD")
    public  String testD(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("test 测试RT");
        return "-----testD";
    }

使用 jmeter 进行压力测试
spring cloud alibaba_第96张图片

2. 异常比例

描述:
spring cloud alibaba_第97张图片
spring cloud alibaba_第98张图片
配置:
spring cloud alibaba_第99张图片
测试代码:

 @GetMapping("/testD")
    public  String testD(){
        log.info("testD 异常比例");
        int age = 10/0;
        return "-----testD";
    }

使用 jmeter 进行测试
spring cloud alibaba_第100张图片

3. 异常数

●异常数(DEGRADE_ GRADE_ EXCEPTION_ _COUNT ): 当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后吗可能
再进入熔断状态。

时间窗口一定要大于等于60秒。

spring cloud alibaba_第101张图片
测试代码

@GetMapping("/testE")
    public  String testE(){

        log.info("testE 异常比数");
        int age = 10/0;
        return "-----testE 测试异常比数";
    }

sentinel 配置
spring cloud alibaba_第102张图片
五次之后自动进行降级熔断

Sentinel热点key

官网:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
spring cloud alibaba_第103张图片

兜底方法
分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
从HystrixCommand到面SentinelResource

热点限流源码:
spring cloud alibaba_第104张图片
测试代码

@GetMapping("/testHotKey")
    // value 可以为任意值,但是为了编码的统一和规范使用的 rest 的地址
    // blockHandler 定义处理降级熔断的方法名称
    @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotkey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2
    ) {


        return "------- testHotKey 成功";
    }


    // 要加入 BlockException
    public String deal_testHotkey(String p1, String p2, BlockException exception) {

        return "------ deal_testHotkey  降级";//sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
    }

sentinel 热点配置
spring cloud alibaba_第105张图片
测试结果
@SentinelResource(value = “testHotKey”) 异常打到了前台用户界面看到,不友好

spring cloud alibaba_第106张图片

@SentinelResource(value = “testHotKey”, blockHandler = “deal_testHotkey”)
方法testHotKey里面第一个 参数只要QPS超过每秒1次,马上降级处理用了我们自己定义的。
spring cloud alibaba_第107张图片

参数例外项

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

配置:
当p1等于5的时候,阈值变为200
当p1不等于5的时候,阈值就是平常的1
热点参数的注意点,参数必须是基本类型或者String

spring cloud alibaba_第108张图片
@Sent inelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
Runt imeException
int age = 10/0, 这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
总结:
@SentinelResource主管配置出错,运行出错该走异常走异常

系统规则

官网地址:https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81
spring cloud alibaba_第109张图片
spring cloud alibaba_第110张图片

@SentinelResource配置

添加依赖:


        <dependency>
            <groupId>com.atguigu.springclundgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>

添加 RateLimitController 类:

@RestController
public class RateLimitController
{
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource()
    {
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    }
    public CommonResult handleException(BlockException exception)
    {
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }


    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl()
    {
        return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
    }

}
  • 按资源名称限流+后续处理
  • 按照Ur|地址限流+后续处理

客户自定义限流处理逻辑

  1. 按照资源名称限流问题解决
    添加测试代码:
  // CustomerBlockHandler
  // blockHandlerClass 表示降级接收的类
  // blockHandler 降级接收的类,该类中所需要降级的方法名
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler ="handlerException2" )
    public CommonResult customerBlockHandler()
    {
        return new CommonResult(200,"客户自定义",new Payment(2020L,"serial003"));
    }

新建 CustomerBlockHandler 类:

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");
    }
}

添加流控:
spring cloud alibaba_第111张图片

进一步说明:
spring cloud alibaba_第112张图片

更多注解属性说明spring cloud alibaba_第113张图片
spring cloud alibaba_第114张图片

Sentinel主要有三个核心Api:

  1. SphU定义资源
  2. Tracer定义统计
  3. ContextUti定义了上下文
    spring cloud alibaba_第115张图片

Sentinel服务熔断Ribbon环境

生产者

新建 cloudalibaba-provider-payment9003 和 cloudalibaba-provider-payment9004

pom:

  <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        <dependency>
            <groupId>com.atguigu.springclundgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

yaml文件:

server:
  port: 9004

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

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

启动类:

package com.atguigu.springcloud.alibaba;

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

/**
 * @author yueLQ
 * @date 2020-08-10 6:56
 */
@SpringBootApplication
public class Payment9004 {
    public static void main(String[] args) {
            SpringApplication.run(Payment9004.class,args);
        }
}

PaymentController:

package com.atguigu.springcloud.alibaba.controller;

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

import java.util.HashMap;

/**
 * @author yueLQ
 * @date 2020-08-10 7:00
 */
@RestController
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long,Payment> hashMap = new HashMap<>();
    static
    {
        hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort:  "+serverPort,payment);
        return result;
    }



}

消费者
新建cloudalibaba-consumer-nacos-order84

pom:

 <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>
        
        <dependency>
            <groupId>com.atguigu.springclundgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

yaml :

server:
  port: 84


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

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

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

启动类:

package com.atguigu.springcloud.alibaba;

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

/**
 * @author yueLQ
 * @date 2020-08-10 7:15
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderNacosMain84 {

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

配置类:

package com.atguigu.springcloud.alibaba.config;

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

/**
 * @author yueLQ
 * @date 2020-08-10 7:17
 */
@Configuration
public class ApplicationContextConfig {

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

PaymentService:

package com.atguigu.springcloud.alibaba.config.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @author yueLQ
 * @date 2020-08-10 7:23
 */
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

实现类 PaymentFallbackService

package com.atguigu.springcloud.alibaba.config.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;

/**
 * @author yueLQ
 * @date 2020-08-10 7:22
 */
@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
        return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
    }
}

创建 controller 类:

package com.atguigu.springcloud.alibaba.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.alibaba.config.service.PaymentService;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

/**
 * @author yueLQ
 * @date 2020-08-10 7:19
 */
@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") //没有配置
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    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);
    }
    //本例是blockHandler
    public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {
        Payment payment = new Payment(id,"null");
        return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
    }

    //==================OpenFeign
    @Resource
    private PaymentService paymentService;

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

热部署对java代码级生效及时
对 @SentinelResource 注解内属性,有时效果不好

访问路径:http://localhost:84/consumer/fallback/1 进行负载均衡。

1. Sentinel服务熔断只配置fallback

在这里插入图片描述
在这里插入图片描述
测试:http://localhost:84/consumer/fallback/4
出现异常时,返回了自己创建的方法
spring cloud alibaba_第116张图片

2. Sentinel服务熔断只配置blockHandler

测试代码块:
在这里插入图片描述
在这里插入图片描述
sentinel 配置
spring cloud alibaba_第117张图片
测试结果:两次错误之后,返回降级方法
spring cloud alibaba_第118张图片

3. Sentinel服务熔断fallback和blockHandler都配置

代码块:
在这里插入图片描述
spring cloud alibaba_第119张图片

sentinel 配置
spring cloud alibaba_第120张图片
测试结果:
有数据据时:
在这里插入图片描述
没有数据时:
spring cloud alibaba_第121张图片
结果:若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。

4. Sentinel服务熔断exceptionsToIgnore(忽略属性)

spring cloud alibaba_第122张图片

5. Sentinel服务熔断OpenFeign

测试代码块 cloudalibaba-consumer-nacos-order84:
添加 OpenFeign 的依赖:

   
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

激活Sentinel对Feign的支持:

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

启动类添加 @EnableFeignClients 注解
spring cloud alibaba_第123张图片

CircleBreakerController
spring cloud alibaba_第124张图片
PaymentService
spring cloud alibaba_第125张图片
PaymentFallbackService
spring cloud alibaba_第126张图片
测试结果:
spring cloud alibaba_第127张图片
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级
在这里插入图片描述

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

Sentinel持久化规则

问题原因:
在 sentinel 控制台中配置了以后,只要后台服务重新启动控制台中的信息就全部消失。一旦我们重启应用,sentinel规则将消失, 生产环境需要将配置规则进行持久化。

解决办法:
将限流配置规则持久化进Nacos保存,只要刷8401某 个rest地址, sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上 sentinel上的流控规则持续有效。
在 cloudalibaba-sentinel-service8401 模块上添加

  1. 添加持久化的依赖

        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>
  1. 修改 yaml 文件 Nacos业务规则配置
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719

      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

feign:
  sentinel:
    enabled: true # 激活Sentinel对Feign的支持



spring cloud alibaba_第128张图片
spring cloud alibaba_第129张图片

  1. nacos 配置
    spring cloud alibaba_第130张图片
[
    {
      "resource":"/rateLimit/byUrl",
      "limitApp":"default",
      "grade":1,
      "count":1,
      "strategy":0,
      "controlBehavior":0,
      "clusterMode":false
    }

]
  1. resource:资源名称;
  2. limitApp:来源应用;
  3. grade:阈值类型,0表示线程数,1表示QPS;
  4. count:单机阈值;
  5. strategy:流控模式,0表示直接,1表示关联,2表示链路;
  6. controlBehavior:流控效果,
  • 0表示快速失败,
  • 1表示Warm Up,
  • 2- 表示排队等待;
    1. clusterMode:是否集群。

测试:
spring cloud alibaba_第131张图片
spring cloud alibaba_第132张图片
停止8401再看sentinel
spring cloud alibaba_第133张图片

重新启动8401再看sentinel
乍一看还是没有,稍等一 会儿多次调用
http://localhost:8401/rateLimit/byUrI
启动8401后刷新sentinel发现业务规则有了
spring cloud alibaba_第134张图片
spring cloud alibaba_第135张图片

Seata

分布式事务问题由来

分布式问题:
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
spring cloud alibaba_第136张图片

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

Seata简介

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

官网地址:https://seata.io/zh-cn/

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

Transaction ID XID 全局唯一的事务ID
Transaction Coordinator(TC) 事务协调器,维护全局事务的运行状态,负责协调并驱动事务的提交或回滚
Transaction Manager(TM ) 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
Resource Manager(RM) 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交或回滚

spring cloud alibaba_第137张图片
处理过程:

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

Seata 下载安装

地址:https://seata.io/zh-cn/blog/download.html

本地@Transactional
全局@GlobalTransactional
spring cloud alibaba_第139张图片

  1. seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件,自定义事务组名称+事务日志存储模式为db +数据库连接信息
    spring cloud alibaba_第140张图片
    spring cloud alibaba_第141张图片
    spring cloud alibaba_第142张图片

  2. 创建 seata 数据库,建表db_ store.sql在\seata-server-0.9.0\seata\conf目录里面

  3. 修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文

    spring cloud alibaba_第143张图片

  4. 先启动Nacos端[口号8848

  5. 再启动seata-server
    spring cloud alibaba_第144张图片

Seata 业务数据准备

spring cloud alibaba_第145张图片
业务说明:
下订单—>扣库存—>减账户(余额)

创建数据库:
seata_ order:存储订单的数据库;
seata_ storage: 存储库存的数据库;
seata_ account:存储账户信息的数据库。

CREATE DATABASE seata_order;
CREATE DATABASE seata_storage;
CREATE DATABASE seata_account; 

seata_order 库下执行该 sql 语句:

CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态: 0:创建中; 1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

SELECT * FROM t_order;

CREATE TABLE t_storage(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT(11) DEFAULT NULL COMMENT '产品id',
total INT(11) DEFAULT NULL COMMENT '总库存',
used INT(11) DEFAULT NULL COMMENT '已用库存',
residue INT(11) DEFAULT NULL COMMENT '剩余库存'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_storage.t_storage( id, product_id, total, used, residue )
VALUES ('1','1', '100' ,'0', '100');
CREATE TABLE t_account(
id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
user_id BIGINT(11) DEFAULT NULL COMMENT '用户id',
total DECIMAL(10,0) DEFAULT NULL COMMENT '总额度',
used DECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',
residue DECIMAL(10,0) DEFAULT '0' COMMENT '剩余可用额度'
)ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


INSERT INTO seata_account.t_account(id, user_id, total, used, residue) VALUES('1','1','1000','0','1000');

订单-库存-账户3个库下都需要建各自的回滚日志表
\seata-server-0.9.0\seata\conf目录下的db_ undo_ log.sql
spring cloud alibaba_第146张图片

Seata之 Order-Module 配置搭建

业务需求:
下订单一>减库存->扣余额->改(订单)状态
目录结构:
spring cloud alibaba_第147张图片

seata-order-service2001
pom文件:

 <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-seataartifactId>
            
            <exclusions>
                <exclusion>
                    <artifactId>seata-allartifactId>
                    <groupId>io.seatagroupId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>io.seatagroupId>
            <artifactId>seata-allartifactId>
            <version>0.9.0version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>5.1.37version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>1.1.10version>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.0.0version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
    dependencies>

yaml 文件

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定义事务组名称需要与seata-server中的对应
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata_order
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

file.conf

transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
  shutdown {
    # when destroy server, wait seconds
    wait = 3
  }
  serialization = "seata"
  compressor = "none"
}

service {

  vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称

  default.grouplist = "127.0.0.1:8091"
  enableDegrade = false
  disable = false
  max.commit.retry.timeout = "-1"
  max.rollback.retry.timeout = "-1"
  disableGlobalTransaction = false
}


client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
  report.retry.count = 5
  tm.commit.retry.count = 1
  tm.rollback.retry.count = 1
}

## transaction log store
store {
  ## store mode: file、db
  mode = "db"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }

  ## database store
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "root"
    min-conn = 1
    max-conn = 3
    global.table = "global_table"
    branch.table = "branch_table"
    lock-table = "lock_table"
    query-limit = 100
  }
}
lock {
  ## the lock store mode: local、remote
  mode = "remote"

  local {
    ## store locks in user's database
  }

  remote {
    ## store locks in the seata's server
  }
}
recovery {
  #schedule committing retry period in milliseconds
  committing-retry-period = 1000
  #schedule asyn committing retry period in milliseconds
  asyn-committing-retry-period = 1000
  #schedule rollbacking retry period in milliseconds
  rollbacking-retry-period = 1000
  #schedule timeout retry period in milliseconds
  timeout-retry-period = 1000
}

transaction {
  undo.data.validation = true
  undo.log.serialization = "jackson"
  undo.log.save.days = 7
  #schedule delete expired undo_log in milliseconds
  undo.log.delete.period = 86400000
  undo.log.table = "undo_log"
}

## metrics settings
metrics {
  enabled = false
  registry-type = "compact"
  # multi exporters use comma divided
  exporter-list = "prometheus"
  exporter-prometheus-port = 9898
}

support {
  ## spring
  spring {
    # auto proxy the DataSource bean
    datasource.autoproxy = false
  }
}

registry.conf

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}


SeataOrderMainApp2001

package com.atguigu.springcloud.alibaba;

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

/**
 * @author yueLQ
 * @date 2020-08-10 19:55
 */
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) //取消数据源的自动装配
@EnableFeignClients
@EnableDiscoveryClient
public class SeataOrderMainApp2001 {

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

Order

package com.atguigu.springcloud.alibaba.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * @author yueLQ
 * @date 2020-08-10 19:58
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order
{
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //订单状态:0:创建中;1:已完结
}

CommonResult

package com.atguigu.springcloud.alibaba.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author yueLQ
 * @date 2020-08-10 20:00
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>
{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
}

OrderDao

package com.atguigu.springcloud.alibaba.dao;

import com.atguigu.springcloud.alibaba.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * @author yueLQ
 * @date 2020-08-10 20:21
 */
@Mapper
public interface OrderDao {

    // 新建顶顶那

    void create(Order order);

    // 修改订单装填 从 0 改为 1
    void update(@Param("userId") Long userId,@Param("status") Integer status);


}

OrderMapper.xml




<mapper namespace="com.atguigu.springcloud.alibaba.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Order">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_id" property="userId" jdbcType="BIGINT"/>
        <result column="product_id" property="productId" jdbcType="BIGINT"/>
        <result column="count" property="count" jdbcType="INTEGER"/>
        <result column="money" property="money" jdbcType="DECIMAL"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
    resultMap>

    <insert id="create">
        insert into t_order (id,user_id,product_id,count,money,status)
        values (null,#{userId},#{productId},#{count},#{money},0);
    insert>


    <update id="update">
        update t_order set status = 1
        where user_id=#{userId} and status = #{status};
    update>

mapper>



OrderService

package com.atguigu.springcloud.alibaba.service;

import com.atguigu.springcloud.alibaba.domain.Order;

/**
 * @author yueLQ
 * @date 2020-08-10 20:48
 */
public interface OrderService {

    void create(Order order);
}

StorageService

package com.atguigu.springcloud.alibaba.service;

import com.atguigu.springcloud.alibaba.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author yueLQ
 * @date 2020-08-10 20:49
 */
@FeignClient( value = "seata-storage-service")
public interface StorageService {
    @PostMapping("/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId,
                          @RequestParam("count") Integer count
                          );

}

AccountService

package com.atguigu.springcloud.alibaba.service;

import com.atguigu.springcloud.alibaba.domain.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;

/**
 * @author yueLQ
 * @date 2020-08-10 20:49
 */
@FeignClient( value = "seata-account-service")
public interface AccountService {

    @PostMapping("/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId,
                          @RequestParam("money") BigDecimal money
    );

}

OrderServiceImp

package com.atguigu.springcloud.alibaba.service.imp;

import com.atguigu.springcloud.alibaba.dao.OrderDao;
import com.atguigu.springcloud.alibaba.domain.Order;
import com.atguigu.springcloud.alibaba.service.AccountService;
import com.atguigu.springcloud.alibaba.service.OrderService;
import com.atguigu.springcloud.alibaba.service.StorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author yueLQ
 * @date 2020-08-10 20:50
 */

@Service
@Slf4j
public class OrderServiceImp implements OrderService {
    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    @Override
    public void create(Order order) {
        log.info("------------>>> 开始新建订单");
        orderDao.create(order);

        log.info("------->>>>> 订单开始调用库存,做扣减 Count 数量");
        // 扣减库存
        storageService.decrease(order.getProductId(), order.getCount());
        log.info("------->>>>> 订单开始调用库存,做扣减  Count 数量 结束");


        log.info("------->>>>> 订单开始调用账户,做 RMB的 扣减 Money 金额");
        // 扣减金额
        accountService.decrease(order.getUserId(), order.getMoney());
        log.info("------->>>>> 订单开始调用账户,做 RMB的 扣减 Money 金额 结束");

        // 修改订单状态 从 0 到 1 代表完成
        log.info("------>>>>修改订单状态开始");
        orderDao.update(order.getUserId(), 0);
        log.info("------>>>>修改订单状态结束");
        log.info("------>>>>下订单结束了,哈哈哈哈哈哈哈哈哈哈哈");

    }
}

DataSourceProxyConfig

package com.atguigu.springcloud.alibaba.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

/**
 * @author yueLQ
 * @date 2020-08-10 22:00
 */
@Configuration
public class DataSourceProxyConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource dataSource) {
        return new DataSourceProxy(dataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSourceProxy);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
        return sqlSessionFactoryBean.getObject();
    }

}

MyBatisConfig

package com.atguigu.springcloud.alibaba.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author yueLQ
 * @date 2020-08-10 22:00
 */
@Configuration
@MapperScan({"com.atguigu.springcloud.alibaba.dao"})
public class MyBatisConfig {
}

OrderController

package com.atguigu.springcloud.alibaba.controller;

import com.atguigu.springcloud.alibaba.domain.CommonResult;
import com.atguigu.springcloud.alibaba.domain.Order;
import com.atguigu.springcloud.alibaba.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yueLQ
 * @date 2020-08-10 21:55
 */
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    public CommonResult create(Order order){
        orderService.create(order);
        return new CommonResult(200,"订单创建成功");
    }

}

@GlobalTransactional

正常下单:
数据库情况

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

超时异常,没加@GlobalTransactional

添加异常的代码AccountServiceImpl

在这里插入图片描述

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

超时异常,添加@GlobalTransactional

  1. AccountServiceImpl添加超时

  2. OrderServiceImpl@GlobalTransactional
    spring cloud alibaba_第148张图片

  3. 下单后数据库数据并没有任何改变记录都添加不进来

Seate 原理简介

再看TC/TM/RM三大组件
spring cloud alibaba_第149张图片

  1. TM开启分布式事务(TM 向TC注册全局事务记录) ;
  2. 按业务场景,编排数据库、服务等事务内资源(RM向TC汇报资源准备状态) ;
  3. TM结束分布式事务,事务- -阶段结束(TM通知TC提交/回滚分布式事务) ;
  4. TC汇总事务信息,决定分布式事务是提交还是回滚;
  5. TC通知所有RM提交/回滚资源,事务二阶段结束。

seata 的四种模式

  • AT 模式
    提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 开发中(默认使用该模式)

  • TCC 模式
    支持 TCC 模式并可与 AT 混用,灵活度更高

  • SAGA 模式
    为长事务提供有效的解决方案

  • XA 模式
    支持已实现 XA 接口的数据库的 XA 模式
    AT模式如何做到对业务的无侵入

  1. 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  2. 二阶段:
    提交异步化,非常快速地完成。
    回滚通过一阶段的回滚日志进行反向补偿。

在一阶段, Seata会拦截“业务SQL"

  1. 解析SQL语义,找到“业务SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image" ,
  2. 执行“业务SQL"更新业务数据,在业务数据更新之后,
  3. 其保存成"after image” ,最后生成行锁。
    以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。
    spring cloud alibaba_第150张图片
    二阶段如是顺利提交的话,
    因为“业务SQL"在-阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉, 完成数据清理即可.

spring cloud alibaba_第151张图片
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚-阶段已经执行的“业务SQL",还原业务数据。
回滚方式便是用"before image"还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和"after image”,如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写, 出现脏写就需要转人工处理。
spring cloud alibaba_第152张图片

debug
测试断点:
spring cloud alibaba_第153张图片
数据库结果
在这里插入图片描述
spring cloud alibaba_第154张图片
spring cloud alibaba_第155张图片
在这里插入图片描述

补充
spring cloud alibaba_第156张图片

spring cloud alibaba_第157张图片

本文博客内容均来自:木鱼水心-,特别感谢该博主spring cloud alibaba_第158张图片

你可能感兴趣的:(学习)