主頁 > 後端開發 > SpringCloud Alibaba學習筆記

SpringCloud Alibaba學習筆記

2022-12-05 07:16:27 後端開發

該筆記整理至尚硅谷周陽老師的SpringCloud課程SpringCloud Alibaba篇

SpringCloud Alibaba入門簡介

Spring Cloud Netflix 專案進入維護模式,Spring Cloud Netflix 將不再開發新的組件,Spring Cloud 版本迭代算是比較快的,因而出現了很多重大 ISSUE 都還來不及 Fix 就又推另一個 Release 了,進入維護模式意思就是目前一直以后一段時間Spring Cloud Netflix提供的服務和功能就這么多了,不在開發新的組件和功能了,以后將以維護和Merge分支Full Request為主,新組件功能將以其他替代平代替的方式實作,基于該背景下,誕生了 SpringCloud Alibaba.

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

SpringCloud Alibaba 特性

  1. 服務限流降級:默認支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降級功能的接入,可以在運行時通過控制臺實時修改限流降級規則,還支持查看限流降級 Metrics 監控,
  2. 服務注冊與發現:適配 Spring Cloud 服務注冊與發現標準,默認集成了 Ribbon 的支持,
  3. 分布式配置管理:支持分布式系統中的外部化配置,配置更改時自動重繪,
  4. 訊息驅動能力:基于 Spring Cloud Stream 為微服務應用構建訊息驅動能力,
  5. 阿里云物件存盤:阿里云提供的海量、安全、低成本、高可靠的云存盤服務,支持在任何應用、任何時間、任何地點存盤和訪問任意型別的資料,
  6. 分布式任務調度:提供秒級、精準、高可靠、高可用的定時(基于 Cron 運算式)任務調度服務,同時提供分布式的任務執行模型,如網格任務,網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行,

Spring Alibaba核心組件

image-20220821151343839

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

英文檔案: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服務注冊和配置中心

Nacos簡介

官網:https://nacos.io/zh-cn/index.html

GitHub:https://github.com/alibaba/Nacos

開發手冊:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

Nacos,全稱 Dynamic Naming and Configuration Service,Nacos = Eureka+Config +Bus,能夠替代 Eureka 做服務注冊中心和 Config 做服務配置中心,

各類注冊中心比較

服務注冊與發現框架 CAP模型 控制臺管理 社區活躍度
Euraka AP 支持 低(2.x 閉源)
Zookeeper CP 不支持
Consul CP 支持
Nacos AP 支持

Nacos安裝與運行

  1. 準備 Java8 + Maven 環境
  2. 下載 Nocas:https://github.com/alibaba/nacos/releases
  3. 解壓安裝包,直接運行 bin 目錄下的 startup.cmd
startup.cmd -m standalone
  1. 命令運行成功后直接訪問:http://localhost:8848/nacos

Nacos服務注冊中心

基于Nacos的服務提供者

  1. 新建 module:cloudalibaba-provider-payment9001

  2. 改 POM

    • 修改父 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
    <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>
    
  3. 寫 YML

server:
  port: 9001

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

management:
  endpoints:
    web:
      exposure:
        include: '*'
  1. 主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001
{
    public static void main(String[] args) {
            SpringApplication.run(PaymentMain9001.class, args);
    }
}
  1. 業務類
@RestController
public class PaymentController
{
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "https://www.cnblogs.com/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id)
    {
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
}
  1. 測驗
    • 訪問:http://localhost:9001/payment/nacos/1,控制臺輸出服務注冊成功提示;
    • 訪問 Nacos 控制臺:http://localhost:8848/nacos,服務串列中顯示注冊成功的服務提供者

基于Nacos的服務消費者

  1. 根據 cloudalibaba-provider-payment9001 新建 cloudalibaba-provider-payment9002 演示負載均衡,
  2. 新建 module:cloudalibaba-consumer-nacos-order83
  3. 改 POM
<dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.xiaobai.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </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>
  1. 寫 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. 主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83
{
    public static void main(String[] args)
    {
        SpringApplication.run(OrderNacosMain83.class,args);
    }
} 
  1. 業務類

    • config
    @Configuration
    public class ApplicationContextBean
    {
        @Bean
        @LoadBalanced
        public RestTemplate getRestTemplate()
        {
            return new RestTemplate();
        }
    }
    
    • OrderNacosController
    @RestController
    public class OrderNacosController
    {
        @Resource
        private RestTemplate restTemplate;
    
        @Value("${service-url.nacos-user-service}")
        private String serverURL;
    
        @GetMapping("/consumer/payment/nacos/{id}")
        public String paymentInfo(@PathVariable("id") Long id)
        {
            return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
        }
    }
    
  2. 測驗

    • 啟動 Nocas 服務、服務提供者 9001、9002
    • 訪問:http://localhost:83/consumer/payment/nacos/13,實作 83 訪問 9001/9002,輪詢負載OK

為什么 Nocas 支持負載輪詢? Nocas 中內置了 Ribbon !

image-20220822222110638

服務注冊中心對比

Nocas 全景圖

image-20220822222303786

Nacos與其他注冊中心特性對比

Nacos Euraka Consul CoreDNS ZooKeeper
一致性協議 CP + AP AP CP / CP
健康檢查 TCP/HTTP/MySQL/Client Beat Client Beat TCP/HTTP/GRPC/Cmd / Client Beat
負載均衡 權重/DSL/metadata/CMDB Ribbon Fabio RR /
雪崩保護 支持 支持 不支持 不支持 不支持
自動注銷 支持 支持 不支持 不支持 不支持
訪問協議 HTTP/DNS/UDP HTTP HTTP/DNS DNS TCP
監聽支持 支持 支持 支持 不支持 支持
多資料中心 支持 支持 支持 不支持 不支持
跨注冊中心 支持 不支持 支持 不支持 不支持
SpringCloud集成 支持 不支持 不支持 不支持 支持
Dubbon集成 支持 不支持 不支持 不支持 支持
K8s集成 支持 不支持 支持 支持 不支持

Nacos 服務發現實體模型

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=https://www.cnblogs.com/xiaobaiLX/p/CP'

Nacos服務配置中心

基礎配置

  1. 新建 module:cloudalibaba-config-nacos-client3377
  2. 改 POM
<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>
  1. 寫 YML

