SpringCloud學習第一季-4

目錄

16.SpringCloud Alibaba Nacos服務注冊和配置中心

SpringCloud Alibaba簡介

1. 為什么出現 SpringCloud Alibaba

2. SpringCloud Alibaba帶來了什么

2.1 能干什么

2.2 去哪里下載

2.3 怎么玩

3. 學習資料的獲取

17.SpringCloud Alibaba Nacos服務注冊和配置中心

一、 Nacos簡介

1 是什么

2 能干什么

3 下載地址

4 各注冊中心比較

二、 安裝并運行Nacos

三、 Nacos作為服務注冊中心演示

1 基于Nacos的服務提供者

1.1 建moudle

1.2 pom

1.3 yml

1.4 主啟動類

1.5 業務類

1.6 測試

1.7 相同配置再新建一個module 9002

2 基于Nacos的服務消費者

2.1 新建module cloudalibaba-consumer-nacos-order83

2.2 pom

2.3 yml

2.4 主啟動類

2.5 業務類

2.6 測試

3. 服務注冊中心對比

3.1 Nacos與CAP

3.2 CP與AP的切換

四、 Nacos作為服務配置中心演示

1. Nacos作為配置中心——基礎配置

1.1 建module cloudalibaba-config-nacos-client3377

1.2 pom

1.3 yml

1.4 主啟動類

1.5 業務類

1.6 在Nacos中添加配置信息

1.7 測試

2. Nacos作為配置中心——分類配置

2.1 分布式開發中的多環境多項目管理問題

2.2 Nacos的圖形化管理界面

2.3 Namespace+Group+Data ID三者關系?為什么這么設計?

3. 三種方案加載配置

3.1 DataID方案

3.2 Group方案

1新建一個配置文件,添加到RPOD_GROUP分組

2新建一個配置文件,添加到TEST_GROUP分組

3在config下增加一條group的配置即可。可配置為PROD_GROUP或TEST_GROUP

4測試:

3.3 namesapce方案

1.新建Namesapce

2.回到服務管理-服務列表查看

3.在這兩個新建的namespace中分別新建配置文件

4.修改3377的yml文件

5.測試

3.4 總結

4. Nacos集群和持久化配置(重點)

4.1 官網說明

4.2 Nacos嵌入式數據庫derby切換到mysql-windows單機版derby到mysql切換步驟

1.首先在nacos安裝目錄的conf目錄下找到一個名為nacos-mysql.sql的sql腳本

2.在conf目錄下找到application.properties

3.重新啟動nacos,可以看到是個全新的空記錄界面,以前是記錄進derby

4.在naocs新建配置時,會自動保存到mysql數據庫中

(踩坑)1.1.4版本nacos使用外部數據庫Mysql8 (這種方式我也親自試了OK)

5.修改nacos源碼(可行)

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

4.3.1 Nacos 下載Linux版

4.3.2 集群配置步驟(重點)

4.4 微服務cloudalibaba-provider-payment9002啟動注冊進nacos集群

18.SpringCloud Alibaba Sentinel實現熔斷與限流


16.SpringCloud Alibaba Nacos服務注冊和配置中心

SpringCloud Alibaba簡介

1. 為什么出現 SpringCloud Alibaba

Spring Cloud Netflix項目進入維護模式
Spring Cloud Greenwich.RC1 available now
進入維護模式意味著Spring Cloud Netflix 將不再開發新的組件
我們都知道Spring Cloud 版本迭代算是比較快的,因而出現了很多重大ISSUE都還來不及Fix就又推另一個Release了。進入維護模式意思就是目前一直以后一段時間Spring Cloud Netflix提供的服務和功能就這么多了,不在開發新的組件和功能了。以后將以維護和Merge分支Full Request為主
新組件功能將以其他替代平代替的方式實現
?

2. SpringCloud Alibaba帶來了什么

官網

2.1 能干什么

服務限流降級:默認支持 Servlet、Feign、RestTemplate、Dubbo 和 RocketMQ 限流降級功能的接入,可以在運行時通過控制臺實時修改限流降級規則,還支持查看限流降級 Metrics 監控。

服務注冊與發現:適配 Spring Cloud 服務注冊與發現標準,默認集成了 Ribbon 的支持。

分布式配置管理:支持分布式系統中的外部化配置,配置更改時自動刷新。

消息驅動能力:基于 Spring Cloud Stream 為微服務應用構建消息驅動能力。

阿里云對象存儲:阿里云提供的海量、安全、低成本、高可靠的云存儲服務。支持在任何應用、任何時間、任何地點存儲和訪問任意類型的數據。

分布式任務調度:提供秒級、精準、高可靠、高可用的定時(基于 Cron 表達式)任務調度服務。同時提供分布式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到所有 Worker(schedulerx-client)上執行。

2.2 去哪里下載

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
之前在父工程的pom文件中已經引入了alibaba的依賴

2.3 怎么玩

3. 學習資料的獲取

官網:Spring Cloud Alibaba
英文:
https://github.com/alibaba/spring-cloud-alibaba
Spring Cloud Alibaba Reference Documentation
中文:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

17.SpringCloud Alibaba Nacos服務注冊和配置中心

對應著我們前面學的:Eureka/Consul/Zookeeper(服務注冊) Config+Bus(配置中心)

一、 Nacos簡介

Nacos——Naming Configuration Service

1 是什么

●一個更易于構建云原生應用的動態服務發現、配置管理和服務的管理平臺。
●Nacos:Dynamic Naming and Configuration Service
●Nacos就是注冊中心 + 配置中心的組合, Nacos = Eureka + Config + Bus

2 能干什么

替代Eureka做服務注冊中心;替代Config做服務配置中心

3 下載地址

https://github.com/alibaba/Nacos
官網文檔:Redirecting to: https://nacos.io/
Spring Cloud Alibaba Reference Documentation

4 各注冊中心比較

二、 安裝并運行Nacos

本地需要準備java8+maven環境。
官網下載:https://github.com/alibaba/nacos/releases
老師安裝的1.1.4,我這里使用的最新版2.0.3
解壓安裝包,運行bin目錄下的startup.cmd
默認的是集群模式,我們需要單機啟動cmd中輸入:
startup.cmd -m standalone(nacos單機模式啟動命令) 即可通過單機模式啟動

命令運行成功后直接訪問http://localhost:8848/nacos 默認賬號密碼都是nacos

啟動成功。

三、 Nacos作為服務注冊中心演示

官網手冊

1 基于Nacos的服務提供者

1.1 建moudle

cloudalibaba-provider-payment9001

1.2 pom

父工程中需要引入alibaba的依賴,之前已經引入過了

<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>
1.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.4 主啟動類
package com.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.5 業務類
package com.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(value = "/payment/nacos/{id}")  public String getPayment(@PathVariable("id") Integer id)  {  return "nacos registry, serverPort: "+ serverPort+"\t id"+id;  }  
}
1.6 測試

這里不像eureka還要寫注冊中心微服務,直接安裝打開nacos即可。
啟動9001,查看nacos控制臺,注冊成功

訪問http://localhost:9001/payment/nacos/1
?


nacos服務注冊中心+服務提供者9001都OK了

1.7 相同配置再新建一個module 9002

步驟與9001一致,就是端口號改成9002
這里有一個簡單的方法,虛擬映射一個和9001配置相同的9011微服務模塊,端口號是9011


控制臺服務名稱

現在有兩個支付模塊,9001和9002。

2 基于Nacos的服務消費者

Nacos自帶負載均衡

2.1 新建module cloudalibaba-consumer-nacos-order83
2.2 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>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</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>

nacos自帶負載均衡


Ribbon:支持負載均衡,自帶RestTemplate

2.3 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
2.4 主啟動類
package com.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);  }  }
2.5 業務類
package com.springcloud.alibaba.controller;  import org.springframework.beans.factory.annotation.Autowired;  
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 {  @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);  }  }

配置類

