目錄
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自己。
支付接口達到閾值,限流下訂單的接口。
- 配置
設置效果:當關聯資源/testB的qps閥值超過1時,就限流/testA的Rest訪問地址,當關聯資源到閾值后限制配置好的資源名
- 測試
單獨訪問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>
- Nacos 服務發現:用于服務注冊和發現,使得微服務可以相互通信。
- Seata 分布式事務:用于實現分布式事務管理,確保多個微服務之間的數據一致性。
- Feign 客戶端:用于簡化 HTTP 客戶端調用,實現聲明式 REST 調用。
- Web 和 Actuator:用于構建 Web 應用程序,并提供了 Actuator 端點,用于監控和管理應用程序。
- MySQL 驅動:用于連接 MySQL 數據庫。
- Druid 數據庫連接池:用于優化數據庫連接管理。
- MyBatis 集成:用于簡化 MyBatis 的使用,實現 ORM(對象關系映射)。
- Spring Boot 測試:用于編寫和運行單元測試。
- 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都不一樣