    • 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}
    
    • application
    spring:
      profiles:
        active: dev # 表示開發環境
    
  2. 主啟動

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

    • controller
    @RestController //通過Spring Cloud原生注解@RefreshScope實作配置自動更新
    @RefreshScope //在控制器類加入@RefreshScope注解使當前類下的配置支持Nacos的動態重繪功能,
    public class ConfigClientController
    {
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/config/info")
        public String getConfigInfo() {
            return configInfo;
        }
    }
    
  2. 在 Nacos 中添加配置資訊

    Nacos中的匹配規則:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html

    設定DataId:\({spring.application.name}-\){spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

    • prefix 默認為 spring.application.name 的值;
    • spring.profile.active 即為當前環境對應的 profile,可以通過配置項 spring.profile.active 來配置;
    • file-exetension 為配置內容的資料格式,可以通過配置項 spring.cloud.nacos.config.file-extension 來配置

    Nacos 會記錄組態檔的歷史版本默認保留30天,此外還有一鍵回滾功能,回滾操作將會觸發配置更新,

image-20220822225618049

  1. 測驗
    • 啟動前需要在nacos客戶端-配置管理-配置管理欄目下有對應的yaml組態檔;
    • 運行cloud-config-nacos-client3377的主啟動類;
    • 呼叫介面查看配置資訊:http://localhost:3377/config/info
    • 修改下 Nacos 中的yaml組態檔,再次呼叫查看配置的介面,就會發現配置已經重繪

分類配置

多環境多專案管理中面臨的問題:

  1. 實際開發中,通常一個系統會準備 dev開發環境、test測驗環境、prod生產環境,如何保證指定環境啟動時服務能正確讀取到Nacos上相應環境的組態檔呢?
  2. 一個大型分布式微服務系統會有很多微服務子專案,每個微服務專案又都會有相應的開發環境、測驗環境、預發環境、正式環境......
    那怎么對這些微服務配置進行管理呢?

Nacos 的圖形化管理界面

image-20220828193156061

Namespace+Group+Data ID三者關系

image-20220828193307419

默認情況: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,就是微服務的實體,

三種方案加載配置

  • DataID方案:指定spring.profile.active和組態檔的DataID來使不同環境下讀取不同的配置,

    1. 新建 dev 配置 DataID

    image-20220828194145448

    1. 同上,新建 test 配置 DataID
    2. 通過spring.profile.active屬性就能進行多環境下組態檔的讀取

    image-20220828194351544

    1. 測驗,訪問:http://localhost:3377/config/info,回傳配置內容
  • Group方案:通過Group實作環境區分

    1. 新建 Group,在 nacos 圖形界面控制臺上面新建組態檔DataID

    image-20220828194558137

    1. 在config下增加一條group的配置即可,可配置為 DEV_GROUP 或 TEST_GROUP

    image-20220828195710576

    1. 測驗,訪問:http://localhost:3377/config/info,回傳配置內容
  • Namespace方案

    1. 新建dev/test的Namespace

    image-20220828202442040

    1. 回到服務管理-服務串列查看

    image-20220828202510990

    1. 按照域名配置填寫

    image-20220828202544924

    1. 修改 YML

      • bootstrap:config 添加 namespace 配置
      config:
              server-addr: localhost:8848 #Nacos作為配置中心地址
              file-extension: yaml #這里我們獲取的yaml格式的配置
              namespace: 5da1dccc-ee26-49e0-b8e5-7d9559b95ab0
              #group: DEV_GROUP
              group: TEST_GROUP
      
      • application
      # Nacos注冊配置,application.yml
      spring:
        profiles:
          #active: test
          active: dev
          #active: info
      
    2. 測驗,訪問:http://localhost:3377/config/info,回傳配置內容

Nacos集群和持久化配置

官網說明

官方檔案:https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

image-20220828211246075

默認Nacos使用嵌入式資料庫實作資料的存盤,所以,如果啟動多個默認配置下的Nacos節點,資料存盤是存在一致性問題的,
為了解決這個問題,Nacos采用了集中式存盤的方式來支持集群化部署,目前只支持MySQL的存盤,

image-20220828211542707

image-20220828211638243

查看官網檔案說明:https://nacos.io/zh-cn/docs/deployment.html

Nacos持久化配置

Nacos默認自帶的是嵌入式資料庫derby,說明:https://github.com/alibaba/nacos/blob/develop/config/pom.xml

切換配置 MySQL 步驟

  1. 新建 nacos 資料庫,在 nacos-server-1.1.4\nacos\conf 目錄下找到 sql 腳本,執行:
create database nacos;
use nacos;
source D:\Devware\nacos-server-2.1.0-BETA\conf\nacos-mysql.sql
  1. nacos-server-1.1.4\nacos\conf 目錄下找到 application.properties,修改資料庫配置
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
  1. 啟動Nacos,可以看到是個全新的空記錄界面,以前是記錄進derby

Linux版Nacos+MySQL、生產環境配置

  1. 環境準備

    • 下載 Linux 版 Nacos: https://github.com/alibaba/nacos/releases/tag/1.1.4,解壓到 opt 目錄下
    tar -xzvf /opt/ nacos-server-1.1.4.tar.gz
    
  2. 集群配置

    • 新建 nacos 資料庫,將 nacos 安裝目錄里的 nacos-mysql.sql 匯入到 mysql 資料庫中
    create database nacos;
    use nacos;
    source /opt/nacos/confnacos-mysql.sql
    
    • 修改 application.properties 配置
    cp application.properties.example application.properties
    vim application.properties
    

    image-20220830214401415

    • Linux服務器上nacos的集群配置 cluster.conf
    cp cluster.conf.example cluster.conf
    vim cluster.conf
    

    image-20220901221836125

    • 編輯Nacos的啟動腳本 startup.sh,使它能夠接受不同的啟動埠
    cd /opt/nacos/bin
    vim startup.sh
    

    image-20220830215410645

    image-20220902002128610

    #執行方式
    ./startup.sh -p 3333
    ./startup.sh -p 4444
    
    • Nginx的配置,由它作為負載均衡器
    vim /usr/local/nginx/conf
    

    配置內容

    upstream cluster{
        server 127.0.0.1:3333;
        server 127.0.0.1:4444;
        server 127.0.0.1:5555;
    }
    
    server {
        listen       1111;
        server_name  localhost;
        #charset koi8-r;
        #access_log  logs/host.access.log  main;
        location / {
        #root   html;
        #index  index.html index.htm;
        proxy_pass http://cluster;
    }
    ......
    

    啟動 nginx,執行: ./nginx -C /usr/local/nginx/conf/nginx.conf

    • 截止到此處,1個Nginx+3個nacos注冊中心+1個mysql

      測驗通過nginx訪問nacos :http://192.168.4.15:1111/nacos/#/login

      新建一個測驗配置

      image-20220830220130928

      服務器插入一條資料

      image-20220830220212197

  3. 測驗

    • 修改 cloudablibaba-provider-payment9002 yml
    server-addr: 192.168.111.144:1111
    
    • 微服務 cloudalibaba-provider-payment9002 啟動,注冊進 nacos 集群

    image-20220830220420634

Sentinel實作熔斷與限流

Sentinel簡介

GitHub:https://github.com/alibaba/Sentinel

Sentinel 是一款功能強大的流量控制組件,以 flow 為突破點,覆寫流量控制、并發限制、斷路、自適應系統保護等多個領域,保障微服務的可靠性,

image-20220824195439848

使用手冊:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

Sentinel安裝

Sentinel 分為兩個部分:

  • 核心庫(Uava客戶端):不依賴任何框架/庫,能夠運行于所有ava運行時環境,同時對 Dubbo/Spring Cloud 等框架也有較好的支特,
  • 控制臺(Dashboard):基于Spring Boot開發,打包后可以直接運行,不需要額外的 Tomcat 等應用容器,
  1. 下載:https://github.com/alibaba/Sentinel/releases
  2. 保證 Java8 環境且 8080 埠不被占用,運行 java -jar sentinel-dashboard-1.7.0.jar
  3. 訪問sentinel管理界面:http://localhost:8080 ,登錄賬號密碼均為 sentinel

初始化工程

  1. 新建 module,cloudalibaba-sentinel-service8401
  2. 改 POM
<dependencies>
        <!--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>
  1. 寫 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. 主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401
{
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class, args);
    }
}
  1. controller