package com.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;  @Configuration  
public class ApplicationContextBean {  @Bean  @LoadBalanced //使用@LoadBalanced注解賦予RestTemplate負載均衡的能力  RestTemplate getRestTemplate(){  return new RestTemplate();  }  }
2.6 測試

啟動9001、9002、83
nacos控制臺


測試鏈接:http://localhost:83/consumer/payment/nacos/1
發現9001與9002交替出現,即輪詢負載均衡。

可能出現的問題:
當消費者通過http://nacos-payment-provider去調用微服務時拋出UnknowHostException:未知主機名異常。
這是因為nacos-payment-provider對應兩個微服務實例,他不知道用哪個微服務,所以報錯。
解決:加上負載均衡注解 @LoadBalanced

3. 服務注冊中心對比

3.1 Nacos與CAP

Nacos可以在CP與AP之間切換

3.2 CP與AP的切換

C(consistency):所有節點在同一時間看到的數據是一致的,強一致性;
A(availability):的定義是所有的請求都會收到響應,最起碼有一個兜底回復,高可用性。

何時選擇使用何種模式?
AP:
一般來說,如果不需要存儲服務級別的信息且服務實例是通過nacos-client注冊,并能夠保持心跳上報,那么就可以選擇AP模式。當前主流的服務如 Spring cloud 和 Dubbo 服務,都適用于AP模式,AP模式為了服務的可能性而減弱了一致性,因此AP模式下只支持注冊臨時實例。
CP:
如果需要在服務級別編輯或者存儲配置信息,那么 CP 是必須,K8S服務和DNS服務則適用于CP模式。
CP模式下則支持注冊持久化實例,此時則是以 Raft 協議為集群運行模式,該模式下注冊實例之前必須先注冊服務,如果服務不存在,則會返回錯誤。

怎么切換:
curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

四、 Nacos作為服務配置中心演示

以前我們將所有的配置信息寫到了GitHub上,用Config+Bus來進行自動刷新和動態的更新。
現在我們可以直接把配置文件寫進Nacos,然后再用Nacos做類似于config這樣的功能,直接從Nacos上抓取我們的配置信息。

1. Nacos作為配置中心——基礎配置

主要是添加一個:nacos-config依賴

com.alibaba.cloudspring-cloud-starter-alibaba-nacos-config

1.1 建module cloudalibaba-config-nacos-client3377
1.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.3 yml

這里需要配置兩個,一個bootstrap和一個application。
原因:Nacos同springcloud-config一樣,在項目初始化時,要保證先從配置中心進行配置拉取,拉取配置之后,才能保證項目的正常啟動。
springboot中配置文件的加載是存在優先級順序的,bootstrap優先級高于application。
全局的放在:bootstrap.yml 自己的放在:application.yml
bootstrap:
微服務端口:3377,服務名nacos-config-client,注冊進nacos:localhost:8848 作為配置客戶端

# 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 # 表示開發環境

bootstrap + application 就表示我要去配置中心找名為dev.yaml的文件。

1.4 主啟動類
package com.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.5 業務類

通過Spring Cloud原生注解@RefreshScope 實現配置自動更新

package com.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 //在控制器類加入@RefreshScope注解使當前類下的配置支持Nacos的動態刷新功能。  
public class ConfigClientController {  @Value("${config.info}")  private String configInfo;  @GetMapping("/config/info")  public String getConfigInfo(){  return configInfo;  }  }
1.6 在Nacos中添加配置信息

Nacos中的匹配規則
理論:
●Nacos中的dataid的組成格式與SpringBoot配置文件中的匹配規則
官網:Nacos 融合 Spring Cloud,成為注冊配置中心 | Nacos 官網

注意nacos只識別yaml,不支持yml。
最終公式:{spring.application.name}-{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
結果: nacos-config-client-dev.yaml
公式圖解說明

實操
配置新增:
?


自己新建一個配置
?

1.7 測試

啟動3377
發送請求:http://localhost:3377/config/info

測試成功
自帶動態刷新:修改nacos中的yaml配置文件,再次調用查看配置,發現配置刷新了。

2. Nacos作為配置中心——分類配置

2.1 分布式開發中的多環境多項目管理問題

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

2.2 Nacos的圖形化管理界面

配置管理:

命名空間:

2.3 Namespace+Group+Data ID三者關系?為什么這么設計?

類似Java里面的package名和類名,最外層的namespace是可以用于區分部署環境的,Group和DataID邏輯上區分兩個目標對象。
三者情況

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

3. 三種方案加載配置

3.1 DataID方案

指定spring.profile.active和配置文件的DataID來使不同環境下讀取不同的配置
默認空間+默認分組+新建dev和test兩個DataID
●dev配置DataID,上一講配置過
●新建test配置DataID

這里命名空間是默認的public,Group也是默認的。

通過spring.profile.active屬性就能進行多環境下配置文件的讀取

重啟3377 測試http://localhost:3377/config/info,成功讀取到test配置下的config.info

3.2 Group方案

默認Group是DEFAULT_GROUP,現在通過Group實現環境分區

1新建一個配置文件,添加到RPOD_GROUP分組

2新建一個配置文件,添加到TEST_GROUP分組

界面就可以看到我們配置的文件了

3在config下增加一條group的配置即可。可配置為PROD_GROUP或TEST_GROUP

4測試:

3.3 namesapce方案
1.新建Namesapce

2.回到服務管理-服務列表查看

3.在這兩個新建的namespace中分別新建配置文件

4.修改3377的yml文件

bootstrap:

5.測試

3.4 總結

DataID方案是在默認namesapce和默認Group下,創建兩個不同的DataID。
Group方案是在默認namespace下,新建兩個DataID相同的配置文件,通過指定不同的分組來讀取不同的配置。
Namespace方案,是相同的Group,相同的DataID,創建并指定不同的namespace來讀取不同配置。

4. Nacos集群和持久化配置(重點)

我們之前在學eureka的時候,配置了兩個eureka注冊中心微服務。學Nacos時,不用我們單獨新建注冊中心微服務模塊了,直接安裝使用即可,很方便。
但是:如果這個注冊中心掛了怎么辦?我們目前就開了一個Nacos程序,也沒有配置集群。顯然,實際情況中不可能只有一個Nacos注冊中心,因此需要用到nacos集群。
集群官網文檔

4.1 官網說明

1.架構圖:

這里vip表示virtual ip(虛擬IP)

2.要將配置持久化到數據庫中:MySQL 不用nacos內嵌的數據庫

3.通俗易懂的Nacos集群架構圖

4.重點說明:
默認Nacos使用嵌入式數據庫實現數據的存儲,我們重啟Nacos后,以前的配置文件不會消失。
但是,如果啟動多個默認配置下的Nacos節點,數據存儲是存在一致性問題的。每個nacos都有自己獨立的嵌入式數據庫,存放的數據不一致。
為了解決這個問題,Nacos采用了集中式存儲的方式來支持集群化部署,目前只支持MySQL的存儲。
nacos支持的三種部署模式:
●單機模式:用于測試和單機使用
●集群模式:用于生產環境,確保高可用
●多集群模式:用于多數據中心場景

4.2 Nacos嵌入式數據庫derby切換到mysql-windows單機版derby到mysql切換步驟

Nacos默認自帶的是嵌入式數據庫derby,那么如果做集群時每個nacos都自帶一個derby,那么就有三個存儲配置穩健的數據庫,顯然數據的統一性存在問題。

1.首先在nacos安裝目錄的conf目錄下找到一個名為nacos-mysql.sql的sql腳本

然后執行nacos-mysql.sql腳本
注意:數據庫nacos_config需要自己創建
新建完數據庫以后打開這個文件復制sql執行一下。
nacos-mysql.sql(10 KB)

2.在conf目錄下找到application.properties

到這一步的時候請再次確認一下你的nacos版本號是多少,我因為自己沒注意,


每次啟動的時候都可以看到,或者登錄web頁面的時候,我這里用的版本竟然是2.0.3,所以不能按照視頻里的來,配置寫法根本就不一樣,看下圖解釋

3.重新啟動nacos,可以看到是個全新的空記錄界面,以前是記錄進derby

在bin目錄下的cmd 輸入 startup.cmd -m standalone 執行,這里還是單機模式,只不過數據庫從derby遷移到了mysql

可以看到,重啟之后之前的配置都沒了,說明遷移成功。

4.在naocs新建配置時,會自動保存到mysql數據庫中

到數據庫中查看一下有沒有生產新的數據

(踩坑)1.1.4版本nacos使用外部數據庫Mysql8 (這種方式我也親自試了OK)

后面Seata章節由于Nacos、sentinel、seata存在版本對應關系,因此需要安裝1.1.4版本nacos。
我在安裝nacos1.1.4配置MYSQL8的application.properties后,報錯:

嘗試了如下方法:
1在nacos\plugins\mysql(自己創建)文件夾下放mysql8對應版本的jar包(不管用)
2修改application.properties文件名為bootstrap.properties:可以正常啟動nacos,但是數據庫并沒有從derby切換為mysql
3添加時區:

一般我在使用mysql8的時候都會添加上時區,仍然不管用。

5.修改nacos源碼(可行)

找到nacos官網 Nacos 快速開始 | Nacos 官網
點擊

然后一直翻,找到1.1.4
修改nacos源碼
下載nacos1.1.4源碼,修改父工程pom的mysql-connector-java的版本,將5.1.34改成自己mysql對應的版本
?


這里一改,naming模塊下的com.alibaba.nacos.naming.healthcheck.MysqlHealthCheckProcessor就會報錯 ,

把import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;改為import com.mysql.cj.jdbc.MysqlDataSource;
?


這里我注釋的一定要刪掉(這里我注釋掉主要是為了區分),不然會報錯:


然后在源碼根目錄下打開cmd,使用maven打包(實際我是直接在idea里面打包的)
執行:mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U

打包好的nacos存放在根目錄\distribution\target目錄下 ,當前項目目錄下去找

這個壓縮包就相當于我們從官網上下的。然后復制到你想解壓的地方,解壓。
隨后,配置conf\application.properties里面的數據庫:

serverTimezone=UTC不要忘記了。(每次配置mysql8,都要帶上這個設置)
然后啟動nacos:

啟動成功,我們試一下,能不能存到數據庫中,新建一個配置,然后查看數據庫中是否生成!

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

一定要先把環境準備好! centos7/Ubuntu/ + maven(3.2+)+mysql(5.6.5+)+JDK1.8 一站式部署環境配置地址: Linux服務器服務搭建及項目部署超詳細 - 哩個啷個波 - 博客園
預計需要1個Nginx+3個nacos注冊中心+1個mysql

4.3.1 Nacos 下載Linux版

下載nacos-linux:https://github.com/alibaba/nacos/releases/tag/2.0.3 我放在了/usr/local 下
解壓: tar -zxvf nacos-server-2.0.3.tar.gz
cp -r nacos /mynacos/ 遞歸拷貝nacos 文件夾到mynacos文件夾下 (我這里沒有執行這步)

4.3.2 集群配置步驟(重點)

(1) Linux服務器上mysql數據庫配置
跟windows一樣,linxu的nacos 在 /nacos/conf 目錄下有一個nacos-mysql.sql的sql腳本
我是通過SQLyog客戶端遠程連接,然后執行。
安裝MySQL可以參考我這里的文章: Linux服務器服務搭建及項目部署超詳細 - 哩個啷個波 - 博客園
直接使用連接遠程的方式去執行創建mysql的表。表創建成功以后我們就可以去Linux服務器上面的MySQL去驗證這些表是否已經創建好了。
?


(2) application.properties 配置
/usr/local/nacos/conf 下,修改內容:數據庫密碼自己要配置自己的并且要配置正確

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

配置的時候注意一下幾個地方,但是如果你的nacos版本是2.0以上,我自己用的那么這個配置文件中是不需要手動寫或者復制的,在注釋里面是可以找到的,只需要打開注釋就可以了。

(3) Linux服務器上nacos的集群配置cluster.conf
梳理出3臺nacos集器的不同服務端口號 3333 4444 5555

cp cluster.conf.example cluster.conf  
vim cluster.conf  

查找自己的ip地址 或者使用命令ip addr

(4) 編輯Nacos的啟動腳本startup.sh,使它能夠接受不同的啟動端口
/usr/local/nacos/bin 目錄下有startup.sh,平時單機版的啟動,都是./startup.sh即可。
集群啟動,我們希望可以類似其它軟件的shell命令,傳遞不同的端口號啟動不同的nacos實例。
命令:./startup.sh -p 3333 表示啟動端口號為3333的nacos服務器實例,和上一步的cluster.conf配置的一致。
注意:2.0版本的-p被占用了,可以用大寫P或者其他字母,剛改完瀏覽器會有延遲,過一會才會好。
●1.1.4 版本修改前后對比方式: 下面的信息一定要配置對 PORT=$OPTARG這里是大寫O,可不是0,我寫錯了第一次的時候 ,下面是-Dserver.port=${PORT}

|

●2.0.3版本修改前后對比

修改前

修改后

如果防火墻沒關,記得將3333,4444,5555端口號設置可訪問
最終只能啟動一個nacos,嘗試各種辦法未解決,更換nacos1.1.4后沒有問題。
(番外)nacos linux踩坑必看
最開始我用的nacos2.0.3,到集群那就出現了問題,只能開一個nacos,剩下兩個開起來就報錯。
嘗試了各種方法,改JVM配置等等,還是不行。最后選擇使用跟視頻里面一樣的nacos版本1.1.4
使用1.1.4的時候數據庫的配置 /usr/local/mynacos/nacos/conf/ (1.1.4的安裝路徑) application.properties 文件使用mysql8+ 需要加上時區 2.0.3 不需要

cluster.conf 還是跟之前一樣配置;
startup.sh 跟老師一樣的改法,然后需要將JVM的配置修改,因為我的虛擬機只有4G內存,所以我把JVM配置調整為如下:

重啟方式:
?


?


上面啟動完以后一定要去日志表里面看一下有沒有報錯信息ERROR,日志啟動的可能比較慢,耐心等待一下,沒有的話用 ps -ef|grep nacos 看一下服務是不是正常啟動著

然后分別執行開啟3333、4444、5555三個nacos,成功開啟nacos集群

使用nacos2+的朋友可以嘗試開三臺虛擬機,參考官方手冊配置集群nacos官方手冊
注意:用nacos1.4以上的,貌似就用不了這個模擬集群的方法了,老實復制3個nacos文件夾再逐個設置端口號啟動 同一臺機啟動多個nacos的問題

(5) Nginx的配置,由它作為負載均衡器
Nginx基礎
/usr/local/nginx/conf 下nginx.conf文件為默認配置文件 啟動nginx通過-c可以指定配置文件啟動。我這里將原來的配置文件拷貝為nginx_nacos.conf

啟動nginx,cd到/usr/local/nginx/sbin下 ,執行:
./nginx -c /usr/local/nginx/conf/nginx_nacos.conf
別忘了防火墻開啟1111端口的訪問:
firewall-cmd --add-port=1111/tcp --permanent
firewall-cmd --reload

(6) 測試
測試通過nginx訪問nacos,成功登陸

然后我們新建一個配置文件:

成功存到Linux的數據庫中!

4.4 微服務cloudalibaba-provider-payment9002啟動注冊進nacos集群

修改9002的application.yml文件。

做監控需要把這個全部暴露出來

18.SpringCloud Alibaba Sentinel實現熔斷與限流

一、Sentinel

https://github.com/alibaba/Sentinel 中文
Sentinel 是輕量級的流量控制、熔斷降級Java庫;功能類似于Hystrix
Web端界面特點:采用的是懶加載模式,服務必須被請求一次后才能在界面中看到,這點需要注意了


下載地址


怎么玩:
入門文檔
服務使用中的各種問題:服務雪崩、服務降級、服務熔斷、服務限流

二、安裝Sentinel控制臺

Sentinel分為兩個部分:
●核心庫(Java客戶端)不依賴任何框架/庫,能夠云星宇所有Java運行時環境,同時對Dubbo/Spring Cloud等框架也有較好的支持——后臺;
●控制臺(Dashboard)基于Spring Boot開發,打包后可以直接運行,不需要額外的Tomcat等應用容器——前臺 8080

安裝步驟
下載到本地sentinel-dashboard-1.8.2.jar
運行命令:
前提需要Java8,且8080端口不能被占用;java -jar sentinel-dashboard-1.8.2.jar (Sentinel啟動命令)

訪問 localhost:8080,賬號密碼均為sentinel

三、初始化演示工程

3.1 啟動Nacos8848

3.2 新建Module cloudalibaba-sentinel-service8401

3.2.1 pom

以后基本上nacos 跟sentinel一起配置

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-sentinel-service8401</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <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>  </project>
3.2.2 yaml

spring.cloud.sentinel.transport.port 端口配置會在應用對應的機器上啟動一個 Http Server,該 Server 會與 Sentinel 控制臺做交互。
比如 Sentinel 控制臺添加了1個限流規則,會把規則數據push給這個Http Server接收,Http Server再將規則注冊到Sentinel中。
spring.cloud.sentinel.transport.port:指定與Sentinel控制臺交互的端口,應用本地會啟動一個占用該端口的Http Server

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: '*'
3.2.3 主啟動類
package com.cloudalibaba.sentinel;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  @EnableDiscoveryClient  
@SpringBootApplication  
public class MainApp8401 {  public static void main(String[] args) {  SpringApplication.run(MainApp8401.class, args);  }  }
3.2.4 業務類

流量控制controller:FlowLimitController

package com.cloudalibaba.sentinel.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.3 測試

啟動Sentinel8080 java -jar sentinel-dashboard-1.8.2.jar、啟動微服務8401
查看Sentinel控制臺,發現什么也沒有。

原因:Sentinel采用懶加載機制

執行一下:http://localhost:8401/testA

sentinel8080正在監控微服務8401

四、流控規則

流量限制控制規則,分為:流控模式和流控效果

各選項含義:

4.1 流控模式

流控模式有三種:直接、關聯、鏈路

4.1.1 直接(默認)+快速失敗(默認)
(1) QPS直接快速失敗

QPS:query per second,每秒鐘的請求數量,當調用該api的QPS達到閾值時,進行限流。
下面設置表示1秒鐘內查詢一次就是OK,若QPS>1,就直接-快速失敗,報默認錯誤

編輯好以后返回界面可以看到自己的設置
?


測試一下,當/testA的訪問超過1次/s是,頁面報錯。被Sentinel限流,還能繼續請求只要QPS<=1。


小結:表示1秒鐘內請求次數大于1,就直接快速失敗,報默認錯誤

直接調用默認的報錯信息在技術上是OK的,但是是否應該有自定義的后續處理?應該有類似Hystrix的fallback的兜底方法。

(2) 線程數直接快速失敗

當調用該api的線程數達到閾值的時候,進行限流。
與QPS直接快速失敗不同的是,QPS情況下限制的是流量,比如銀行的人流量只能是1人/s,也就是說每次只能一個人進入銀行辦理業務;而線程數就好比銀行只有一個窗口開放,一群人都可以進入銀行,但是每次只能處理一個人的業務。

演示效果:
先修改一下8401的業務類

然后重啟8401,測試/testA,最好用兩個瀏覽器訪問,效果更明顯

4.1.2 關聯

當關聯的資源達到閾值時,就限流自己。比如當與A關聯的資源B達到閾值后,就限流A自己。
支付接口達到閾值,限流下訂單的接口。

  1. 配置
    設置效果:當關聯資源/testB的qps閥值超過1時,就限流/testA的Rest訪問地址,當關聯資源到閾值后限制配置好的資源名

  1. 測試
    單獨訪問testB成功。

postman模擬并發密集訪問testB
先創建一個集合,名字自己隨便取。

然后將創建的訪問/testB的請求保存在創建的集合中
設定集合運行參數,20個線程,每次間隔0.3s訪問一次(QPS>1),執行:

然后再訪問/testA,發現被限流,等postman執行完畢,testA又可以訪問了

4.1.3 鏈路

需要測試鏈路的話,springcloud 阿里巴巴版本需要2.1.1.RELEASE以上,在父工程的pom中修改,不要直接在子module的pom中修改,版本有對應關系,不然報錯。

●Sentinel從1.6.3版本開始,Sentinel Web Filter 默認收斂所有的URL入口的Context,因此鏈路限流不生效
●1.7.0版本開始,官方在CommomFilter中引入了WEB_CONTEXT_UNIFY這個init parameter,用于控制是否收斂context,將其配置為false即可根據不同的URL進行鏈路限流
●Spring Cloud Alibaba 在2.1.1.RELEASE版本后,可以通過配置spring.cloud.sentinel.web-context-unify=false關閉


https://github.com/alibaba/Sentinel/issues/1313

測試
啟動8401,給/testA設置鏈路+快速失敗流控規則:

這里入口資源就是簇點鏈路中,資源名稱的上一級。
訪問http://localhost:8401/linktestA ,多次刷新出現限流

,但是這種情況我個人覺得跟直接快速失敗區別不大,只直接是監控/testA資源,而鏈路是監控/testA的資源入口sentinel_web_servlet_context。

然后我又參看了其他博客,流控模式——鏈路。增加了FlowLimitService 修改了controller

分別通過/linktetA 和 /linktestB都是message的入口,然后設置/linktestA入口的流量限制,發現不起作用。。。

4.2 流控效果

快速失敗在上面的流控模式演示過了,他是默認的流控效果,直接失敗,拋出異常。源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

4.2.1 warm up 預熱

官網 源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
公式:閾值除以coldFactor(默認值為3),經過預熱時長后才會達到閾值

配置圖片說明

測試
5秒之前得閾值不是10,5秒時間過了以后才能達到設定的閾值,得有前戲才行,不能一上來就。。。
?


狂點請求,可以看通過的QPS逐漸增加,最開始會報錯限流,之后就可以抗住10/s的QPS了
**應用場景:**秒殺系統在開啟的瞬間,會有很多流量上來,很有可能把系統打死,預熱方式就是把為了保護系統,可慢慢的把流量放進來,慢慢的把閥值增長到設置的閥值。

4.2.2 排隊等待

官網 源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

勻速排隊,讓請求以均勻的速度通過,閥值類型必須設成QPS,否則無效。

/testA的QPS最大為1,超過的話就排隊等待,等待的超時時間為20000ms。

修改一下業務代碼,把線程名打印出來以驗證是否排隊。


postman配置成20個請求,延時300毫秒


測試
postman:
遍歷20次,耗時時間接近20,說明每秒請求限制為1個。

也可以監測idea控制臺輸入語句看看頻率是多少。但是我發現Sentinel配置的話要檢查一下配置是不是還在,刷新頁面以后看看是不是就消失了。

可以看到剛好滿足1s一個請求,說明請求的執行進行了排隊。

五、降級規則(熔斷規則)

官網
老版本的Sentinel的斷路器是沒有半開狀態的,半開的狀態系統自動去檢測是否請求有異常,沒有異常就關閉斷路器恢復使用,有異常則繼續打開斷路器不可用。具體可以參考Hystrix。(在Hystrix中 快照時間窗口是值 閾值檢測時間 ,而休眠時間窗口是指 斷路器從開啟到半開狀態間隔的時間)
新版本的Sentinel加入了半開狀態!

5.1 降級策略

Sentinel 熔斷降級會在調用鏈路中某個資源出現不穩定狀態時(例如調用超時或異常比例升高),對這個資源的調用進行限制,讓請求快速失敗,避免影響到其它的資源而導致級聯錯誤。
當資源被降級后,在接下來的降級時間窗口之內,對該資源的調用都自動熔斷(默認行為是拋出 DegradeException)。

5.1.1 慢調用比例,RT(平均響應時間,秒級)

老版本:
?


新版本:
●慢調用比例 (SLOW_REQUEST_RATIO):選擇以慢調用比例作為閾值,需要設置允許的慢調用 RT(即最大的響應時間),請求的響應時間大于該值則統計為慢調用。當單位統計時長(statIntervalMs)內請求數目大于設置的最小請求數目,并且慢調用的比例大于閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求響應時間小于設置的慢調用 RT 則結束熔斷,若大于設置的慢調用 RT 則會再次被熔斷。(跟豪豬科類似)

實戰測試
業務類中加一個rest 接口,以用于測試:

@GetMapping("/testD")
public String testD()
{//暫停幾秒鐘線程try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }log.info("testD 測試RT");return "------testD";
}

訪問沒有問題

編輯熔斷規則:
在1000ms的統計時間內,總請求數(超過5次)中有80%的請求最大RT超過了200ms,那么觸發熔斷機制,熔斷2s。

jmeter壓測:
設置線程數10個,響應時間1秒,
?


添加取樣器,HTTP請求
?


添加請求路徑

永遠一秒鐘打進來10個線程(大于5個了)調用testD,我們希望200毫秒處理完本次任務,如果超過200毫秒還沒處理完,在未來s秒鐘的時間內,斷路器打開(保險絲跳閘)微服務不可用,保險絲跳閘斷電了。
testD被熔斷了


從實時監控也可以看到,看看什么時間熔斷的。
后續我停止jmeter,沒有這么大的訪問量了,斷路器半開到關閉(保險絲恢復),微服務恢復OK。

5.1.2 異常比例

新版本:
●異常比例 (ERROR_RATIO):當單位統計時長(statIntervalMs)內請求數目大于設置的最小請求數目,并且異常的比例大于閾值,則接下來的熔斷時長內請求會自動被熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。異常比率的閾值范圍是 [0.0, 1.0],代表 0% - 100%。

實戰測試
修改業務類:

@GetMapping("/testD")
public String testD()
{log.info("testD 測試RT");int age = 10/0;return "------testD";
}

編輯熔斷規則:
?


1000ms統計時長內,大于5次的請求中超過80%的請求出現異常,則熔斷2s。

jmeter壓測:
單獨訪問一次,必然來一次報錯一次(int age = 10/0),調一次錯一次;開啟jmeter后,直接高并發發送請求,多次調用達到我們的配置條件了。斷路器開啟(保險絲跳閘),微服務不可用了,不再報錯error而是服務降級了。

testD被熔斷。
停掉jemeter后過2s。報/zero錯誤,因為業務類中有個10/0。

5.1.3 異常數

老版本:
?


時間窗口一定要大于等于60秒。
新版本
●異常數 (ERROR_COUNT):當單位統計時長內的異常數目超過閾值之后會自動進行熔斷。經過熔斷時長后熔斷器會進入探測恢復狀態(HALF-OPEN 狀態),若接下來的一個請求成功完成(沒有錯誤)則結束熔斷,否則會再次被熔斷。

實戰測試
修改業務類:

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

編輯熔斷規則:

手動測試:


這里我測試有bug,雖然熔斷了但是熔斷時長不是我配置的5s,大約是一分鐘,統計時長也不是1s,好像也是一分鐘,同時沒達到最小請求數,只達到3次異常就直接熔斷了。雖然我用的新版本,但是邏輯好像跟老版本的一樣?

六、熱點key限流

6.1 基本介紹

官網
何為熱點:熱點即經常訪問的數據,很多時候我們希望統計或者限制某個熱點數據中訪問頻次最高的TopN數據,并對其訪問進行限流或者其它操作。比如:
●商品 ID 為參數,統計一段時間內最常購買的商品 ID 并進行限制
●用戶 ID 為參數,針對一段時間內頻繁訪問的用戶 ID 進行限制
熱點參數限制會統計傳入參數中的熱點參數,并根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限制。熱點參數限流可以看作是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。

Sentinel利用LRU策略統計最近最常訪問的熱電參數,結合令牌桶算法來進行參數級別的流控。熱點參數限流支持集群模式。

6.2 基本使用

兜底防范分為系統默認和客戶自定義;兩種,根據之前的case,都是使用sentinel系統默認的提示:Blocked by Sentinel (flow limiting)。那我們能不能自定義兜底方法呢?類似hystrix,某個方法出問題了,就找對應的兜底降級方法?
類似于@HystrixCommand, 引入@SentinelResource注解。
熱點規則共有資源名、限流模式(只支持QPS模式)、參數索引、單機閾值、統計窗口時長、是否集群6種參數,還有一些高級選項,用到時會詳細介紹。這里會用到注解中的value作為資源名,兜底方法會在后面詳細介紹@SentinelResource注解詳解

注意:
資源名:唯一路徑,默認為請求路徑。此處必須是 @SentinelResource 注解的 value 屬性值,配置@GetMapping 的請求路徑無效)

6.2.1 測試方法

還是在8401的controller中,加入熱點測試方法。

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1, @RequestParam(value = "p2",required = false) String p2){return "------testHotKey";
}
public String dealHandler_testHotKey(String p1,String p2,BlockException exception)
{return "-----dealHandler_testHotKey";
}