@RestController
@Log4j2
public class FlowLimitController
{

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

    @GetMapping("/testB")
    public String testB()
    {
        return "------testB";
    }
}
  1. 測驗

    • 啟動 nacos 執行 startup.cmd -m standalone,訪問:http://localhost:8848/nacos/#/login
    • 啟動 sentinel,執行 java -jar sentinel-dashboard-1.7.0.jar,訪問:http://localhost:8080
    • 啟動8401微服務,查看 sentienl 控制臺,分別訪問:http://localhost:8401/testA、http://localhost:8401/testB

image-20220824201256133

Sentinel 采用的懶加載方式,只有在微服務被訪問之后 Sentienl 才進行監測,

流控規則

image-20220824201540069

  • 資源名:唯一名稱,默認請求路徑
  • 針對來源:Sentinel可以針對呼叫者進行限流,填寫微服務名,默認default(不區分來源)
  • 調值型別單機闊值:
    • QPS(每秒鐘的請求數量):當呼叫該 API 的 QPS 達到閾值的時候,進行限流
    • 執行緒數:當呼叫該p的執行緒數達到闊值的時候,進行限流
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:API 達到限流條件時,直接限流
    • 關聯:當關聯的資源達到閾值時,就限流自己
  • 鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,如果達到城值,就進行限流)【 API 級別的針對來源】
  • 流控效果:
    • 快速失敗:直接失敗,拋例外
    • warm Up:根據 codeFactor (冷加載因子,默認3) 的值,從闊值/codeFactor,經過預熱時長,才達到設定的QPS闊值
    • 排隊等待:勻速排隊,讓請求以勻速的速度通過,閾值型別必須設定為QPS,否則無效

流量模式

  • 直接模式(默認):直接->快速失敗

image-20220824205931469

表示1秒鐘內查詢1次就是OK,若超過次數1,就直接-快速失敗,報默認錯誤,

? 快速點擊訪問:http://localhost:8401/testA,結果 Blocked by Sentinel (flow limiting)

  • 關聯模式:當關聯的資源達到閾值時,就限流自己

image-20220828111029179

postman 模擬并發密集訪問 testB,大批量執行緒高并發訪問B,導致A失效了

image-20220824210538330

  • 鏈路模式:多個請求呼叫了同一個微服務

流控效果

  • 默認的流控處理:直接->快速失敗

    原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

  • 預熱:閾值除以coldFactor(默認值為3),經過預熱時長后才會達到閾值

    image-20220824211142224

檔案:https://github.com/alibaba/Sentinel/wiki/流量控制

原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

例如:系統初始化的閥值為10 / 3 約等于3,即閥值剛開始為3;然后過了5秒后閥值才慢慢升高恢復到10

image-20220824211315336

測驗:多次點擊:http://localhost:8401/testB,剛開始不行,后續慢慢OK

運用場景如:秒殺系統在開啟的瞬間,會有很多流量上來,很有可能把系統打死,預熱方式就是把為了保護系統,可慢慢的把流量放進來,慢慢的把閥值增長到設定的閥值,

  • 等待排隊:勻速排隊,讓請求以均勻的速度通過,閥值型別必須設成QPS,否則無效,

官網:https://github.com/alibaba/Sentinel/wiki/流量控制

原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

測驗

image-20220824211757086

降級規則

官網:https://github.com/alibaba/Sentinel/wiki/熔斷降級

Sentinel 熔斷降級會在呼叫鏈路中某個資源出現不穩定狀態時(例如呼叫超時或例外比例升高),對這個資源的呼叫進行限制,
讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤,

當資源被降級后,在接下來的降級時間視窗之內,對該資源的呼叫都自動熔斷(默認行為是拋出 DegradeException),

注意:Sentinel的斷路器是沒有半開狀態的

image-20220824212037295

降級策略實戰

  1. RT(平均回應時間,秒級):平均回應時間 超出閾值在時間視窗內通過的請求>=5,兩個條件同時滿足后觸發降級,視窗期過后關閉斷路器,RT最大4900(更大的需要通過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效),

    • 代碼
    @GetMapping("/testD")
    public String testD()
    {
        //暫停幾秒鐘執行緒
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        log.info("testD 測驗RT");
        return "------testD";
    }
    
    • 配置

    image-20220824212726686

    • jmeter壓測

    image-20220824212803736

    • 結論

      永遠一秒鐘打進來10個執行緒(大于5個了)呼叫testD,我們希望200毫秒處理完本次任務,如果超過200毫秒還沒處理完,在未來1秒鐘的時間視窗內,斷路器打開(保險絲跳閘)微服務不可用,保險絲跳閘斷電了,后續我停止jmeter,沒有這么大的訪問量了,斷路器關閉(保險絲恢復),微服務恢復OK,

  2. 例外比列(秒級):QPS >= 5 且例外比例(秒級統計)超過閾值時,觸發降級;時間視窗結束后,關閉降級,

    • 代碼
    @GetMapping("/testD")
    public String testD()
    {
        log.info("testD 測驗RT");
        int age = 10/0;
        return "------testD";
    }
    
    • 配置

    image-20220824213006765

    • jmeter壓測

    image-20220824213040944

    • 結論

      開啟jmeter后,直接高并發發送請求,多次呼叫達到我們的配置條件了,斷路器開啟(保險絲跳閘),微服務不可用了,不再報錯error而是服務降級了,

  3. 例外數(分鐘級):例外數(分鐘統計)超過閾值時,觸發降級;時間視窗結束后,關閉降級,

    • 代碼
    @GetMapping("/testE")
    public String testE()
    {
        log.info("testE 測驗例外比例");
        int age = 10/0;
        return "------testE 測驗例外比例";
    }
    
    • 配置

    image-20220824214033195

    http://localhost:8401/testE,第一次訪問絕對報錯,因為除數不能為零,我們看到error視窗,但是達到5次報錯后,進入熔斷后降級,

    • jmeter壓測

    image-20220824214151002

熱點key限流

官網:https://github.com/alibaba/Sentinel/wiki/熱點引數限流

熱點即經常訪問的資料,很多時候我們希望統計或者限制某個熱點資料中訪問頻次最高的TopN資料,并對其訪問進行限流或者其它操作,

@SentinelResource

@GetMapping("/testHotKey")
@SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "https://www.cnblogs.com/xiaobaiLX/p/p1",required = false) String p1, 
                         @RequestParam(value = "https://www.cnblogs.com/xiaobaiLX/p/p2",required = false) String p2){
    return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{
    return "-----dealHandler_testHotKey";
}

image-20220824214631462

限流模式只支持QPS模式,固定寫死了(這才叫熱點),@SentinelResource注解的方法引數索引,0代表第一個引數,1代表第二個引數,以此類推單機閥值以及統計視窗時長表示在此視窗時間超過閥值就限流,上面的抓圖就是第一個引數有值的話,1秒的QPS為1,超過就限流,限流后呼叫dealHandler_testHotKey支持方法,

測驗:

  • http://localhost:8401/testHotKey?p1=abc,快速訪問回傳例外
  • http://localhost:8401/testHotKey?p1=abc&p2=33,快速訪問回傳例外
  • http://localhost:8401/testHotKey?p2=abc,快速訪問成功返回

引數例外項

我們期望p1引數當它是某個特殊值時,它的限流值和平時不一樣,假如當p1的值等于5時,它的閾值可以達到200

image-20220824215205219

  • 訪問:http://localhost:8401/testHotKey?p1=5,快速訪問成功
  • 訪問:http://localhost:8401/testHotKey?p1=3,快速訪問回傳例外

注意:熱點引數的注意點,引數必須是基本型別或者String

系統規則

官網:https://github.com/alibaba/Sentinel/wiki/系統自適應限流

系統保護規則是從應用級別的入口流量進行控制,從單臺機器的Iod、CPU使用率、平均RT、入口QPS和并發執行緒數等幾個維度監控應用指標,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性,

系統保護規則是應用整體維度的,而不是資源維度的,并且僅對入口流量生效,入口流量指的是進入應用的流量(EntryType.IW),比如Web服務或Dubbo服務端接收的請求,都屬于入口流量,

系統規則支持以下的模式:

  • Load自適應(僅對Linux/Unix-ike機器生效):系統的load1作為啟發指標,進行自適應系統保護,當系統Ioād1超過設定的啟發值,且系統當前的并發執行緒數超過估算的系統容量時才會觸發系統保護(BBR階段),系統容量由系統的maxQps*mit估算得出,設定參考值一般是CPU cores 2.5,
  • CPU usage(1.5.0+版本):當系統CPU使用率超過閾值即觸發系統保護(取值范圍0.0-1.0),比較靈敏,
  • 平均T:當單臺機器上所有入口流量的平均T達到闊值即觸發系統保護,單位是毫秒,
  • 并發執行緒數:當單臺機器上所有入口流量的并發執行緒數達到闊值即觸發系統保護,
  • 入口QPS:當單臺機器上所有入口流量的QPS達到閾值即觸發系統保護,

@SentinelResource

按資源名限流+后續處理

啟動 Nacos,執行:startup.cmd -m standalone,訪問:http://localhost:8848/nacos/#/login 測驗

啟動 Sentinel,執行:java -jar sentinel-dashboard-1.7.0.jar

修改 cloudablibaba-sentinel-service8401

  1. 改 POM,添加以依賴
<dependencies>
    <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
        <groupId>com.xiaobai.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
</dependencies>
  1. controller,新建 RateLimitController
@RestController
public class RateLimitController
{
    @GetMapping("/byResource")
    @SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/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 服務不可用");
    }
}
  1. 流控規則配置

    • 配置步驟

    image-20220827225436433

    • 圖形配置與代碼關系

    image-20220827225502196

  2. 測驗,啟動 8401,訪問:http://localhost:8401/byResource,間隔時間大于等于 1s 訪問成功,間隔時間小于 1s 回傳自定義的限流處理資訊,

遺留問題: 當 8401 服務關閉,Sentinel 流量控制規則消失,

按Url地址限流+后續處理

  1. 修改 controller,添加如下方法
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/byUrl")
public CommonResult byUrl()
{
    return new CommonResult(200,"按url限流測驗OK",new Payment(2020L,"serial002"));
}
  1. 初步測驗,訪問:http://localhost:8401/rateLimit/byUrl,訪問成功
  2. 配置流控規則

image-20220827225918925

  1. 二次測驗,連續訪問:http://localhost:8401/rateLimit/byUrl,回傳 Sentinel 自帶的限流處理結果

上述兜底方案面臨的問題:

  1. 系統默認的,沒有體現我們自己的業務要求,
  2. 依照現有條件,我們自定義的處理方法又和業務代碼耦合在一塊,不直觀,
  3. 每個業務方法都添加一個兜底的,那代碼膨脹加劇,
  4. 全域統一的處理方法沒有體現,

客戶自定義限流處理邏輯

  1. 創建 CustomerBlockHandler 類用于自定義限流處理邏輯