注解@SentinelResource(value = "testHotKey", blockHandler = "del_testHotKey")
分析:
●其中 value = "testHotKey" 是一個標識(Sentinel資源名),與rest的/testHotKey對應,這里value的值可以任意寫,但是我們約定與rest地址一致,唯一區別是沒有/
●blockHandler = "del_testHotKey" 則表示如果違背了Sentinel中配置的流控規則,就會調用我們自己的兜底方法del_testHotKey

6.2.2 配置熱點key限流規則

綁定testHotKey資源,把testHotKey對應的第一個參數作為熱點key進行監控。設定熱點限流規則:當該資源的訪問QPS超過1次/s的時候,產生限流并執行自定義的del_testHotKey兜底方法。
簡而言之:方法testHotKey里面第一個參數只要QPS超過每秒1次,馬上降級處理。

6.2.3 測試

1訪問http://localhost:8401/testHotKey?p1=a&p2=b
1次/s正常顯示,迅速點擊兩次,觸發熱點限流,執行自定義兜底方法:

2僅傳入參數p2沒有任何影響: http://localhost:8401/testHotKey?p2=b


3現在開兩個訪問,一個通過jmeter壓測http://localhost:8401/testHotKey?p1=a&p2=b,另外再單獨使用瀏覽器訪問http://localhost:8401/testHotKey?p2=b,發現只帶參數p2訪問沒有任何影響。