public class CustomerBlockHandler
{
    public static CommonResult handleException(BlockException exception){
        return new CommonResult(2020,"自定義的限流處理資訊......CustomerBlockHandler-----1");
    }
    
    public static CommonResult handleException2(BlockException exception){
        return new CommonResult(2020,"自定義的限流處理資訊......CustomerBlockHandler------2");
    }
}
  1. RateLimitController 添加自定義限流處理邏輯
   /**
     * 自定義通用的限流處理邏輯,
     * blockHandlerClass = CustomerBlockHandler.class
     * blockHandler = handleException2
     * 上述配置:找CustomerBlockHandler類里的handleException2方法進行兜底處理
     */
    /**
     * 自定義通用的限流處理邏輯
     */
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
    public CommonResult customerBlockHandler()
    {
        return new CommonResult(200,"按客戶自定義限流處理邏輯");
    }
  1. 啟動微服務后呼叫:http://localhost:8401/rateLimit/customerBlockHandler
  2. Sentinel 控制臺添加配置

image-20220827230635488

  1. 再次呼叫:http://localhost:8401/rateLimit/customerBlockHandler

服務熔斷

Ribbon系列

服務提供者9003/9004

  1. 新建cloudalibaba-provider-payment9003/9004
  2. 改 POM
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
        <groupId>com.xiaobai.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </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>
  1. 寫 YML(記得該埠號)
server:
  port: 9003

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

management:
  endpoints:
    web:
      exposure:
        include: '*'
  1. 主啟動
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003
{
    public static void main(String[] args) {
            SpringApplication.run(PaymentMain9003.class, args);
    }
}
  1. 業務類
@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 = "https://www.cnblogs.com/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;
    }
}
  1. 測驗,訪問:http://localhost:9003/paymentSQL/1

服務消費者84

  1. 新建 cloudalibaba-consumer-nacos-order84
  2. 改 POM
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.xiaobai.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </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>
  1. 寫 YML
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
  1. 主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84
{
    public static void main(String[] args) {
            SpringApplication.run(OrderNacosMain84.class, args);
    }
}
  1. 配置類
@Configuration
public class ApplicationContextConfig
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}
  1. 業務類
@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 = "https://www.cnblogs.com/xiaobaiLX/p/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;
    }
}
  1. 測驗:http://localhost:84/consumer/fallback/4,直接給客戶展示 error 頁面,不友好

添加 fallback 配置

  1. 修改 CircleBreakerController,添加 fallback 配置
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/fallback",fallback = "handlerFallback") //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;
}

public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(444,"兜底例外handlerFallback,exception內容  "+e.getMessage(),payment);
}

image-20220827232336442

  1. 測驗

image-20220827233104659

image-20220827233122275

image-20220827233128339

添加 blockHandler

  1. 修改 CircleBreakerController,添加 fallback 配置
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/fallback",blockHandler = "blockHandler") //blockHandler負責在sentinel里面配置的降級限流
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 ("非法引數例外....");
    }else if (result.getData() == null) {
        throw new NullPointerException ("NullPointerException,該ID沒有對應記錄");
    }
    return result;
}

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

  1. Sentinel 配置

image-20220827233622337

  1. 測驗

image-20220827232900199

添加 fallback 和 blockHandler 配置

  1. 修改 CircleBreakerController,添加 fallback 和 blockHandler 配置
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/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 ("非法引數例外....");
    }else if (result.getData() == null) {
        throw new NullPointerException ("NullPointerException,該ID沒有對應記錄");
    }
    return result;
}

public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(444,"fallback,無此流水,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-20220827233338591

  1. Sentinel 配置

image-20220827233447671

  1. 測驗

image-20220827233510656

若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 時只會進入 blockHandler 處理邏輯,

忽略屬性...

  1. 修改 controller
@RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "https://www.cnblogs.com/xiaobaiLX/p/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 ("非法引數例外....");
        }else if (result.getData() == null) {
            throw new NullPointerException ("NullPointerException,該ID沒有對應記錄");
        }
        return result;
    }

image-20220827235610418

Feign系列

修改84模塊

  1. 修改 POM,添加依賴
<!--SpringCloud openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 修改 YML,添加配置
# 激活Sentinel對Feign的支持
feign:
  sentinel:
    enabled: true 
  1. 業務類

    • 帶 @FeignClient 注解的業務介面
    /** 使用 fallback 方式是無法獲取例外資訊的,
     *  如果想要獲取例外資訊,可以使用 fallbackFactory引數
     */
    @FeignClient(value = "https://www.cnblogs.com/xiaobaiLX/p/nacos-payment-provider",fallback = PaymentFallbackService.class)//呼叫中關閉9003服務提供者
    public interface PaymentService
    {
        @GetMapping(value = "https://www.cnblogs.com/paymentSQL/{id}")
        public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
    }
    
    • 添加 fallback 類
    @Component
    public class PaymentFallbackService implements PaymentService
    {
        @Override
        public CommonResult<Payment> paymentSQL(Long id)
        {
            return new CommonResult<>(444,"服務降級回傳,沒有該流水資訊",new Payment(id, "errorSerial......"));
        }
    }
    
  2. controller

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

    @GetMapping(value = "https://www.cnblogs.com/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
    {
        if(id == 4)
        {
            throw new RuntimeException("沒有該id");
        }
        return paymentService.paymentSQL(id);
    }
  1. 主啟動,添加 @EnableFeignClients 啟動 Feign 的功能
  2. 測驗,訪問:http://localhost:84/consumer/paymentSQL/1,故意關閉9003微服務提供者,看84消費側自動降級,不會被耗死

熔斷框架比較

Sentinel Hystrix resilience4j
隔離策略 信號量隔離(并發執行緒數限質) 執行緒池隔離/信號量隔離 信號量隔離
熔斷策略 基于回應時間、例外比率、例外數 基于例外比率 基于例外比率、回應時間
實時統計實作 滑動視窗 滑動視窗 Ring Bit Buffer
動態規則配置 支持多種資料源 支持多種資料源 有限支持
擴展性 多個擴展點 插件形式 介面形式
基于注解支持 支持 支持 支持
限流 基于QPS,支持基于呼叫關系的限流 有限支持 Rate Limiter
流量整形 支持預熱模式、勻速器模式、預熱排隊模式 不支持 簡單的 Rate Limiter
系統自適應保護 支持 不支持 不支持
控制臺 提供開箱即用的控制臺,可配置規則、查看秒級監控 簡單的監控查看 不提供控制臺,可對接其他監控系統

規則持久化

  1. 修改 cloudalibaba-sentinel-service8401
  2. 修改 POM,添加依賴
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  1. 修改 YML,添加 Nacos 資料源配置
sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow
  1. 添加 Nacos 業務規則配置

image-20220828002717212

[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
  • resource:資源名稱;
  • limitApp:來源應用;
  • grade:閾值型別,0表示執行緒數,1表示QPS;
  • count:單機閾值;
  • strategy:流控模式,0表示直接,1表示關聯,2表示鏈路;
  • controlBehavior:流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等待;
  • clusterMode:是否集群,
  1. 啟動8401后重繪 sentinel 發現業務規則有了

image-20220828002810458

  1. 快速訪問測驗介面:http://localhost:8401/rateLimit/byUrl,回傳限流資訊
  2. 停止 8401 再看 sentinel

image-20220828003009082

  1. 重新啟動 8401 再看 sentinel,呼叫:http://localhost:8401/rateLimit/byUrl,配置重新出現了,持久化驗證通過

Seata處理分布式事務

分布式事務問題

單體應用被拆分成微服務應用,原來的三個模塊被拆分成 三個獨立的應用,分別使用 三個獨立的資料源,業務操作需要呼叫三個服務來完成,此時 每個服務內部的資料一致性由本地事務來保證,但是全域的資料一致性問題沒法保證,

image-20220828222510643

總結:一次業務操作需要跨多個資料源或需要跨多個系統進行遠程呼叫,就會產生分布式事務問題,

Seata簡介

官網:http://seata.io/zh-cn/

下載:https://github.com/seata/seata/releases

Seata是一款開源的分布式事務解決方案,致力于在微服務架構下提供高性能和簡單易用的分布式事務服務,

一個典型的分布式事務程序由一個事務ID + 三個組件組成,三個組件包括:

  1. Transaction Coordinator (TC):事務協調器,維護全域事務的運行狀態,負責協調并驅動全域事務的提交或回滾;
  2. Transaction Manager (TM):控制全域事務的邊界,負責開啟一個全域事務,并最終發起全域提交或全域回滾的決議;
  3. Resource Manager (RM):控制分支事務,負責分支注冊、狀態匯報,并接收事務協調器的指令,驅動分支(本地)事務的提交和回滾,

image-20220828223010974

處理程序:

  1. TM 向 TC 申請開啟一個全域事務,全域事務創建成功并生成一個全域唯一的 XID;
  2. XID 在微服務呼叫鏈路的背景關系中傳播;
  3. RM 向 TC 注冊分支事務,將其納入 XID 對應全域事務的管轄;
  4. TM 向 TC 發起針對 XID 的全域提交或回滾決議;
  5. TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求,

image-20220828223216587

Seata 只需兩個注解:本地@Transactional 和 全域@GlobalTransactional 即可實作分布式事務控制,

Seata-Server安裝

  1. 下載:https://github.com/seata/seata/releases,課程版本下載的是seata-server-0.9.0.zip

  2. seata-server-0.9.0.zip 解壓到指定目錄并修改 conf 目錄下的 file.conf 組態檔

    • 備份 file.conf 檔案

    • 修改自定義事務組名稱+事務日志存盤模式為db+資料庫連接資訊

      service 模塊

      service {
        vgroup_mapping.my_test_tx_group = "fsp_tx_group"
      }
      

      store 模塊

      ## transaction log store
      store {
        ## store mode: file、db
        mode = "db"
       
        ## database store
        db {
          driver-class-name = "com.mysql.jdbc.Driver"
          url = "jdbc:mysql://127.0.0.1:3306/seata"
          user = "root"
          password = "root"
        }
      }
      
  3. mysql5.7 資料庫新建庫 seata,運行在 seata 安裝目錄中 conf 目錄下的 db_store.sql

create database seata;
use seata;
source D:\Devware\seata-server-0.9.0\conf\db_store.sql
  1. 修改 seata 安裝目錄下 conf 目錄下的 registry.conf 組態檔
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos" #指明注冊中心為nacos
 
  nacos {
    serverAddr = "localhost:8848" #修改nacos連接資訊
  }
  1. 啟動 Nacos 埠號8848,執行 startup.cmd -m standalone
  2. 啟動 seata-server,運行 seata 安裝目錄中 bin 目錄下的 seata-server.bat

訂單/庫存/賬戶業務資料庫準備

創建三個服務,一個訂單服務,一個庫存服務,一個賬戶服務,當用戶下單時,會在訂單服務中創建一個訂單,然后通過遠程呼叫庫存服務來扣減下單商品的庫存,再通過遠程呼叫賬戶服務來扣減用戶賬戶里面的余額,最后在訂單服務中修改訂單狀態為已完成,該操作跨越三個資料庫,有兩次遠程呼叫,很明顯會有分布式事務問題,

  1. 建庫
CREATE DATABASE seata_order;
CREATE DATABASE seata_storage; 
CREATE DATABASE seata_account;
  1. 按照上述 3 庫分別建對應業務表
#seata_order庫下建t_order表
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;

#seata_storage庫下建t_storage 表
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');
 
SELECT * FROM t_storage;

#seata_account庫下建t_account 表
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');
 
SELECT * FROM t_account;
  1. 按照上述3庫分別建對應的回滾日志表,匯入 seata 安裝目錄 conf 下的 _undo_log.sql 檔案
source D:\Devware\seata-server-0.9.0\conf\db_undo_log.sql

訂單/庫存/賬戶業務微服務準備

seata-order-service2001

  1. 新建 moudle,seata-order-service2001
  2. 修改 POM
<dependencies>
    <!--nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--seata-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <exclusions>
            <exclusion>
                <artifactId>seata-all</artifactId>
                <groupId>io.seata</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-all</artifactId>
        <version>0.9.0</version>
    </dependency>
    <!--feign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</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>
    <!--mysql-druid-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
  1. 寫 YML
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
  1. 組態檔
  • 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 = "123456"
    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"
  }
}
  1. domain

    • CommonResult
    @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);
        }
    }
    
    • Order
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Order
    {
        private Long id;
    
        private Long userId;
    
        private Long productId;
    
        private Integer count;
    
        private BigDecimal money;
    
        /**
         * 訂單狀態:0:創建中;1:已完結
         */
        private Integer status;
    }
    
  2. Dao 介面實作及實作

    • OrderDao
    @Mapper
    public interface OrderDao {
    
        /**
         * 創建訂單
         */
        void create(Order order);
    
        /**
         * 修改訂單金額
         */
        void update(@Param("userId") Long userId, @Param("status") Integer status);
    }
    
    • resource 目錄下添加 mapper 檔案夾后添加 OrderMapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    
    <mapper namespace="com.xiaobai.springcloud.alibaba.dao.OrderDao">
    
        <resultMap id="BaseResultMap" type="com.xiaobai.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>
    
  3. Service介面及實作

    • OrderService
    public interface OrderService {
    
        /**
         * 創建訂單
         */
        void create(Order order);
    }
    
    • OrderServiceImpl
    @Service
    @Slf4j
    public class OrderServiceImpl implements OrderService
    {
        @Resource
        private OrderDao orderDao;
    
        @Resource
        private StorageService storageService;
    
        @Resource
        private AccountService accountService;
    
        /**
         * 創建訂單->呼叫庫存服務扣減庫存->呼叫賬戶服務扣減賬戶余額->修改訂單狀態
         * 簡單說:
         * 下訂單->減庫存->減余額->改狀態
         */
        @Override
        @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
        public void create(Order order) {
            log.info("------->下單開始");
            //本應用創建訂單
            orderDao.create(order);
    
            //遠程呼叫庫存服務扣減庫存
            log.info("------->order-service中扣減庫存開始");
            storageService.decrease(order.getProductId(),order.getCount());
            log.info("------->order-service中扣減庫存結束");
    
            //遠程呼叫賬戶服務扣減余額
            log.info("------->order-service中扣減余額開始");
            accountService.decrease(order.getUserId(),order.getMoney());
            log.info("------->order-service中扣減余額結束");
    
            //修改訂單狀態為已完成
            log.info("------->order-service中修改訂單狀態開始");
            orderDao.update(order.getUserId(),0);
            log.info("------->order-service中修改訂單狀態結束");
    
            log.info("------->下單結束");
        }
    }
    
    • StorageService
    @FeignClient(value = "https://www.cnblogs.com/xiaobaiLX/p/seata-storage-service")
    public interface StorageService {
    
        /**
         * 扣減庫存
         */
        @PostMapping(value = "https://www.cnblogs.com/storage/decrease")
        CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
    }
    
    • AccountService
    @FeignClient(value = "https://www.cnblogs.com/xiaobaiLX/p/seata-account-service")
    public interface AccountService {
    
        /**
         * 扣減賬戶余額
         */
        //@RequestMapping(value = "https://www.cnblogs.com/account/decrease", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
        @PostMapping("/account/decrease")
        CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
  4. controller

@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    /**
     * 創建訂單
     */
    @GetMapping("/order/create")
    public CommonResult create( Order order) {
        orderService.create(order);
        return new CommonResult(200, "訂單創建成功!");
    }
}
  1. Config配置

    • MyBatisConfig
    @Configuration
    @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
        public class MyBatisConfig {
        }
    }
    
    • DataSourceProxyConfig
    @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();
        }
    }
    