4不配置blockeHandler(兜底方法)

觸發熱點限流降級會出現error page,對用戶不友好。

6.3 參數例外項

上述案例演示了第一個參數p1,當QPS超過1秒1次點擊后馬上被限流
特例情況:我們期望p1參數當它是某個特殊值時,它的限流值和平時不一樣,比如當p1的值等于5時,它的閾值可以達到200。

測試
狂點http://localhost:8401/testHotKey?p1=5&p2=b 沒有限流

6.4 其他

手動添加一個異常:


測試直接錯誤頁面。

要注意: Sentinel它只管你有沒有觸發它的限流規則,也可以說只管這個web交互頁面(控制臺)里面的東西。 配置類的東西Sentinel可以管,java異常的錯誤我不管。
@SentinelResource

處理的是Sentinel控制臺配置的違規情況,有blockHandler方法配置的兜底處理;

RuntimeException

int age = 10/0,這個是java運行時報出的運行時異常RunTimeException,@SentinelResource不管

總結:

@SentinelResource主管配置出錯,運行出錯該走異常走異常

七、系統規則(系統自適應限流)
官網
Sentinel 系統自適應限流從整體維度對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 RT、入口 QPS 和并發線程數等幾個維度的監控指標,通過自適應的流控策略,讓系統的入口流量和系統的負載達到一個平衡,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
系統保護規則是應用整體維度的,而不是資源維度的,并且僅對入口流量生效。入口流量指的是進入應用的流量(EntryType.IN),比如 Web 服務或 Dubbo 服務端接收的請求,都屬于入口流量。
系統規則支持以下的模式:
●Load 自適應(僅對 Linux/Unix-like 機器生效):系統的 load1 作為啟發指標,進行自適應系統保護。當系統 load1 超過設定的啟發值,且系統當前的并發線程數超過估算的系統容量時才會觸發系統保護(BBR 階段)。系統容量由系統的 maxQps * minRt 估算得出。設定參考值一般是 CPU cores * 2.5。
●CPU usage(1.5.0+ 版本):當系統 CPU 使用率超過閾值即觸發系統保護(取值范圍 0.0-1.0),比較靈敏。
●平均 RT:當單臺機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。
●并發線程數:當單臺機器上所有入口流量的并發線程數達到閾值即觸發系統保護。
●入口 QPS:當單臺機器上所有入口流量的 QPS 達到閾值即觸發系統保護。

案例——配置全局QPS

不管是/testA還是/testB 只要QPS > 1 整個系統就不能用。
這個粒度太粗,就相當于一個窗口人很多,整個銀行就不接待人了,不太建議使用。

八、@SentinelResource 注解詳解

8.1 按資源名稱限流+后續處理

啟動nacos+sentinel

8.1.1 修改8401

(1) pom
引入我們自定義的公共api jar包

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

(2) 業務類

package com.cloudalibaba.sentinel.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @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 服務不可用");  }  }
8.1.2 配置流控規則——按資源名稱添加流控規則

8.1.3 測試

自測:


觸發流控規則:

8.1.4 問題

如果我們重啟8401會發現之前配置的一些規則都沒有了。難道每次重啟服務器都要重新配置一遍規則嗎?規則如何進行持久化?

8.2 按照Url地址限流+后續處理

通過訪問的URL來限流,會返回Sentinel自帶默認的限流處理信息

8.2.1 修改controller
 @GetMapping("/rateLimit/byUrl")@SentinelResource(value = "byUrl")public CommonResult byUrl(){return new CommonResult(200,"按url限流測試OK",new Payment(2020L,"serial002"));}
8.2.2 設置流控規則及測試

先自測,沒有問題:

按rest URI設置流控規則

觸發流控:


這個沒有自定義的兜底的方法,返回Sentinel自帶的限流處理結果。

8.3 總結以及面臨的問題

不管是@GetMapping(rest url)還是 @SentinelResource,只要是唯一的,就可以作為流控規則的資源名稱。如果沒有自定義自己的兜底方法,那么就使用系統自帶的。

問題:
●依照現有條件,我們自定義的處理方法又和業務代碼耦合在一塊,不直觀。如果都用系統默認的,就沒有體現我們自己的業務要求。
●如果每個業務方法/API接口都添加一個兜底的,那代碼膨脹加劇。
●全局統一的處理方法沒有體現。

8.4 客戶自定義限流處理邏輯

為了解決代碼耦合與膨脹的問題

8.4.1 創建CustomerBlockHandler類用于自定義限流處理邏輯

在CustomerBlockHandler類中統一的處理限流提示、服務降級的說明等等。。

package com.cloudalibaba.sentinel.myHandler;  import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  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");  }  }
8.4.2 修改RateLimitController,使用自定義處理邏輯類
/**  * 自定義通用的限流處理邏輯,  blockHandlerClass = CustomerBlockHandler.class  blockHandler = handleException2 上述配置:找CustomerBlockHandler類里的handleException2方法進行兜底處理  */  
/**  * 自定義通用的限流處理邏輯  */  
@GetMapping("/rateLimit/customerBlockHandler")  
@SentinelResource(value = "customerBlockHandler",  blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")  
public CommonResult customerBlockHandler()  
{  return new CommonResult(200,"按客戶自定義限流處理邏輯");  
}
8.4.3 測試

啟動8401,先測試一次http://localhost:8401/rateLimit/customerBlockHandler

設置流控規則:


觸發流控,看是否是我們自定義提示:


自定義提示出來了!

8.4.4 結構說明

這樣就實現了兜底方法與業務方法的解耦。

8.5 更多屬性說明

注解支持文檔

九、服務熔斷

主要內容:
●sentinel分別整合ribbon+openFeign以及設置fallback
●熔斷框架比較

9.1 Ribbon系列

nacos中整合了Ribbon,所以直接使用nacos就行。啟動nacos和Sentinel。

9.1.1 服務提供者9003/9004

新建cloudalibaba-provider-payment9003/9004兩個一樣的做法

(1) pom

2020版和springcloud和nacos記得引入spring-cloud-starter-loadbalancer依賴

<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-provider-payment9003</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <dependencies>        <!--SpringCloud ailibaba nacos -->  <dependency>  <groupId>com.alibaba.cloud</groupId>  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  </dependency>        <dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->  <groupId>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</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>  </project>
(2) yml

9004 別忘了改端口號

 
server:port: 9003spring:application:name: nacos-payment-providercloud:nacos:discovery:server-addr: localhost:8848 #配置Nacos地址management:endpoints:web:exposure:include: '*'
(3) 主啟動類
package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication 
@EnableDiscoveryClient
public class PaymentMain9003 {  public static void main(String[] args) {  SpringApplication.run(PaymentMain9003.class,args);  }  
}
(4) 業務類

這里圖方便,就沒有連接數據庫。

package com.java.cloud.controller;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.PathVariable;  
import org.springframework.web.bind.annotation.RestController;  import java.util.HashMap;  @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,"565123155216156315153131"));  hashMap.put(2L,new Payment(2L,"565123155216156315153131"));  hashMap.put(3L,new Payment(3L,"565123155216156315153131"));  }  public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){  Payment payment = hashMap.get(id);  CommonResult<Payment> commonResult = new CommonResult<>(200,"from mysql,serPort:"+serverPort,payment);  return commonResult;  }  }
(5) 測試

http://localhost:9003/paymentSQL/1

http://localhost:9004/paymentSQL/1

9.1.2 服務消費者84

新建cloudalibaba-consumer-nacos-order84

(1) pom
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>cloudalibaba-consumer-nacos-order84</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>  <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>org.xu.springcloud</groupId>  <artifactId>cloud-api-commons</artifactId>  <version>1.0.0</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>  
</project>
(2) 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
(3) 主啟動類
package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  @SpringBootApplication  
@EnableDiscoveryClient
public class OrderNacosMain84 {  public static void main(String[] args) {  SpringApplication.run(OrderNacosMain84.class,args);  }  
}
(4) 業務類

因為用的Ribbon,需要使用其提供的RestTemplate

package com.java.cloud.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;  @Configuration  
public class ApplicationContextConfig {  @Bean  @LoadBalanced    public RestTemplate getRestTemplate()  {  return new RestTemplate();  }  }

業務類:

package com.java.cloud.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.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.RestController;  
import org.springframework.web.client.RestTemplate;  import javax.annotation.Resource;  @RestController  
@Slf4j  
public class CircleBreakerController {  public static final String SERVECE_URL = "http://nacos-payment-provider";  @Resource  private RestTemplate restTemplate;  @GetMapping("/consumer/fallback/{id}")  @SentinelResource(value = "fallback")  public CommonResult<Payment> fallback(@PathVariable Long id) throws IllegalAccessException {  CommonResult forObject = restTemplate.getForObject(SERVECE_URL + "/paymentSql/"+id, CommonResult.class, id);  if(id==4){  throw new IllegalAccessException("IllegalAccessException,非法參數異常");  }else if(forObject.getData()==null){  throw new NullPointerException("NullPointerException,空指針異常,沒有獲取到對應id的數據");  }  return forObject;  }  }
(5) 測試

負載均衡實現。

9.1.3 fallback 和 blockHandler

加深@SentinelResource(value = "xxx", fallback = "fffff", blockHandler = "bbbbbb")注解理解:
fallback管運行異常,blockHandler管配置違規。

fallback對應服務降級,就是服務出錯了應該怎么辦(需要有個兜底方法);
blockHandler對應服務熔斷,就是我現在服務不可用,我應該怎么辦,怎么給客戶一個戶提示(同樣需要一個兜底方法)

9.1.4 差異化配置

這里改的業務類代碼均是消費者84端的業務類代碼,不要搞錯了。
降級是服務業務代碼出現錯誤的兜底,熔斷是服務不可用。降級是兜底方法,熔斷是對服務保護時間窗口期服務不可用。