seata-storage-service2002

  1. 新建moudle,seata-storage-service2002

  2. 改 POM

<dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  1. 寫 YML
server:
  port: 2002

spring:
  application:
    name: seata-storage-service
  cloud:
    alibaba:
      seata:
        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_storage
    username: root
    password: 123456

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml
  1. 組態檔

    • 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->rgroup
      vgroup_mapping.fsp_tx_group = "default"
      #only support single node
      default.grouplist = "127.0.0.1:8091"
      #degrade current not support
      enableDegrade = false
      #disable
      disable = false
      #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
      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 {
      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"
    }
    
    support {
      ## spring
      spring {
        # auto proxy the DataSource bean
        datasource.autoproxy = false
      }
    }
    
    • registry.conf
    registry {
      # file 、nacos 、eureka、redis、zk
      type = "nacos"
    
      nacos {
        serverAddr = "localhost:8848"
        namespace = ""
        cluster = "default"
      }
      eureka {
        serviceUrl = "http://localhost:8761/eureka"
        application = "default"
        weight = "1"
      }
      redis {
        serverAddr = "localhost:6381"
        db = "0"
      }
      zk {
        cluster = "default"
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      file {
        name = "file.conf"
      }
    }
    
    config {
      # file、nacos 、apollo、zk
      type = "file"
    
      nacos {
        serverAddr = "localhost"
        namespace = ""
        cluster = "default"
      }
      apollo {
        app.id = "fescar-server"
        apollo.meta = "http://192.168.1.204:8801"
      }
      zk {
        serverAddr = "127.0.0.1:2181"
        session.timeout = 6000
        connect.timeout = 2000
      }
      file {
        name = "file.conf"
      }
    }
    
  2. domain

    • CommanResult
    @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);
        }
    }
    
    • Storage
    @Data
    public class Storage {
    
        private Long id;
    
        /**
         * 產品id
         */
        private Long productId;
    
        /**
         * 總庫存
         */
        private Integer total;
    
        /**
         * 已用庫存
         */
        private Integer used;
    
        /**
         * 剩余庫存
         */
        private Integer residue;
    }
    
  3. Dao介面及實作

    • StorageDao
    @Mapper
    public interface StorageDao {
    
        /**
         * 扣減庫存
         */
        void decrease(@Param("productId") Long productId, @Param("count") Integer count);
    }
    
    • resources檔案夾下新建mapper檔案夾后添加 StorageMapper.xml
    <mapper namespace="com.xiaobai.springcloud.alibaba.dao.StorageDao">
    
        <resultMap id="BaseResultMap" type="com.xiaobai.springcloud.alibaba.domain.Storage">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="product_id" property="productId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="INTEGER"/>
            <result column="used" property="used" jdbcType="INTEGER"/>
            <result column="residue" property="residue" jdbcType="INTEGER"/>
        </resultMap>
    
        <update id="decrease">
            UPDATE t_storage
            SET used    = used + #{count},
                residue = residue - #{count}
            WHERE product_id = #{productId}
        </update>
    
    </mapper>
    
  4. Service介面及實作

    • StorageService
    public interface StorageService {
        /**
         * 扣減庫存
         */
        void decrease(Long productId, Integer count);
    }
    
    • StorageServiceImpl
    @Service
    public class StorageServiceImpl implements StorageService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
    
        @Resource
        private StorageDao storageDao;
    
        /**
         * 扣減庫存
         */
        @Override
        public void decrease(Long productId, Integer count) {
            LOGGER.info("------->storage-service中扣減庫存開始");
            storageDao.decrease(productId,count);
            LOGGER.info("------->storage-service中扣減庫存結束");
        }
    }
    
  5. Controller