(1) 沒有任何配置

前面我們的84消費端,@SentinelResource里面只配置了value,fallback和blockHandler都沒有配置,該情況下我們測試一下http://localhost:84/consumer/fallback/4

出現了錯誤頁面,error page對客戶不友好,所以我們需要有兜底方法。

(2) 只配置fallback

fallback對應服務降級,就是服務可以正常訪問,但是業務邏輯出現錯誤,需要降級兜底。

訪問http://localhost:84/consumer/fallback/4,可以看到業務異常

(3) 只配置blockHandler

blockHandler對應服務熔斷,當前sentinel配置已經違規(RT數過多、異常過多),服務熔斷后不可用,需要給客戶提示,進行一個熔斷的兜底。

配置sentinel

訪問:http://localhost:84/consumer/fallback/5

如果異常數達到規定的標準就會觸發熔斷,比如我們設置的異常數是2,那么請求兩次以后就會報以下異常
?


這里還是跟之前一樣的bug,很迷。

(4) fallback和blockHandler都配置

同時有降級跟熔斷的兜底方法,當降級達到sentinel配置規則后,觸發熔斷。

配置sentinel
刪除之前的熔斷規則(這里不是刪除,因為每次重啟以后就沒有了),配置流控:

沒有觸發限流時,我們觸發業務異常http://localhost:84/consumer/fallback/4,會被降級方法fallback兜底:

觸發限流時,我們仍然訪問可以觸發業務異常的連接,此時服務已經被限流(可以理解為服務不可用即熔斷),此時觸發的是限流(熔斷blockHandler):
?


也就是說,同時配置fallback:處理業務異常(微服務自身異常,服務降級)和blockHandler:處理觸發sentinel配置(微服務不可用,服務熔斷)時。在沒有違反sentinel規則時,出現業務異常(降級)走fallback方法;違反了sentinel規則時,直接微服務不可用(熔斷),走blockHandler指定的自定義方法。

(5) 異常忽略屬性

可以選擇性的配置當某些異常發生時,不觸發fallback的兜底方法。

測試一下,訪問:http://localhost:84/consumer/fallback/4

直接報錯誤頁面,沒有了降級兜底方法。
其他異常不受影響:

當然,觸發流控之后,仍然通過blockHandler指定的方法進行熔斷兜底。

測試代碼:

package com.java.cloud.controller;  import com.alibaba.csp.sentinel.annotation.SentinelResource;  
import com.alibaba.csp.sentinel.slots.block.BlockException;  
import com.java.cloud.common.CommonResult;  
import com.java.cloud.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.RestController;  
import org.springframework.web.client.RestTemplate;  import javax.annotation.Resource;  @RestController  
@Slf4j  
public class CircleBreakerController {  public static final String SERVECE_URL = "http://nacos-payment-provider";  @Resource  private RestTemplate restTemplate;  @GetMapping("/consumer/fallback/{id}")  
//    @SentinelResource(value = "fallback",fallback = "handlerFallback")  
//    @SentinelResource(value = "fallback",blockHandler = "blockHandler")  @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",  exceptionsToIgnore = {IllegalAccessException.class})  public CommonResult<Payment> fallback(@PathVariable Long id) throws IllegalAccessException {  CommonResult forObject = restTemplate.getForObject(SERVECE_URL + "/paymentSql/"+id, CommonResult.class, id);  if(id==4){  throw new IllegalAccessException("IllegalAccessException,非法參數異常");  }else if(forObject.getData()==null){  throw new NullPointerException("NullPointerException,空指針異常,沒有獲取到對應id的數據");  }  return forObject;  }  public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {  Payment payment = new Payment(id,"null");  return new CommonResult<>(444,"兜底異常handlerFallback,exception內容  "+e.getMessage(),payment);  }  public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {  Payment payment = new Payment(id,"null");  return new CommonResult<>(445,"blockHandler-sentinel限流,無此流水: blockException  "+blockException.getMessage(),payment);  }  
}

9.2 Feign系列

9.2.1 修改84模塊

修改84模塊,Feign組件一般是在消費側。

(1) pom

pom 加入feign的依賴

<!--SpringCloud openfeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
(2) yml

激活Sentinel對Feign的支持

(3) 業務類

后續84的controller不找restTemplate(Ribbon),不是restTemplate去調用payment微服務中的接口。而是通過調用PaymentFeignService,service再去調用payment微服務中的端口。

Feign需要定義一個業務邏輯(service)接口+ @FeignClient注解以調用服務提供者。
新建PaymentFeignService interface:

package com.java.cloud.service;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import com.java.cloud.service.impl.PaymentFallbackService;  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.PathVariable;  
/**  * @auther zzyy  * @create 2019-12-10 17:17  * 使用 fallback 方式是無法獲取異常信息的,  * 如果想要獲取異常信息,可以使用 fallbackFactory參數  */  
@FeignClient(value="nacos-payment-provider",fallback = PaymentFallbackService.class)  
public interface PaymentFeignService {  @GetMapping(value = "/paymentSql/{id}")  public CommonResult<Payment> paymentSql(@PathVariable("id") Long id);  }

調用失敗的兜底方法:

package com.java.cloud.service.impl;  import com.java.cloud.common.CommonResult;  
import com.java.cloud.entities.Payment;  
import com.java.cloud.service.PaymentFeignService;  
import org.springframework.stereotype.Component;  @Component  
public class PaymentFallbackService implements PaymentFeignService {  @Override  public CommonResult<Payment> paymentSql(Long id) {  return new CommonResult<>(444,"服務降級返回,沒有該流水信息",new Payment(id, "errorSerial......"));  }  
}

84端口controller加入openFeign的接口:

@Resource  
private PaymentFeignService paymentFeignService;  @GetMapping("/consumer/openFeign/{id}")  
public CommonResult<Payment> paymentSql(@PathVariable("id") Long id){  if(id == 4){  throw new RuntimeException("沒有該id");  }  return paymentFeignService.paymentSql(id);  
}
(4) 主啟動類

加上@EnableFeignClient注解開啟OpenFeign

package com.java.cloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@SpringBootApplication  
@EnableFeignClients  
public class OrderNacosMain84 {  public static void main(String[] args) {  SpringApplication.run(OrderNacosMain84.class,args);  }  
}
(5) 測試

啟動9003、9004、84
訪問:http://localhost:84/consumer/paymentSQL/1
9003、9004負載均衡

關閉9003、9004微服務提供者,看到84消費者自動執行降級兜底方法。

如果yaml沒有配置Sentinel對Feign的支持,就不會執行降級方法,而是直接報錯誤頁面。

9.3 熔斷框架比較

十、持久化規則

前面我們微服務新增的限流規則后,微服務關閉后就會丟失,當時配置都限流規則都是臨時的。 將限流配置規則持久化進Nacos保存,只要刷新8401某個rest地址,sentinel控制臺的流控規則 就能看到。只要nacos里面的配置不刪除,針對8401上的sentinel上的流控規則就持續存在。 (也可以持久化到文件,redis,數據庫等)

案例——修改8401已完成持久化設置

(1) pom

導入持久化所需依賴

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

(2) yaml

添加nacos數據源配置

 
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848 #Nacos服務注冊中心地址sentinel:transport:dashboard: localhost:8080 #配置Sentinel dashboard地址port: 8719datasource:ds1:nacos:server-addr: localhost:8848dataId: cloudalibaba-sentinel-servicegroupId: DEFAULT_GROUPdata-type: jsonrule-type: flowmanagement:endpoints:web:exposure:include: '*'feign:sentinel:enabled: true # 激活Sentinel對Feign的支持

(3) 添加nacos業務規則配置

我們將sentinel的流控配置保存在nacos中,因為nacos的配置持久化在了數據庫中。

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

resource:資源名稱;
limitApp:來源應用;
grade:閾值類型,0表示線程數,1表示QPS;
count:單機閾值;
strategy:流控模式,0表示直接,1表示關聯,2表示鏈路;
controlBehavior:流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等待;
clusterMode:是否集群。

注意:這里如果要配nacos的命名空間(public、dev、test)的話,應該是配namespace的id,不是名稱

(4) 測試
啟動8401,訪問8401任意接口,刷新Sentinel。可以看到Sentinel中加載了通過nacos持久化的規則配置文件。
?

關掉8401后發現流控規則沒有了。
?


再次啟動8401查看sentinel,訪問幾次8401后流控規則又出現了。
查看數據庫,發現規則持久化到數據庫中了。

19. SpringCloud Alibaba Seata處理分布式事務

分布式事務問題

只要用到分布式,必然會提及分布式的事務。
在分布式之前,一切組件全都在一臺機器上。
在使用分布式之后,單體應用被拆分成微服務應用,原來的三個模塊被拆分成三個獨立的應用,分別使用三個獨立的數據源。
業務操作需要調用三個服務來完成。此時每個服務內部的數據一致性由本地事務來保證,但是全局的數據一致性問題沒法保證。

一句話:一次業務操作需要跨多個數據源或需要跨多個系統進行遠程調用,就會產生分布式事務問題。

一、Seata簡介與安裝

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

1.1 相關術語

一個典型的分布式事務過程,可以用分布式處理過程的一ID+三組件模型來描述。
一ID(全局唯一的事務ID):Transaction ID XID,在這個事務ID下的所有事務會被統一控制
三組件:
●Transaction Coordinator (TC):事務協調器,維護全局事務的運行狀態,負責協調并驅動全局事務的提交或回滾;(Server端,為單獨服務器部署)
●Transaction Manager (TM):事務管理器,控制全局事務的邊界,負責開啟一個全局事務,并最終發起全局提交或全局回滾的決議;
●Resource Manager (RM):資源管理器,控制分支事務,負責分支注冊、狀態匯報,并接收事務協調器的指令,驅動分支(本地)事務的提交和回滾
●Seata分TC、TM和RM三個角色,TC(Server端)為單獨服務端部署,TM和RM(Client端)由業務系統集成(微服務)。

1.2 典型的分布式控制事務流程

1.TM 向 TC 申請開啟一個全局事務,全局事務創建成功并生成一個全局唯一的 XID;
2.XID 在微服務調用鏈路的上下文中傳播;(也就是在多個TM,RM中傳播)
3.RM 向 TC 注冊分支事務,將其納入 XID 對應全局事務的管轄;
4.TM 向 TC 發起針對 XID 的全局提交或回滾決議;
5.TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求。

1.3 Seata-Server的下載與配置

我這里下載了0.9.0版本跟1.4.2版本(配了半天沒配好,后面再填坑),差別還是蠻大的。
github地址:https://github.com/seata/seata/releases
官網地址:Apache Seata

1.3.1 修改file.conf文件

解壓到指定目錄并修改conf目錄下的file.conf配置文件。
1.備份原始file.conf文件。

* a 主要修改:自定義事務組名稱+事務日志存儲模式為db+數據庫連接信息。

2.修改file.conf文件

* a service模塊(1.4.2里面沒有這個模塊,需要自己加)

* b store模塊


注意:上面配置文件中的driver-class-name,如果你的mysql是8.0以后的版本,記得名稱修改成:com.mysql.cj.jdbc.Driver,同時url里面要配置上時區

1.3.2 數據庫中建庫建表

數據庫新建庫seata,建表db_store.sql在\seata-server-0.9.0\seata\conf目錄里面

1.3.3 修改seata-server-0.9.0\seata\conf目錄下的registry.conf配置文件

目的是:指明注冊中心為nacos,及修改nacos連接信息

1.3.4 啟動nacos和seata (先啟動nacos)

seata-server-0.9.0\seata\bin\seata-server.bat
啟動失敗,報錯:

解決:0.9.0默認的mysql是5.1.30版本,將lib文件夾下mysql-connector-java-5.1.30.jar刪除,替換成自己mysql版本的jar包,我的是mysql-connector-java-8.0.22.jar。
再次啟動:

出現這些提示信息代表seata啟動成功。
nacos中成功注冊了seate:

**Seata 1.4.2版本填坑---nacos作為seata的注冊/配置中心