@RestController
public class StorageController {

    @Autowired
    private StorageService storageService;

    /**
     * 扣減庫存
     */
    @RequestMapping("/storage/decrease")
    public CommonResult decrease(Long productId, Integer count) {
        storageService.decrease(productId, count);
        return new CommonResult(200,"扣減庫存成功!");
    }
}
  1. Config配置

    • MyBatisConfig
    @Configuration
    @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
    public class MyBatisConfig {
    }
    
    • DataSourceProxyConfig
    @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();
        }
    
    }
    
  2. 主啟動

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataStorageServiceApplication2002 {

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

}

seata-account-service2003

  1. 新建moudle,seata-account-service2003
  2. 改 POM
<dependencies>
        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--seata-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>0.9.0</version>
        </dependency>
        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
  1. 寫YML
server:
  port: 2003

spring:
  application:
    name: seata-account-service
  cloud:
    alibaba:
      seata:
        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_account
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml
  1. 組態檔

    • 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 = "123456"
        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"
      }
    }
    
  2. domain

    • CommonResult
    @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);
        }
    }
    
    • Account
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Account {
    
        private Long id;
    
        /**
         * 用戶id
         */
        private Long userId;
    
        /**
         * 總額度
         */
        private BigDecimal total;
    
        /**
         * 已用額度
         */
        private BigDecimal used;
    
        /**
         * 剩余額度
         */
        private BigDecimal residue;
    }
    
  3. Dao介面及實作

    • AccountDao
    @Mapper
    public interface AccountDao {
    
        /**
         * 扣減賬戶余額
         */
        void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
    }
    
    • resources檔案夾下新建mapper檔案夾后添加AccountMapper.xml
    <mapper namespace="com.xiaobai.springcloud.alibaba.dao.AccountDao">
    
        <resultMap id="BaseResultMap" type="com.xiaobai.springcloud.alibaba.domain.Account">
            <id column="id" property="id" jdbcType="BIGINT"/>
            <result column="user_id" property="userId" jdbcType="BIGINT"/>
            <result column="total" property="total" jdbcType="DECIMAL"/>
            <result column="used" property="used" jdbcType="DECIMAL"/>
            <result column="residue" property="residue" jdbcType="DECIMAL"/>
        </resultMap>
    
        <update id="decrease">
            UPDATE t_account
            SET
              residue = residue - #{money},used = used + #{money}
            WHERE
              user_id = #{userId};
        </update>
    
    </mapper>
    
  4. Service介面及實作

    • AccountService
    public interface AccountService {
    
        /**
         * 扣減賬戶余額
         * @param userId 用戶id
         * @param money 金額
         */
        void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
    }
    
    • AccountServiceImpl
    /**
     * 賬戶業務實作類
     * Created by zzyy on 2019/11/11.
     */
    @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(30); } catch (InterruptedException e) { e.printStackTrace(); }
            accountDao.decrease(userId,money);
            LOGGER.info("------->account-service中扣減賬戶余額結束");
        }
    }
    
  5. Controller

@RestController
public class AccountController {

    @Resource
    AccountService accountService;

    /**
     * 扣減賬戶余額
     */
    @RequestMapping("/account/decrease")
    public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
        accountService.decrease(userId,money);
        return new CommonResult(200,"扣減賬戶余額成功!");
    }
}
  1. Config配置

    • MyBatisConfig
    @Configuration
    @MapperScan({"com.xiaobai.springcloud.alibaba.dao"})
    public class MyBatisConfig {
    }
    
    • DataSourceProxyConfig
    @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();
        }
    
    }
    
  2. 主啟動

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataAccountMainApp2003
{
    public static void main(String[] args)
    {
        SpringApplication.run(SeataAccountMainApp2003.class, args);
    }
}

訂單/庫存/賬戶業務微服務測驗

  1. 添加訂單:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
  2. 初次測驗,t_order、t_storage、t_account 三個 表添加資料均符合預期;
  3. 給 AccountServiceImpl 添加超時,再次測驗;
  4. 再次測驗,當庫存和賬戶金額扣減后,訂單狀態并沒有設定為已經完成,沒有從零改為1,而且由于feign的重試機制,賬戶余額還有可能被多次扣減;
  5. 使用 @GlobalTransactional 對 OrderServiceImpl 進行全域事務處理;
  6. 再次測驗,下單后資料庫資料并沒有任何改變,記錄都添加不進來,

補充內容

再看TC/TM/RM三大組件

image-20220829221013662

分布式事務的執行流程:

  1. TM 開啟分布式事務(TM 向 TC 注冊全域事務記錄);
  2. 按業務場景,編排資料庫、服務等事務內資源(RM 向 TC 匯報資源準備狀態 );
  3. TM 結束分布式事務,事務一階段結束(TM 通知 TC 提交/回滾分布式事務);
  4. TC 匯總事務資訊,決定分布式事務是提交還是回滾;
  5. TC 通知所有 RM 提交/回滾 資源,事務二階段結束,

AT模式如何做到對業務的無侵入

在一階段,Seata 會攔截 業務 SQL

  1. 決議 SQL 語意,找到 業務 SQL 要更新的業務資料,在業務資料被更新前,將其保存成 before image ;
  2. 執行 業務 SQL 更新業務資料,在業務資料更新之后,
  3. 其保存成 after image,最后生成行鎖,

以上操作全部在一個資料庫事務內完成,這樣保證了一階段操作的原子性,

image-20220829221333370

二階段如是順利提交的話,因為“業務 SQL”在一階段已經提交至資料庫,所以Seata框架只需 將一階段保存的快照資料和行鎖刪掉,完成資料清理即可,

image-20220829221430273

二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的 業務 SQL ,還原業務資料,回滾方式便是用 before image 還原業務資料;但在還原前要首先要校驗臟寫,對比 資料庫當前業務資料after image ,如果兩份資料完全一致就說明沒有臟寫,可以還原業務資料,如果不一致就說明有臟寫,出現臟寫就需要轉人工處理,

image-20220829221611030

原始碼邏輯:

image-20220829222715554

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/539135.html

標籤:Java

上一篇:Spring回圈依賴

下一篇:微信支付 “商家轉賬到零錢”

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more