seata1.2.0 Seata1.4.0+nacos
Seata0.9.0版本不支持集群,生產環境下需要使用1.0.0以上版本。我們這里配置seata的最新版本1.4.2,下載后文件目錄如下圖所示:

0. 啟動nacos

啟動nacos,新建一個命名空間seata用于存放seata的配置信息。


注意這里的命名空間ID,后面會用到。這里不新建也可以,seata使用的是public。
我們使用nacos充當seata的注冊中心和配置中心!

1. 修改配置文件
①進入conf文件夾,修改file.conf文件

1.4.2版本可以參考file.conf.example(server端)和conf文件夾下README-zh.md中的client端配置
總共需要修改的地方:我這里用seate_1_4_2數據庫來對應seata1.4.2版本

②修改conf\registry.conf文件

思考:這里我們把seata-server端的config設置為了nacos,那么是不是第一步的file.conf文件就不再需要了。因為直接從nacos讀取配置?

2. 將配置導入到nacos
① 準備nacos-config.sh腳本

在conf文件夾下,需要有個nacos-config.sh文件,這個文件1.4.2版本沒有。README-zh.md文件中訪問config-center超鏈接(https://github.com/seata/seata/tree/develop/script/config-center),nacos文件夾下:

用這個可以直接下當前頁的文件github-directory-downloader

② config.txt準備及修改

在conf目錄下還需要一個config.txt文件,1.4.2版本同樣沒有,還是去README里面的config-center超鏈接。

config.txt需要放在conf的上級目錄下。

修改config.txt文件中的內容,主要是下面這幾項:

改為使用db存儲:

注意這里store.db.url中數據庫的名字就是我們之后需要新建的數據庫的名字。

相比于其他版本,1.4.2這里多了個distributedLockTable。
整個config.txt文件中,store.publicKey、store.redis.sentinel.masterName、store.redis.sentinel.sentinelHosts、store.redis.password四個屬性默認都是空的

。所以后面在將config.txt文件中的配置注冊到nacos的時候,會出現四個失敗項。

③ 導入seata相應的配置項到Nacos

config.txt就是seata各種詳細的配置,執行nacos-config.sh即可將這些配置導入到nacos。這樣就不需要將file.conf和registry.conf放到我們的項目中了,需要什么配置就直接從nacos中讀取。(這句話是參考博客Seata 1.4.0 + nacos配置和使用,超詳細_seata config.txt-CSDN博客,我覺不完全對,后面registry.conf里的配置項雖然不需要.conf文件配置,但是需要在yml或properties文件中配置,而file.conf可以直接在nacos中讀取)
導入配置:

然后在git bash界面輸入:

注:h表示nacos的地址,p表示端口號,g表示配置的分組,t表示命名空間的ID,u跟w表示nacos的賬戶密碼。如果沒有設置命名空間,而且都是默認選項直接 sh nacos-config.sh -h localhost就行。

可以看到共98項,導入失敗4項,就是上面沒有值的那四項(不影響,如果用到直接在nacos里面新建配置即可)

可以看到,nacos的seata命名空間中已經導入了配置項。(seata命名空間是我自己創建的,可以按自己的需求創建,不創建默認的就是public。)

3. 數據庫中建庫建表

我們先創建數據庫seata1_4_2(數據庫要與config.txt中db設置那里對應),數據庫的建表語句在README文件的server連接中:

然后執行mysql.sql(1.4.2多了個distributed_lock表,和一些插入語句):

,這四張表跟config.txt文件中的配置對應。

4. 啟動seata

運行bin目錄下的seata-server.bat。
出現下面字段表示seata啟動成功。seata啟動日志在C:\Users\admin\logs\seata文件夾下。

nacos中在seata命名空間內也成功注冊,注意這里服務名對應的是registry.conf文件中nacos下面application的值。0.9.0版本好像設置不了這個,默認是serverAddr。

二、訂單/庫存/賬戶業務數據庫準備

以下演示都需要先啟動Nacos后啟動Seata,保證兩個都OK。Seata沒啟動報錯no available server to connect。

2.1 分布式事務業務說明

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

2.2 創建業務數據庫與表

1. 創建業務數據庫

●seata_order:存儲訂單的數據庫;
●seata_storage:存儲庫存的數據庫;
●seata_account:存儲賬戶信息的數據庫。

CREATE DATABASE seata_order;CREATE DATABASE seata_storage;CREATE DATABASE seata_account;
2. 按照上述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;
3. 按照上述3庫分別建對應的回滾日志表

訂單-庫存-賬戶3個庫下都需要建各自的回滾日志表,\seata-server-0.9.0\seata\conf目錄下的db_undo_log.sql;

-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此腳本必須初始化在你當前的業務數據庫中,用于AT 模式XID記錄。與server端無關(注:業務數據庫)
-- 注意此處0.3.0+ 增加唯一索引 ux_undo_log
drop table `undo_log`;
CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

1.4.2版本的在README_ZH文件中的client:

注意:0.9版本跟1.4.2版本的undo_log表的屬性有差別,1.4.2版本沒有id跟ext這兩個屬性,個人覺得使用上沒有影響
這里的話我還是按0.9.0版本來建表。

最終效果: 這里展示了1.4.2版本跟0.9版本,實際中用一個就行,別的版本一樣。

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

業務需求:下訂單->減庫存->扣余額->改(訂單)狀態

版本對應關系——很重要
注意:由于seata0.9.0版本跟1.0之后的版本(支持yml、properties配置)區別巨大,這里使用0.9.0版本(跟視頻一致),其版本對應關系見版本說明。(seata0.9.0 + nacos 1.1.4 + sentinel 1.7.0 + SpringCloud Alibaba 2.1.1RELEASE)前面用的各組件版本得對應上(頭疼)

3.1 新建訂單Order-Module——seata-order-service2001

新建seata-order-service2001

(1) 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. Nacos 服務發現:用于服務注冊和發現,使得微服務可以相互通信。
  1. Seata 分布式事務:用于實現分布式事務管理,確保多個微服務之間的數據一致性。
  1. Feign 客戶端:用于簡化 HTTP 客戶端調用,實現聲明式 REST 調用。
  1. Web 和 Actuator:用于構建 Web 應用程序,并提供了 Actuator 端點,用于監控和管理應用程序。
  1. MySQL 驅動:用于連接 MySQL 數據庫。
  1. Druid 數據庫連接池:用于優化數據庫連接管理。
  1. MyBatis 集成:用于簡化 MyBatis 的使用,實現 ORM(對象關系映射)。
  1. Spring Boot 測試:用于編寫和運行單元測試。
  1. Lombok 插件:用于減少樣板代碼,如 getter、setter、toString 等。
(2) application.yml

這里配置的是我們自己微服務的數據源

server:  port: 2001  # 指定服務運行的端口為2001  spring:  application:  name: seata-order-service  # 指定應用程序的名稱為seata-order-service  cloud:  alibaba:  seata:  tx-service-group: tx_group  # 指定Seata事務分組名稱  #自定義事務組名稱需要與seata-server中的對應  nacos:  discovery:  server-addr: localhost:8848  # 指定Nacos服務發現地址  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  # 指定MySQL數據庫驅動類  url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8  # 指定MySQL數據庫連接URL  username: root  # 指定MySQL數據庫用戶名  password: root  # 指定MySQL數據庫密碼  feign:  hystrix:  enabled: false  # 禁用Feign的Hystrix支持  logging:  level:  io:  seata: info  # 設置Seata的日志級別為info  mybatis:  mapper-locations: classpath:mapper/*.xml  # 指定MyBatis的Mapper XML文件位置
(3) file.conf

程序中依賴的是 seata-all,對應于 *.conf 文件,所以需要在resource新建.conf文件,高版本的支持yml、properties配置。這里僅僅是seata-order-service2001模塊的file.conf(配置2001的分布式事務),seata軟件那里配置的是總控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.xu_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"  driver-class-name = "com.mysql.cj.jdbc.Driver"  url = "jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8"  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  }  
}

注意修改這兩處:

幾個配置文件對應關系

(4) registry.conf

指明注冊到nacos中:

registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "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 = 6000connect.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、etcd3type = "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 = 6000connect.timeout = 2000}etcd3 {serverAddr = "http://localhost:2379"}file {name = "file.conf"}
}
(5) domain

domain 就是entity(pojo,bean),對應數據庫的表,不同公司習慣不一樣。
新建Order類與CommonResult類

Order

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  import java.math.BigDecimal;  @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;  
}

CommonResult

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class CommonResult<T> {  private Integer code;  private String message;  private T data;  /**  * 這種構造函數重載的方式常用于簡化對象的創建過程。  * 例如,在創建CommonResult對象時,如果不需要提供data參數,  * 可以直接使用只接受code和message的構造函數,這樣可以減少代碼的復雜度和冗余。  * @param code  * @param message  */  public CommonResult(Integer code, String message){  this(code,message,null);  }  }
(6) Dao接口及實現(SQL映射文件)

dao中至少要有兩個方法,一個是創建訂單,一個是修改訂單狀態

OrderDao

package com.java.springcloud.dao;  import com.java.springcloud.domain.Order;  
import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  @Mapper  
public interface OrderDao {  /**  * 創建訂單  * @param order  */  void create(Order order);  /**  * 修改訂單狀態  */  void update(@Param("userId")Long userId,@Param("status")Integer  status);  }

OrderMapper.xml
resources文件夾下新建mapper文件夾后添加OrderMapper.xml。完成dao的具體實現。

<?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.java.springcloud.dao.OrderDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.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>
(7) Service接口及實現

Order2001驅動自己,外加調用庫存和賬戶:共3個service

OrderService

package com.java.springcloud.service;  import com.java.springcloud.domain.Order;  
import org.springframework.cloud.openfeign.EnableFeignClients;  public interface OrderService {  void create(Order order);  
}

StorageService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.EnableFeignClients;  
import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  @FeignClient(value = "seata-storage-service")  
public interface StorageService {  /**  * 修改庫存  */  @PostMapping("/storage/decrease")  void decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);  }

AccountService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  import java.math.BigDecimal;  @FeignClient(value="seata-account-service")  
public interface AccountService {  /**  * 修改庫存  * 必須使用RequestParam注解,不然啟動就會報錯  */  @PostMapping("/account/decrease")  void decrease(@RequestParam("userId") Long userId, @RequestParam("money")BigDecimal money);  }

OrderServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.OrderDao;  
import com.java.springcloud.domain.Order;  
import com.java.springcloud.service.AccountService;  
import com.java.springcloud.service.OrderService;  
import com.java.springcloud.service.StorageService;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.stereotype.Service;  import javax.annotation.Resource;  @Service  
@Slf4j  
public class OrderServiceImpl implements OrderService {  @Resource  private OrderDao orderDao;  @Resource  private AccountService accountService;  @Resource  private StorageService storageService;  @Override  public void create(Order order) {  log.info("新建訂單,用戶id是{}",order.getUserId());  orderDao.create(order);  //遠程調用庫存服務扣減庫存  log.info("調用庫存服務,扣減庫存-開始");  storageService.decrease(order.getUserId(),order.getCount());  log.info("調用庫存服務,扣減庫存-結束");  //遠程調用賬戶服務扣減余額  log.info("調用賬戶服務,扣減金額-開始");  accountService.decrease(order.getUserId(),order.getMoney());  //修改訂單狀態為已完成  log.info("調用賬戶服務,扣減金額-結束");  orderDao.update(order.getUserId(),0);  log.info("新建訂單結束,用戶id是{}",order.getUserId());  }  
}
(8) controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.domain.Order;  
import com.java.springcloud.service.OrderService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  @RestController  
public class OrderController  
{  @Autowired  private OrderService orderService;  @GetMapping("/order/create")  public CommonResult create(Order order){  orderService.create(order);  return new CommonResult(200,"訂單創建成功");  }  }
(9) config

MyBatisConfig
mybatis配置類,綁定實現文件OrderMapper.xml與Dao接口

package com.java.springcloud.config;  import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.context.annotation.Configuration;  @Configuration  
@MapperScan({"com.java.springcloud.dao"})  
public class MyBatisConfig {  
}

DataSourceProxyConfig

package com.java.springcloud.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;  @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();  }  }

DataSouce的包是sql下的,DataSourceProxy是seata下的,不要搞錯了。

(10) 主啟動類
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@EnableFeignClients  
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消數據源的自動創建  
public class SeataOrderMainApp2001 {  public static void main(String[] args) {  SpringApplication.run(SeataOrderMainApp2001.class, args);  }  
}

啟動測試
先啟動nacos-1.1.4和seata-0.9.0,再啟動2001。
2001啟動成功,成功注冊到nacos中

測試nacos-2.0.3和seata-0.9.0,再啟動2001 也可以啟動成功

3.2 新建庫存Storage-Module——seata-storage-service2002

(1) 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>
(2) application.yml

用到的數據庫跟上面的是不一樣的,我們就是為了測試分布式數據庫的

server:  port: 2002  spring:  application:  name: seata-storage-service  cloud:  alibaba:  seata:  tx-service-group: xu_group  nacos:  discovery:  server-addr: localhost:8848  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&useInformationSchema=false  # 指定MySQL數據庫連接URL  username: root  password: root  logging:  level:  io:  seata: info  mybatis:  mapperLocations: classpath:mapper/*.xml
(3) file.conf & registry.conf

跟2001的一模一樣

(4) domain

Storage

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class Storage  
{  private Long id;  /**  * 產品id  */    private Long productId;  /**  * 總庫存  */  private Integer total;  /**  * 已用庫存  */  private Integer used;  /**  * 剩余庫存  */  private Integer residue;  }

CommonResult

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class CommonResult<T> {  private Integer code;  private String message;  private T data;  /**  * 這種構造函數重載的方式常用于簡化對象的創建過程。  * 例如,在創建CommonResult對象時,如果不需要提供data參數,  * 可以直接使用只接受code和message的構造函數,這樣可以減少代碼的復雜度和冗余。  * @param code  * @param message  */    public CommonResult(Integer code, String message){  this(code,message,null);  }  }
(5) Dao接口及實現(SQL映射文件)

StorageDao

package com.java.springcloud.dao;  import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  @Mapper  
public interface StorageDao {  /**  * 扣減庫存  */  void decrease(@Param("productId") Long productId, @Param("count") Integer count);  }

StorageMapper.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.java.springcloud.dao.StorageDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.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>
(6) Service接口及實現

StorageService

package com.java.springcloud.service;  import org.springframework.cloud.openfeign.FeignClient;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  public interface StorageService {  /**  * 修改庫存  */  @PostMapping("/storage/decrease")  void decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);  }

StorageServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.StorageDao;  
import com.java.springcloud.service.StorageService;  
import org.apache.ibatis.annotations.Param;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  @Service  
public class StorageServiceImpl implements StorageService {  @Autowired  private StorageDao storageDao;  @Override  public void decrease(@Param("productId") Long productId, @Param("count") Integer count) {  storageDao.decrease(productId,count);  }  
}
(7) Controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.service.StorageService;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  @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,"扣減庫存成功!");  }  }
(8) config配置

與2001的一模一樣

package com.java.springcloud.config;  import org.mybatis.spring.annotation.MapperScan;  
import org.springframework.context.annotation.Configuration;  @Configuration  
@MapperScan({"com.java.springcloud.dao"})  
public class MyBatisConfig {  
}
package com.java.springcloud.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;  @Configuration  
public class DataSourceProxyConfig {  @Value("${mybatis.mapper-locations}")  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();  }  }
(9) 主啟動類
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableFeignClients  
@EnableDiscoveryClient  
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})  
public class SeataStorageServiceApplication2002 {  public static void main(String[] args) {  SpringApplication.run(SeataStorageServiceApplication2002.class, args);  }  
}

啟動測試
啟動nacos、seata、2002;啟動成功,注冊進nacos。

3.3 新建賬戶Account-Module——seata-account-service2003

(1) pom
<?xml version="1.0" encoding="UTF-8"?>  
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  <parent>        <artifactId>springcloud2020</artifactId>  <groupId>org.xu.springcloud</groupId>  <version>1.0.0</version>  </parent>    <modelVersion>4.0.0</modelVersion>  <artifactId>seata-account-service2003</artifactId>  <properties>        <maven.compiler.source>8</maven.compiler.source>  <maven.compiler.target>8</maven.compiler.target>  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  </properties>    <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>8.0.18</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>  
</project>
(2) application.yml
server:  port: 2003  spring:  application:  name: seata-account-service  cloud:  alibaba:  seata:  tx-service-group: xu_group  nacos:  discovery:  server-addr: localhost:8848  datasource:  driver-class-name: com.mysql.cj.jdbc.Driver  url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&useInformationSchema=false  # 指定MySQL數據庫連接URL  username: root  password: root  feign:  hystrix:  enabled: false  logging:  level:  io:  seata: info  mybatis:  mapperLocations: classpath:mapper/*.xml
(3) file.conf & registry.conf

跟2001一模一樣

(4) domain

Account

package com.java.springcloud.domain;  import lombok.AllArgsConstructor;  
import lombok.Data;  
import lombok.NoArgsConstructor;  import java.math.BigDecimal;  @Data  
@NoArgsConstructor  
@AllArgsConstructor  
public class Account {  private Long id;  /**  * 用戶id  */    private Long userId;  /**  * 總額度  */  private BigDecimal total;  /**  * 已用額度  */  private BigDecimal used;  /**  * 剩余額度  */  private BigDecimal residue;  }

CommonResult

(5) Dao接口及實現

AccountDao

package com.java.springcloud.dao;  import org.apache.ibatis.annotations.Mapper;  
import org.apache.ibatis.annotations.Param;  import java.math.BigDecimal;  @Mapper  
public interface AccountDao {  void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);  
}

AccountMapper.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.java.springcloud.dao.AccountDao">  <resultMap id="BaseResultMap" type="com.java.springcloud.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>
(6) Service接口及實現

AccountService

package com.java.springcloud.service;  import org.springframework.web.bind.annotation.RequestParam;  import java.math.BigDecimal;  public interface AccountService {  void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);  }

AccountServiceImpl

package com.java.springcloud.service.impl;  import com.java.springcloud.dao.AccountDao;  
import com.java.springcloud.service.AccountService;  
import org.springframework.stereotype.Service;  import javax.annotation.Resource;  
import java.math.BigDecimal;  @Service  
public class AccountServiceImpl implements AccountService {  @Resource  private AccountDao accountDao;  @Override  public void decrease(Long userId, BigDecimal money) {  accountDao.decrease(userId,money);  }  
}
(7) Controller
package com.java.springcloud.controller;  import com.java.springcloud.domain.CommonResult;  
import com.java.springcloud.service.AccountService;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestParam;  
import org.springframework.web.bind.annotation.RestController;  import javax.annotation.Resource;  
import java.math.BigDecimal;  
import java.util.concurrent.TimeUnit;  @RestController  
public class AccountController {  @Resource  private AccountService accountService;  @PostMapping("/account/decrease")  public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) {  try {  TimeUnit.MILLISECONDS.sleep(2000);  } catch (InterruptedException e) {  throw new RuntimeException(e);  }  accountService.decrease(userId, money);  return new CommonResult(200, "扣減賬戶余額成功!");  }  }
(8) config配置

和2001一模一樣

(9) 主啟動類
package com.java.springcloud;  import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;  
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;  
import org.springframework.cloud.openfeign.EnableFeignClients;  @EnableDiscoveryClient  
@EnableFeignClients  
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})  
public class SeataAccountMainApp2003 {  public static void main(String[] args) {  SpringApplication.run(SeataAccountMainApp2003.class,args);  }  
}

啟動測試
啟動nacos、seata、2003;啟動成功,成功注冊進nacos

填坑高版本seata1.4.2client配置

seata1.2.0 Seata1.4.0+nacos
使用seata1.4.2 各個微服務整體上的代碼是差不多的,區別的地方在于1.4.2支持yml、properties文件里配置client端的seata,不再需要file.conf/registry.conf文件。同時支持@EnableAutoDataSourceProxy注解開啟數據源的自動代理(不需要手動配置數據源)

① 修改父工程版本控制

seata高版本各微服務可以直接通過yml、properties來配置seata,不需要在微服務中加入file.conf和registry.conf文件。

注意版本對應關系,這里要使用SpringCloud Hoxton.SR9+SpringCloud Alibaba 2.2.6.RELEASE+Spring Boot 2.3.2RELEASE+Nacos 1.4.2(我用的2.0.3)+Seata 1.3.0(我用的1.4.2)。
修改父工程的依賴版本,主要是讓springboot、SpringCloud、SpringCloud alibaba版本對應。

② 修改微服務模塊seata依賴pom

官網上對seata依賴是這么描述的:

官網推薦依賴配置方式:

但是經過我測試發現,還是需要排除掉spring-cloud-starter-alibaba-seata里面的seata-all

實際上我有微服務只配置了seata-spring-boot-starter依賴,spring-cloud-starter-alibaba-seata沒有配置,并不影響正常使用。

③ 修改微服務application.yml

直接將seata的相關配置,配置到application.yml文件中,幾個微服務的yml類似:

注意對應關系:

④ 修改主啟動類和DataSourceProxyConfig類

主啟動類
主啟動類加上@EnableAutoDataSourceProxy注解,這里以storage的微服務為例:

配置類
使用@EnableAutoDataSourceProxy注解后,不再需要DataSourceProxyConfig配置數據源代理。強行寫會報錯。如果需要自己配置數據源代理的話,在application.yml中設置seata.enable-auto-data-source-proxy為false,主啟動類上去掉@EnableAutoDataSourceProxy注解即可。

參考博客
1Seata1.4.2+Nacos搭建使用
2Seata1.4.2整合SpringCloud H——Seata安裝與搭建
3最詳細實際項目中seata踩坑、部署教程以及多鏈路調用_seata lock.retrytimes-CSDN博客
4spring cloud使用nacos和seata(windows環境)
5SEATA配合nacos使用

四、測試

Seata全局事務怎么使用
Spring提供的本地事務:@Transactional
Seata提供的全局事務:@GlobalTransactional

4.0 數據庫初始情況

下訂單->減庫存->扣余額->改(訂單)狀態

4.1 測試正常下單

啟動nacos、seata、2001、2002、2003;
測試:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

報錯
java.sql.SQLException:Failed to fetch schema of t_order
Connector/J 5.0.0以后的版本有一個名為useInformationSchema的數據庫連接參數,Connector/J 在mysql8.0中默認配置連接屬性useInformationSchema為true,使查詢table信息時更為有效。用戶依然可以配置useInformationSchema為false,但是在8.0.3及其之后的版本中,由于不能支持早期的特性,某些數據字典的查詢可能會失敗。
useInformationSchema配置為false的時候,也可能會造成REMARKS信息(對應數據庫中各字段的comment)的缺失。

在各微服務的application.yml 文件的spring.datasource.url 后面加上&useInformationSchema=false設置useInformationSchema為false,即可解決該問題。 參考:jdbc中useInformationSchema屬性分析 - 簡書

再次測試
訪問成功

數據庫情況:

4.2 測試超時異常:不加@GlobalTransactional

AccountServiceImpl添加超時:

我們使用的是Openfeign,默認超時時長是1s,這里我們延遲30s。

報錯超時異常:


數據庫情況:

當庫存和賬戶金額扣減后,訂單狀態并沒有設置為已經完成,沒有從零改為1;而且由于feign的重試機制,賬戶余額還有可能被多次扣減。

4.3 測試超時異常:加@GlobalTransactional

OrderServiceImpl添加@GlobalTransactional注解,注意改注解只能用在方法上!

這個注解加在哪個業務類上呢? 誰是最開始的發起者就加在誰上面
?


●name:給定全局事務實例的名稱,隨便取,唯一即可
●rollbackFor:當發生什么樣的異常時,進行回滾
●noRollbackFor:發生什么樣的異常不進行回滾。

測試:
依然超時異常 ,


重點是數據庫有沒有發生變化:

我們發現數據庫中的數據根本就沒有變化,記錄都添加不進來,說明回滾成功!

4.4 小結

做好配置后,我們只需要使用一個 @GlobalTransactional(name = "lsp-create-order", rollbackFor = Exception.class) 放在業務的入口,即可實現控制全局的事務。注意該注解只能放在方法上。

五、補充說明

Seata:Simple Extensible Autonomous Transaction Architecture,簡單可擴展自治事務框架
0.9不支持集群,生產環境請使用1.0以上的版本。

5.0 undo_log表的作用

模塊內方法也可以加@Transactional注解,如果一個模塊的事務提交了,Seata會把提交了哪些數據記錄到undo_log表中,如果這時TC通知全局事務回滾,那么RM就從undo_log表中獲取之前修改了哪些資源,并根據這個表回滾。(有待考證)

5.1 再看TC/TM/RM三大組件

TC:seata服務器; (我們電腦上啟動的seata )
TM:事物的發起者,業務的入口。 哪個微服務使用了@GlobalTransactional哪個就是TM
RM:事務的參與者,一個數據庫就是一個RM。

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

5.2 AT模式(默認)如何做到對業務的無侵入

Seata有四大模式:AT(默認)、TCC、SAGA、XA。(阿里云上的AT叫做GTS,收費)
AT模式
AT模式兩階段提交協議的演變:
●一階段:業務數據和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源。
●二階段:
○提交異步化,非常快速地完成。
○回滾通過一階段的回滾日志進行反向補償(前面insert,后面回滾時就delete)。

每個數據庫除了自身存儲數據的表以外,都會有一個事務回滾表:undo_log
Seata庫中存在:branch_table\global_table\lock_table\distributed_lock(高版本才有)這樣一些表

5.2.1 一階段加載

在一階段,Seata 會攔截“業務 SQL”,
1 解析 SQL 語義,找到“業務 SQL”要更新的業務數據,在業務數據被更新前,將其保存成“before image”(前置鏡像)
2 執行“業務 SQL”更新業務數據,在業務數據更新之后,
3 其保存成“after image”,最后生成行鎖。
以上操作全部在一個數據庫事務內完成,這樣保證了一階段操作的原子性。

5.2.2 二階段提交

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

5.2.3 二階段回滾

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

5.3 debug查看流程

最開是seata庫中的三張表是沒有數據的

2003打上斷點,debug啟動

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


此時seata庫中的三個表都是有數據的:

看一下branch_table,記錄了各個RM的信息,分別對應order、storage、account三個微服務

可以看到xid跟global_table中的xid一致。

再看global_table:

查看lock_table:
?


rollback_info是JSON字符串,存儲了beforeimage、afterimage:

查看seata_storage庫中的undo_log表的roobal_info信息,可以看到beforeimage和afterimage分別保存了修改前后的信息。

debug放行,seata庫中表中的中間數據和undo_log表的數據都刪除了。(我的seata_account表的undo_log中沒有被刪除,等了半天也沒有。)異步任務階段的分支提交請求將異步和批量地刪除相應的undo_log記錄。
發現account2003微服務的日志跟2001和2002都不一樣

5.4 整體流程圖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91925.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91925.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91925.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式開發學習———Linux環境下數據結構學習(五)

折半查找&#xff08;二分查找&#xff09;適用于已排序的數組&#xff0c;通過不斷縮小查找范圍定位目標值。int binarySearch(int arr[], int size, int target) {int left 0, right size - 1;while (left < right) {int mid left (right - left) / 2;if (arr[mid] t…

(一)React +Ts(vite創建項目/useState/Props/Interface)

文章目錄 項目地址 一、React基礎 1.1 vite創建 1. 創建項目 2. 安裝項目所需環境 1.2 jsx 1. 三元表達式 1.3 基礎 1. 創建第一個組件 2. 安裝boostrap 3. 插件常用命令 4. map 二、組件 2.1 useState 1. useState 2. 使用 3.更新對象 4. 更新數組(增,刪,改) 5. 使用immer…

網關和BFF是如何演化的

BFF&#xff08;Backend For Frontend&#xff09;:對返回的數據結構進行聚合、裁剪、透傳等適配邏輯。 適用于API網關之后的數據聚合、裁剪與透傳簡化客戶端邏輯&#xff0c;減少網絡開銷敏感數據過濾 BFF邏輯層 架構沒有最好&#xff0c;要看是否滿足當前的業務場景。 業務的…

SQL中的WITH語句(公共表表達式CTE)解釋

SQL中的WITH語句&#xff08;公共表表達式CTE&#xff09; WITH語句&#xff0c;也稱為公共表表達式&#xff08;Common Table Expression&#xff0c;CTE&#xff09;&#xff0c;是SQL中一種強大的功能&#xff0c;它允許你創建臨時結果集&#xff0c;這些結果集可以在后續的…

服務器地域選擇指南:深度分析北京/上海/廣州節點對網站速度的影響

更多云服務器知識&#xff0c;盡在hostol.com你準備開一個覆蓋全國的線上零食店&#xff0c;現在萬事俱備&#xff0c;只差一個核心問題沒解決&#xff1a;你唯一的那個總倉庫&#xff0c;應該建在哪里&#xff1f;是建在哈爾濱&#xff0c;讓南方的朋友下單后&#xff0c;一包…

桶排序-Java實現

桶排序是一種分配式排序算法&#xff0c;將元素分到有限數量的桶里&#xff0c;每個桶再單獨排序&#xff08;比如用插入排序&#xff09;&#xff0c;最后依次把各個桶中的元素取出來即完成排序。 時間復雜度&#xff1a;最佳 O(n) | 平均 O(n n/k k) | 最差 O(n) 空間復雜…

oracle知識

這里寫自定義目錄標題Oracle常用的數據類型&#xff1a;Oracle實操&#xff1a;創建數據表Oracle約束建表的時候設置約束&#xff1a;表創建后添加添加約束&#xff1a;Oracle常用的數據類型&#xff1a; Oracle實操&#xff1a;創建數據表 Oracle約束 建表的時候設置約束&…

超級人工智能+無人機操控系統,振興鄉村經濟的加速器,(申請專利應用),嚴禁抄襲!

無人機邊緣智能系統&#xff1a;山林珍稀資源探測的完整架構與實戰指南本人設計的多模態邊緣AI系統已在秦嶺山區完成實地驗證&#xff0c;對7種高價值食用菌識別準確率達94.3%&#xff0c;定位誤差小于0.8米一、前沿技術融合的商業化機遇根據Gartner 2025年技術成熟度曲線分析&…

用騰訊地圖寫一個逆地址解析(很詳細)

首先說明以下代碼適合有前端基礎知識的同學。以下是css和html部分<!DOCTYPE html><html lang"zh-CN"><!-- lang是用來申明語言類型&#xff0c;這里申明為中文&#xff08;zh&#xff09;中國大陸&#xff08;CN&#xff09;補充中文繁體為zh-TW --&g…

在 Vue3+Vite+TypeScript 項目中使用 svg 文件并支持自定義樣式

參考文檔&#xff1a;vite-svg-loader 安裝與配置 安裝插件 pnpm add vite-svg-loader -D配置 // vite.config.ts import svgLoader from vite-svg-loaderexport default defineConfig({plugins: [vue(),svgLoader({defaultImport: component})] })使用 <script setup …

ShimetaPi M4-R1:國產高性能嵌入式平臺的異構計算架構與OpenHarmony生態實踐

在全球化芯片供應鏈波動及樹莓派等硬件持續漲價的背景下&#xff0c;ShimetaPi M4-R1 作為全棧國產化嵌入式開發平臺&#xff0c;以 高性能異構計算架構 和 開源鴻蒙原生支持 為核心突破點&#xff0c;填補了中高端邊緣設備開發的國產方案空白。其基于瑞芯微 RK3568B2 的硬件設…

zookeeper分布式鎖 -- 讀鎖和寫鎖實現方式

讀鎖和寫鎖讀鎖: 是共享鎖,讀鎖與讀鎖是可以兼容的,所以同時有多個請求都可以持有寫鎖: 是獨占鎖,寫鎖與任何鎖都互斥,所以只有一個請求持有,這個請求釋放寫鎖其他請求才能持有一旦持有寫鎖,說明數據在發送變化就不能讀了,自然一個請求就不能出現讀鎖和寫鎖共存的情況總結: 讀鎖…

第二篇:Linux 文件系統操作:從基礎到進階

目錄 一、文件與目錄管理基礎 創建文件 創建目錄 目錄結構查看 二、鏈接文件深入理解 創建軟鏈接 創建硬鏈接 核心區別對比 三、文件壓縮與解壓縮全攻略 1、壓縮命令對比 2、解壓縮命令 3、三種壓縮方式性能對比 4、通用解壓技巧 四、文件查找與搜索 1、按文件名…

嗶哩嗶哩招游戲內容產品運營

游戲內容產品運營【2026屆】&#xff08;崗位信息已獲jobleap.cn授權轉發到csdn&#xff09;嗶哩嗶哩集團 上海收錄時間&#xff1a; 2025年08月01日職位描述1、負責研究B站游戲創作者的創作過程、動機及遇到的問題&#xff0c;產出研究報告&#xff1b; 2、結合用研分析和相關…

談談Flutter中的Key

目錄 前言 一、什么是Key 1.StatelessWidget 2.StatefulWidget 3.加入Key后的效果 二、什么時候應該使用 Key&#xff1f; 1.Flutter判斷widget的邏輯 1.Flutter判斷組件身份的規則 1.Widget的類型&#xff08;runtimeType&#xff09;相同 2. Key相同&#xff08;ke…

重生之我在暑假學習微服務第八天《OpenFeign篇》

個人主頁&#xff1a;VON文章所屬專欄&#xff1a;微服務 微服務系列文章 重生之我在暑假學習微服務第一天《MybatisPlus-上篇》重生之我在暑假學習微服務第二天《MybatisPlus-下篇》重生之我在暑假學習微服務第三天《Docker-上篇》重生之我在暑假學習微服務第四天《Docker-下篇…

風光儲綜合能源系統雙層優化規劃設計【MATLAB模型實現】

本模型基于雙層優化框架&#xff0c;利用KKT條件、大M法、對偶理論求解&#xff0c;專注于綜合能源系統&#xff08;微電網&#xff09;多電源容量優化配置的模型介紹。代碼采用CPLEX求解器&#xff0c;注釋詳盡&#xff0c;非常適合新手學習該類問題的建模與求解思路。 模型總…

雪花算法重復id問題

原理解析 雪花算法實現簡單、適配性強&#xff0c;無論是電商訂單、日志追蹤還是分布式存儲&#xff0c;都能滿足 “唯一、有序、高效、可擴展” 的核心需求&#xff0c;因此成為分布式ID主流選擇。雪花算法生成的ID是一個64位的整數&#xff0c;由多段不同意義的數字拼接而成&…

MQTT 入門教程:三步從 Docker 部署到 Java 客戶端實現

在物聯網&#xff08;IoT&#xff09;與邊緣計算快速發展的今天&#xff0c;設備間的高效通信成為核心需求。MQTT 作為一種輕量級的發布 / 訂閱模式協議&#xff0c;憑借其低帶寬占用、強穩定性和靈活的消息路由能力&#xff0c;已成為物聯網通信的事實標準。無論是智能家居的設…

公網服務器上Nginx或者Openresty如何屏蔽IP直接掃描

0x01 背景云服務器很多時候為了通信需要設置公網訪問&#xff0c;但是網絡當中存在很多的掃描器&#xff0c;無時無刻在掃描&#xff0c;當80,443端口暴露時&#xff0c;成了這些掃描IP的攻擊對象&#xff0c;無時無刻收到威脅。0x02 掃描攻擊方式1.直接通過公網IP地址進行一些…