十一. SpringCloud Alibaba

1. SpringCloud Alibaba簡介

1.1 爲何會出現SpringCloud Alibaba

Spring Cloud Netflix項目進入到維護模式html

什麼是維護模式?=> 將模塊置於維護模式,意味着Spring Cloud團隊將不會再向模塊添加新功能(咱們將修復block級別的bug以及安全問題,咱們也會考慮並審查社區的小型pull request)。java

進入維護模式意味着?=> SpringCloud Netflix將再也不開發新的組件。新組件功能將以其餘替代品代替的方法實現。mysql

1.2 SpringCloud Alibaba有什麼

官網 誕生於2018.10.31,Spring Clooud Alibaba正式入駐Spring Cloud官方孵化器,並在Maven中央庫發佈了第一個版本。linux

能幹什麼?nginx

功能 具體說明
服務限流與降級 默認支持Servlet,Feign,RestTemplate,Dubbo和RocketMQ限流降級功能的接入,能夠在運行時經過控制檯實時修改限流降級規則,還支持查看限流降級Metrics監控。
服務註冊與發現 適配Spring Cloud服務註冊與發現標準,默認集成了Ribbon的支持。
分佈式配置管理 支持分佈式系統中的外部化配置,配置更改時自動刷新。
消息驅動能力 基於SpringCloud Stream 爲微服務應用構建消息驅動能力。
阿里雲對象儲存 阿里雲提供的海量,安全,底成本,高可靠的雲存儲服務。支持在任什麼時候間,任何地點儲存和訪問任意類型的數據。
分佈式任務的調度 提供秒級,精準,高可靠,高可用的定時(基於Cron表達式)任務調度服務。同時提供分佈式的任務執行模型,如網格任務。網格任務支持海量子任務均勻分配到全部Worker(schedulerx-client)上執行。
image-20210305153745306

使用SpringCloud Alibaba須要在POM文件中引入其依賴:git

<dependencyManagement>
    <dependencies>
        <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>
    </dependencies>
</dependencyManagement>

2. Nacos做爲服務註冊中心

官方文檔github

2.1 Nacos簡介

「Nacos」,前四個字母分別問Naming和Configuration的前兩個字母,最後的s爲Service,它是一個更易於構建雲原生應用的動態服務發現、配置管理和服務管理平臺,說白了就是 註冊中心 + 配置中心 的組合,等價於SpringCloud以前的 Eureka + Config + Bus,因此Nacos能夠替代Eureka作服務中心,能夠替代Config作配置中心。web

2.2 Nacos安裝與運行

Nacos官網下載地址中下載Nacos,在安裝Nacos前須要本地Java8和Maven的環境已經準備好,而後下載其壓縮包:算法

image-20210305154441392

解壓該壓縮文件後直接運行在bin目錄下的startup.cmd啓動Nacos:spring

image-20210305170416260

而後訪問 http://localhost:8848/nacos/ 進入Nacos,其默認的帳號密碼都是nacos,登陸成功後便可看到以下界面:

image-20210305170522725

在以前學習服務註冊中心的時候講過CAP理論,即

C A P
Consistency Available Partition tolerance
強一致性 可用性 分區容錯性

CAP原則又稱CAP定理,指的是在一個分佈式系統中,一致性、可用性、分區容錯性。

CAP 原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。在分佈式架構中,P永遠要求被保證,因此當前的分佈式架構只有AP和CP兩種。Nacos屬於AP模型,再次對比這些服務註冊中心:

服務註冊與發現框架 CAP模型 控制檯管理 社區活躍度
Eureka AP 支持 低(2.x版本歷史問題)
Zookeeper CP 不支持
Consul CP 支持
Nacos AP(事實上也能夠支持CP) 支持

聽說Nacos在阿里巴巴內部有超過10萬的實例運行,已通過了相似雙十一等各類大型流量的考驗。

2.3 Nacos做爲服務註冊中心

基於Nacos的服務提供者

新建Module:cloudalibaba-provider-payment9002做爲服務提供方微服務。固然爲了實現Nacos的負載均衡,使nacos-payment-provider服務集羣化,咱們同事也仿照9002搭建一個9003微服務。

而後在工程的父POM中引入依賴(Spring Cloud Alibaba簡介中引入的依賴)的前提下,在該模塊的POM引入依賴Nacos服務註冊發現的依賴:

<!--SpringCloud ailibaba nacos -->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

而後添加其配置文件application.yml:

server:
  port: 9002

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

management:
  endpoints:
    web:
      exposure:
        include: '*' # 監控端點所有打開

編寫其主啓動類並在主啓動類上添加 @EnableDiscoveryClient 註解,使9002微服務可以被註冊中心發現:

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

而後編寫一個簡單的業務類:

@RestController
public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id) {
        return "Nacos服務註冊,端口:" + serverPort + ";id:" + id;
    }
}

啓動900二、9003微服務模塊,在Nacos的服務列表中咱們能夠看到這兩個微服務已經入駐服務註冊中心:

image-20210305172414336

點開nacos-payment-provider服務的詳情頁面,能夠看到服務的實例詳情:

image-20210305172451467

基於Nacos的服務消費者

新建Module:cloudalibaba-consumer-nacos-order83做爲服務消費方微服務,在該模塊的POM中一樣引入令Nacos服務註冊中心發現本身的依賴,配置其配置文件:

server:
  port: 83

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

# 消費者將要去訪問的微服務名稱(註冊成功進Nacos的微服務提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

而後編寫服務消費方的主啓動類,Nacos自己就具備負載均衡功能,由於引入Nacos依賴的同時,Nacos內部集成了Ribbon,如圖所示:

image-20210305173056948

而咱們在學習Ribbon時知道,用了Ribbon就須要使用 RestTemplate ,全部咱們編寫配置類,向Spring容器中注入 RestTemplate

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced //負載均衡
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

注意 注入 RestTemplate 時必定要添加 @LoadBalanced 註解,不然不會開啓負載均衡,在服務提供方集羣的狀況下,因爲沒有開啓負載均衡,消費方會沒法選擇具體調用哪一個微服務實例,也就是服務提供方實例有不少個,而消費方服務不知道要調用哪一個具體的服務提供方實例,不加該註解會產生 UnknownHostException 的異常。

而後編寫其業務類:

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

啓動83服務消費方,在Nacos服務註冊中心中咱們能夠看到入駐了兩個服務提供方實例和一個服務消費方實例。

測試

屢次訪問 http://localhost:83/consumer/payment/nacos/1 ,咱們發現服務消費方微服務83能夠完成對服務提供方微服務9002/9003的輪詢負載均衡調用,也就是 Nacos內部就整合了Ribbon,實現了負載均衡,默認負載均衡算法採用輪詢

2.4 各類服務註冊中心的對比

事實上,Nacos能夠在AP模式和CP模式中進行切換,也就是說Nacos不只僅支持AP(可用性和分區容錯性),它一樣支持CP(一致性和分區容錯性)。當 採起入駐到Nacos服務註冊中心的微服務對本身的健康狀態進行上報時,也就是對入駐到註冊中心的微服務進行非持久化的保存,一旦客戶端上報不健康信息,就將不健康的實例摘除掉,這相似於Eureka(固然Eureka能夠開啓自我保護模式);當 採起由Nacos服務註冊中心本身探測入駐到中心的微服務是否健康時,也就是對入駐到註冊中心的微服務進行持久化保存,即便服務註冊中心發現微服務已經不健康了,也不會刪除到微服務,這相似於Consul。以下圖(CoreDNS也是一種服務註冊中心):

image-20210305155924885

Nacos與其餘服務註冊中心特性的對比:

Nacos Eureka Consul CoreDNS Zookeeper
一致性協議 CP/AP AP CP / CP
健康檢查 TCP/HTTP/MySQL/Client Beat(客戶端心跳) Clent Beat TCP/HTTP/gRPC/cmd / Client Beat
負載均衡 權重/DSL/元數據/CMDB Ribbon Fabio RR /
雪崩保護 支持 支持 不支持 不支持 不支持
自動註銷實例 支持 支持 不支持 不支持 支持
訪問協議 HTTP/DNS/UDP HTTP HTTP/DNS DNS TCP
監聽支持 支持 支持 支持 不支持 不支持
多數據中心 支持 支持 支持 不支持 不支持
跨註冊中心 支持 不支持 支持 不支持 不支持
SpringCloud集成 支持 支持 支持 不支持 不支持
Dubbo集成 支持 不支持 不支持 不支持 支持
K8s集成 支持 不支持 支持 支持 不支持

從理論知識中咱們知道Nacos服務註冊中心能夠在AP和CP模式中進行切換,C是全部節點在同一時間看到的數據是一致的,而A是全部的請求都會受到響應(A能夠近似理解爲高可用)。

通常來講,若是不須要存儲服務級別的信息且服務實例是經過Nacos-Client註冊的,而且服務能保持心跳上報,那麼就能夠選擇AP模式,當前主流的微服務框架如SpringCloud和Dubbo,都適用於AP模式,AP模式爲了服務的可能性而減弱了一致性,所以 AP模式下只支持註冊臨時實例

而若是須要在服務級別編輯或存儲配置信息,那麼CP是必須的,K8s服務和DNS服務使用於CP模式,CP模式下則支持註冊持久化實例,此時以Raft協議爲集羣運行模式,該模式下注冊實例以前必須先註冊服務,若是服務不存在,則會返回錯誤。

若是須要讓Nacos服務從AP模式切換到CP模式的話,只須要向服務註冊中心發送POST請求便可:

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

3. Nacos做爲服務配置中心

在用SpringCloud Config結合SpringCloud Bus時,咱們能夠把配置信息託管到遠端如GitHub上,而如今咱們應用了Nacos後,能夠直接在Nacos上託管配置信息。

3.1 基礎配置

新建Module:cloudalibaba-config-nacos-client3377做爲服務配置中心微服務,在其POM文件中引入必要的依賴(如Nachos服務註冊中心的依賴)爲,引入配置中心的依賴:

<!--nacos-config-->
<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

Nacos同SpringCloud Config同樣,在項目初始化時,要保證先從配置中心(直接以Nacos做爲配置中心)進行配置拉取,拉取配置後,才能保證項目的正常啓動,而咱們知道SpringBoot配置文件的加載順序是存在優先級的,bootstrap的優先級要高於application,因此咱們建立兩個配置文件 bootstrap.yml (從配置中心拉取配置)和 application.yml(寫本身的配置),咱們配置 bootstrap.yml 以使3377服務從Nacos上拉取配置信息:

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        #Nacos服務註冊中心地址
        server-addr: localhost:8848 
      config:
        #Nacos做爲配置中心地址
        server-addr: localhost:8848 
        # 指定yaml格式的配置,也就是說從Nacos上讀yaml格式的配置文件
        file-extension: yaml 
        
# 配置匹配規則   
# ${spring.application.name}-${spring.profile.active}.${file-extension}

而後用 application.yml 定製本身的配置信息,將環境定義爲開發環境:

spring:
  profiles:
    active: dev # 表示開發環境

編寫其主啓動類後,而後編寫其業務類,在業務來上添加SpringCloud的原生註解@RefreshScope 以使服務能夠支持從Nacos配置中心動態刷新配置信息:

@RestController
@RefreshScope //支持Nacos的動態刷新功能
public class ConfigClientController {

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

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

在上面的業務層中咱們從配置中心讀取了配置信息

而咱們在Nacos配置中心中添加配置信息,在Nacos配置中心中添加配置文件要遵循必定的匹配規則。

Nacos中的 dataId 的組成格式及與SpringBoot配置文件中的匹配規則,更詳細的信息能夠參考Nacos官方文檔

在Nacos SpringCloud中,dataId 的完整格式爲:

${prefix}-${spring.profile.active}.${file-extension}
  • prefix 默認爲 spring.application.name (服務名)的值,也能夠經過配置項 spring.cloud.nacos.config.prefix 來配置。
  • spring.profile.active 即爲當前環境對應的 profile(上面咱們在 application.yml中配置的屬性),詳情能夠參考 Spring Boot文檔。 注意:當 spring.profile.active 爲空時,對應的鏈接符 - 也將不存在,dataId 的拼接格式變成 ${prefix}.${file-extension}, 建議 不要使 spring.profile.active 爲空
  • file-exetension 爲配置內容的數據格式,能夠經過配置項 spring.cloud.nacos.config.file-extension 來配置。目前只支持 propertiesyaml 類型。

因爲prefix 默認爲 spring.application.name (服務名)的值,因此 dataId 的完整格式能夠替換爲:

${spring.application.name}-${spring.profile.active}.${file-extension}

結合咱們在 boostrap.yml 配置文件和 application.yml 配置文件的配置,咱們能夠獲得在當前實例中的 dataId 應該爲:

nacos-config-client-dev.yaml

根據上述公式獲得 dataId 應爲的值後,咱們就能夠在Nacos配置中心的配置列表中新建配置文件。在新建配置文件時,Data ID 中填入咱們獲得的 dataId ,組名先選擇默認的便可,配置格式選擇咱們在 file-extension 中設置的YAML格式,而後編寫配置文件,編寫後點擊 發佈便可:

image-20210305181023290

注意,在Nacos中的 dataId 中的後綴名必須用 yaml 而不能用 yml

添加配置文件後再點開Nacos配置中心的配置列表,就能發現已經存在剛纔建立的配置文件:

image-20210305181125490

而後咱們啓動3377微服務,訪問 http://localhost:3377/config/info 查看可否訪問都Nacos配置中心的配置信息:

image-20210305181817421

Nacos配置中心直接就 支持動態刷新,再更改了Nacos配置中心的配置信息後,再經過3377微服務訪問,就能夠獲得更新後的配置信息。目前Nacos已經實現了Eureka + Config + Bus的功能,可是Nacos之因此如此優秀是由於這些框架有的功能它都有,而這些框架沒有的功能它還有!下面看Nacos的一些高級功能。

3.2 分類配置

問題:多環境多項目管理

在多環境多項目管理時,也就是實際開發中,一般一個系統會準備不一樣的開發環境,如dev開發環境、test測試環境、prod生產環境,如何保證指定環境啓動時服務能正確讀取到Nacos上相應環境的配置文件呢?一個大型分佈式微服務系統會有不少微服務子項目,每一個微服務項目又都會有相應的開發環境、測試環境、預發環境、正式環境等,那對這些微服務配置該如何管理呢?=> 在Nacos配置中心中就能夠進行分類配置,一個配置文件的具體所屬由 namespace + Group + dataId 所構成

image-20210305182438870

爲何如此設計?

這種設計就相似於Java中的包名和類名,最外層的 namespace 是能夠用於區分部署環境的,主要用來實現隔離,好比有三個環境:開發、測試、生產,那就能夠建立三個 namespace

Group dataId 邏輯上區分兩個目標對象。Group能夠把不一樣的微服務劃分到同一個分組裏面去。Service就是微服務,一個Service能夠包含多個Cluster(集羣),Nacos默認Cluster是DEFALUT,Cluster是對指定微服務的一個虛擬劃分。

舉例:好比說爲了容災,將Service微服務分別部署在了杭州機房和廣州機房,這時就能夠給杭州機房的Service微服務起一個集羣名稱HZ,給廣州機房的Service微服務起一個集羣名字GZ,還能夠儘可能讓同一個機房的微服務互相調用,以提高性能。

最後就是Instance,就是微服務的實例。

默認狀況下 namespace 的值爲 publicGroup 的值爲 DEFAULT_GROUP,默認Cluster是DEFAULT。

image-20210305182816916

下面對這三個屬性進行詳細展開。

dataId 方案

指定 spring.profile.active 配置屬性和配置文件的 dataId 來使不一樣環境下讀取不一樣的配置,也就是說咱們如今用默認的命名空間 namespace (即 public),默認的分組 Group (即 DEFAULT_GROUP),而後在Nacos配置中心中創建兩個 dataId 分別爲 dev 環境和test環境。此時在同一命名空間同一組中就有了兩個應用於開發、測試不一樣環境的配置文件:

image-20210305183940816

而後咱們經過修改微服務本身定製配置信息 application.yml 配置文件中的spring.profile.active 屬性便可進行多環境下的配置文件的讀取,配置什麼就加載什麼,在以前咱們的3377微服務獲取的是配置中心中 nacos-config-client-dev.yaml 配置文件的配置信息,如今咱們將 application.yml 修改成:

spring:
  profiles:
#    active: dev # 表示開發環境
    active: test # 表示開發環境

而後重啓3377微服務,再次進行測試發現訪問到的配置信息爲測試環境的配置信息:

image-20210305184116109

Group 方案

不只經過 dataId 能夠實現環境分區,應用 Group 也能夠實現環境分區,首先咱們在Nacos配置中心新建兩個相同 dataId,不一樣 Group 的配置文件,一個用於開發環境,一個用於測試環境:

image-20210305184507922

因爲這兩個配置文件的 dataId 均爲 nacos-config-client-info.yaml ,根據以前解釋的 dataId 的命名規則,咱們應該將3377微服務的自定義配置文件 application.yml 中的 spring.profile.active 屬性更改成 info

spring:
  profiles:
    active: info

在確認了讀取的配置文件的 dataId 後,咱們在其 bootstrap.yml 配置文件中添加 spring.cloud.nacos.config.group 屬性,設置爲相應的組名便可,好比咱們如今設置爲測試環境組:

spring:
  cloud:
    nacos:
      config:
        group: TEST_GROUP

重啓3377微服務,再次訪問能夠發現獲取到的配置信息即爲TEST_GROUP組的配置信息。

namespace 方案

根據namespace一樣能夠進行環境區分。

新建兩個命名空間

image-20210305185521368

服務列表查看

image-20210305190235725

bootstrap.yml 配置文件中添加 spring.cloud.nacos.config.namespace 屬性,設置爲相應的命名空間流水號便可,好比咱們如今設置爲開發環境命名空間:

spring:
  cloud:
    nacos:
      config:
		namespace: 5e4fbf07-10f3-4216-86c5-e40391667310

在dev命名空間下新建不一樣group的配置文件

image-20210305191801785

重啓3377微服務,再次訪問能夠發現獲取到的配置信息即爲 dev 命名空間下TEST_GROUP組的配置信息。

image-20210305191909731

4. Nacos集羣和持久化配置

4.1 官網說明

官網

官網集羣部署架構圖

開源的時候推薦用戶把全部服務列表放到一個vip下面,而後掛到一個域名下面

http://ip1:port/openAPI 直連ip模式,機器掛則須要修改ip纔可使用。

http://SLB:port/openAPI 掛載SLB模式(內網SLB,不可暴露到公網,以避免帶來安全風險),直連SLB便可,下面掛server真實ip,可讀性很差。

http://nacos.com:port/openAPI 域名 + SLB模式(內網SLB,不可暴露到公網,以避免帶來安全風險),可讀性好,並且換ip方便,推薦模式

VIP:即虛擬IP,即Nginx

image-20210305232024660

上圖翻譯一下就是

image-20210305232351466
4.2 Nacos持久化配置解釋

默認Nacos使用嵌入式數據庫實現數據的存儲,因此,若是啓動多個默認配置下的Nacos節點,數據儲存是存在一致性問題的。Nacos默認自帶的嵌入式數據庫爲derby。

爲了解決這個問題,Nacos採用了 集中式存儲的方式來支持集羣化部署,目前只支持MySQL的存儲。

image-20210305232900696

單機模式支持MySQL,在0.7版本以前,在單機模式時Nacos使用嵌入式數據庫實現數據的儲存,不方便觀察數據的基本狀況。0.7版本增長了支持MySQL數據源能力,具體的操做步驟以下。

derby到mysql切換配置步驟:

  • 安裝數據庫,版本要求:5.6.6+
  • nacos-server-1.1.4\nacos\conf目錄下找到sql腳本(nacos-mysql.sql),初始化MySQL數據庫nacos_config,數據庫初始化文件:nacos-mysql.sql
  • nacos-server-1.1.4\nacos\conf目錄下找到application.properties,修改conf/application.properties文件,增長支持MySQL數據源配置(目前只支持MySQL),添加MySQL數據源的url,用戶名和密碼。
# 若是是Mysql5.+
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://mpolaris.top:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&serverTimezone=UTC
db.user=root
db.password=123456

# 若是是Mysql8.+
spring.datasource.platform=mysql

jdbc.DriverClassName=com.mysql.cj.jdbc.Driver
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?serverTimezone=GMT%2B8&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user= root
db.password= 123456

啓動Nacos,能夠看到是個全新的空記錄界面,之前是記錄進derby。

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

須要一個Nginx,三個Nacos註冊中心,一個Mysql

Nacos下載Linux版,下載地址,nacos-server-1.1.4.tar.gz,解壓後安裝

集羣配置步驟

Linux服務器上MySQL數據庫配置(按照4.3配置)

application.properties配置(按照4.3配置)

Linux服務器上Nacos的集羣配置cluster.conf

  • 梳理出三臺Nacos機器的不一樣服務端口號(3333,4444,5555)
  • 複製出cluster.conf
cd /opt/nacos/conf
cp cluster.conf.example cluster.conf
  • 修改內容

注意:這個IP不能寫127.0.0.1,必須是Linux命令hostname -i 可以識別的IP

# it is ip
# example
# 這裏我填的是本身的私網ip
172.20.xxx.xxx:3333
172.20.xxx.xxx:4444
172.20.xxx.xxx:5555

編輯Nacos的啓動腳本startup.sh(先備份),使它可以接受不一樣的啓動端口

  • /mynacos/nacos/bin 目錄下有startup.sh
  • 怎樣改?
# 平時單機版的啓動,都是./startup.sh便可
# 可是集羣啓動,咱們但願能夠相似其餘軟件的shell命令,傳遞不一樣的端口號啓動不一樣的nacos實例。
# 例如:命令 ./startup.sh -p 3333 表示啓動端口號爲3333的nacos服務器實例
vim ./startup.sh
# 修改第57行開始增長p
export SERVER="nacos-server"
export MODE="cluster"
export FUNCTION_MODE="all"
while getopts ":m:f:s:p:" opt
do
 case $opt in
    m)
        MODE=$OPTARG;;
    f)
        FUNCTION_MODE=$OPTARG;;
    s)
        SERVER=$OPTARG;;
    p)
        PORT=$OPTARG;;
    ?)
    echo "Unknown parameter"
    exit 1;;
 esac
done
# 修改第134行,增長 - Dserver.port=${PORT}
 nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1
  • 執行方式
./startup.sh -p 3333
./startup.sh -p 4444
./startup.sh -p 5555

#注意:提示服務器內存不夠,能夠把nacos啓動腳本里-Xms和-Xmx jvm內存調整小一點

# 同時經過window瀏覽器訪問:
http://192.168.42.82:3333/nacos/#/login
http://192.168.42.82:4444/nacos/#/login
http://192.168.42.82:5555/nacos/#/login

Nginx的配置,由它做爲負載均衡器

  • 修改Nginx的配置文件Nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;
	
	upstream cluster {
		server 127.0.0.1:3333;
		server 127.0.0.1:4444;
		server 127.0.0.1:5555;
	}

    server {
        listen       1111;
        server_name  localhost;

        location / {
            # root   html;
            # index  index.html index.htm;
			proxy_pass http://cluster;
        }
    }

}
  • 按照指定配置文件啓動Nginx
cd /usr/local/nginx/sbin
./nginx -c /opt/nginx/conf/nginx.conf

開始測試

  • 測試經過Nginx訪問Nacos:http//192.168.42.82:1111/nacos/#/login,成功訪問!
  • 新建一個配置測試數據庫,Linux服務器的MySQL也插入一條記錄!

微服務測試

微服務springalibaba-provider-payment9002啓動註冊進nacos集羣

  • yml配置文件
server:
  port: 9002

spring:
  application:
    name: nacos-payment-provider # 微服務名稱
  cloud:
    nacos:
      discovery:
        # server-addr: localhost:8848 # 配置Nacos地址
        # 換成Nginx的1111端口,作集羣
        server-addr: 192.168.42.1:1111

management:
  endpoints:
    web:
      exposure:
        include: '*' # 監控端點所有打開
  • 結果:成功啓動!

高可用總結

image-20210305235136270

5. Sentinel熔斷與限流

5.1 Sentiel簡介

官網

主要特性

image-20210306145730077

與Hystrix對比

  • Hystrix須要咱們本身手工搭建監控平臺,沒有一套Wbe界面能夠給咱們進行更加細粒度化的配置,流控,速率控制,服務熔斷,服務降級。
  • Sentinel單獨一個組件,能夠獨立出來。直接界面化的細粒度統一配置。

Sentiel能夠解決的問題

  • 服務雪崩
  • 服務降級
  • 服務熔斷
  • 服務限流
  • ...
5.2 安裝Sentiel控制檯

Sentinel分爲兩部分:

  • 核心庫(Java客戶端)不依賴任何框架/庫,可以運行於全部Java運行時環境,同時對Dubbo / Spring Cloud等框架也有較好的支持。
  • 控制檯(Dashboard)基於Spring Boot開發,打包後能夠直接運行,不須要額外的Tomcat等應用容器.

下載

運行命令

# 前提:Java8環境,8080端口不被佔用
java -jar sentinel-dashboard-1.7.0.jar

訪問Sentinel管理界面

5.3 初始化演示工程

啓動Nacos8848,http://localhost:8848/nacos/#/login

新建Moudle:cloudalibaba-sentinel-service8401

pom.xml

<!--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>
<!--其餘基礎包-->

yml配置文件

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # Nacos服務註冊中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 # 配置Sentinel dashboard地址
        # 默認8719端口,假如被佔用會自動從8719開始依次+1掃描,直至找到未被佔用的端口
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

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

主啓動

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

業務類FlowLimitController

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

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

啓動Sentinel8080,啓動微服務8401

啓動8401微服務後臺查看sentinel控制,啥也沒有?=> Sentinel採用懶加載,執行兩次訪問http:localhost8401/testA,http:localhost8401/testB,效果以下:

image-20210306154151514
5.4 流控規則

名詞說明:

  • 資源名:惟一名字,默認請求路徑
  • 針對來源:Sentinel能夠針對調用者進行限流,填寫微服務名,默認default(不區分來源)
  • 閾值類型/單機閾值:
    • QPS(每秒鐘的請求數量):當調用該API的QPS達到閾值的時候,進行限流
    • 線程數:當調用該API的線程數達到閾值的時候,進行限流
  • 是否集羣:咱們這裏不須要集羣
  • 流控模式:
    • 直接:API達到限流條件時,直接限流
    • 關聯:當關聯的資源達到閾值時,就限流本身
    • 鏈路:只記錄指定鏈路上的流量(指定資源從入口資源進來的流量,若是達到閾值,就進行限流)【API級別的針對來源】
  • 流控效果:
    • 快速失敗:直接失敗,拋異常
    • Warm Up:根據codeFactor(冷加載因子,默認3)的值,從閾值/codeFactor,通過預熱時長,才達到設置的QPS閾值
    • 排隊等待:勻速排隊,讓請求以勻速的速度經過,閾值類型必須設置爲QPS,不然無效
image-20210306165736258

流控模式

直接(默認)

  • 配置:表示1秒鐘內查詢1次就是OK,若超過次數1,就直接快速失敗,報默認錯誤
image-20210306155910383
  • 測試:快速點擊訪問 http://localhost:8401/testA => Blocked by Sentinel(flow limiting)

  • 思考:直接調用了默認報錯信息,技術方面是沒問題的,可是 是否因該有咱們本身的後續處理呢?(相似有一個fallback的兜底方法),後面會講到如何配置

關聯

  • 是什麼:當關聯的資源達到閾值時,就限流本身。當與A關聯的資源B達到閾值後,就限流本身。例如B惹事A掛了
  • 配置A:當關聯資源/testB的QPS閾值超過1時,就限流/testA的Rest訪問地址,當關聯資源到閾值後限制配置好的資源名
image-20210306160647827
  • postman模擬併發密集訪問testB
    • 訪問B成功
    • postman裏新建多線程集合組(Collection)
    • 將訪問地址添加進新線程組(Collection)
    • RUN => 大批量線程高併發訪問B,致使A失效了
image-20210306171838253
  • 運行後發現testA掛了(點擊訪問A =>Blocked by Sentinel(flow limiting) )

鏈路

  • 多個請求調用了同一個微服務,相同道理,這裏就不演示了

流控效果

快速失敗

  • 直接失敗,拋出異常(Blocked by Sentinel(flow limiting))
  • 源碼:com.alibaba.csp.sentinel.slots.block.controller.DefaultController

Warm Up(預熱)

  • 說明:公式 => 閾值除以coldFactor冷加載因子(默認值爲3),通過預熱時長後纔會達到閾值
  • 源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
  • Warm Up
    • Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即預熱/冷啓動方式。當系統長期處於低水位的狀況下,當流量忽然增長時,直接把系統提高到高水位可能瞬間把系統壓垮。經過「冷啓動」,讓經過的流量緩慢增長,在必定時間內逐漸增長到閾值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。詳細文檔能夠參考 流量控制 - Warm Up文檔,具體的例子可見 WarmUpFlowDemo。
    • 一般冷處理的過程系統容許經過的QPS曲線以下圖所示:
image-20210306164734715
  • WarmUp配置
image-20210306173035040
  • 屢次點擊http://localhost:8401/testB,剛開始不行,後續慢慢OK
  • 應用場景如:秒殺系統在開啓瞬間,會有不少流量上來,極可能把系統打死,預熱方式就是爲了保護系統,可慢慢的把流量放進來,慢慢的把閾值增長到設置的閾值。

排隊等待

  • 勻速排隊,閾值必須設置爲QPS,不然無效
  • 源碼:com.ailibaba.csp.sentinel.slots.block.controller.RateLimiterController
  • 勻速排隊:
    • 勻速排隊(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式會嚴格控制請求經過的間隔時間,也便是讓請求以均勻的速度經過,對應的是漏桶算法。詳細文檔能夠參考 流量控制 - 勻速排隊模式,具體的例子能夠參見 PaceFlowDemo
    • 該方式的做用以下圖所示:這種方式主要用於處理間隔性突發的流量,例如消息隊列。想象一下這樣的場景,在某一秒有大量的請求到來,而接下來的幾秒則處於空閒狀態,咱們但願系統可以在接下來的空閒期逐漸處理這些請求,而不是在第一秒直接拒絕多餘的請求。
image-20210306165434981
  • 配置:/test每秒1次請求,超過的話就排隊等待,等待的超時時間爲20000毫秒
image-20210306174603204
5.5 降級規則

官網說明

名詞說明:

RT(平均響應時間,秒級)

  • 平均響應時間,超出閾值在時間窗口內經過的請求>=5,兩個條件同時知足後觸發降級,窗口期事後關閉斷路器
  • RT最大4900 ms(更大的須要經過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

異常比例(秒級)

  • QPS >= 5 且 比例(秒級統計)超過閾值時,觸發降級,窗口期事後關閉斷路器
image-20210306175319257

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

半開狀態:半開的狀態,系統自動去檢測是否請求有異常,沒有異常就關閉斷路器恢復使用,有異常則繼續打開斷路器不可用,具體能夠參考Hystrix。

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

當資源被降級後,在接下來的降級時間窗口以內,對該資源的調用都自動熔斷(默認行爲是拋出DegradException )。

降級策略演示

RT(平均響應時間(DEGRADE_GRADE_RT))

  • 是什麼:當 1s 內持續進入 5 個請求,且對應時刻的平均響應時間(秒級)均超過閾值(count,以ms爲單位),那麼在接下來的時間窗口(DegradeRule中的timeWindow,以 s 爲單位)以內,對這個方法的調用都會自動的熔斷(拋出DegradeException)。注意 Sentinel 莫仍統計的 RT 上限是 4900 ms,超出次閾值的都會算做 4900 ms,若須要變動此上限能夠經過啓動配置項 -Dcsp.sentinel.statistic.max.rt=xxx來配置。
image-20210306214533536
  • 測試

    • 代碼
@GetMapping("/testD")
public String testD() {
    //暫停幾秒線程
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("testD 測試RT");
    return "==> testD";
}
  • 配置
image-20210306215724273
  • jmeter壓測:直接 Blocked by Sentinel (flow limiting)
image-20210306221204059
  • 結論:① 默認請求是 1 秒 5個,而咱們能jmeter壓測設置的 1 秒 10個。② 代碼中設置的平均響應時間爲 1 秒,而咱們設置的RT爲 2 毫秒。兩個條件都不知足則直接斷路器開啓,微服務不可用了,服務熔斷降級。

異常比例(DEGRADE_GRADE_EXCEPTION_RATIO)

  • 是什麼:當資源的每秒請求量 >= 5,而且每秒異常總數佔經過量的比值超過閾值(DegradeRule中的count)以後,資源進入降級狀態,即在接下來的時間窗口(DegradeRule中的timeWindow,以 s 爲單位)以內,對這個方法的調用都會自動的返回。異常比率的閾值範圍是 [0.0,1.0],表明 0% - 100%。
image-20210306222409080
  • 測試

    • 代碼
@GetMapping("/testD")
public String testD() {
    log.info("testD 測試RT");
    int age = 10 / 0;
    return "==> testD";
}
  • 配置
image-20210306222650846
  • jmeter壓測:直接 Blocked by Sentinel (flow limiting)

異常數(DEGRADE_GRADE_EXCEPTION_COUNT):

  • 是什麼:當資源近 1 分鐘的異常數目超過閾值以後會進行熔斷降級。注意因爲統計 時間窗口時分鐘級別的,若 timeWindow 小於 60s,則結束熔斷狀態後仍可能再進入熔斷狀態。即 時間窗口必定要 >= 60 秒。
image-20210306223104462
  • 測試

    • 代碼:同異常比例
    • 配置
image-20210306223302413
  • jemter壓測:直接 Blocked by Sentinel (flow limiting)
5.6 熱點key限流

官網說明

源碼

com.alibaba.csp.sentinel.slots.block.BlockException

名詞說明

何爲熱點?熱點即常常訪問的數據。不少時候咱們但願統計某個熱點數據中訪問頻次最高的 Top K 數據,並對其訪問進行限制。好比

  • 商品 ID 爲參數,統計一段時間內最常購買的商品 ID 並進行限制
  • 用戶 ID 爲參數,針對一段時間內頻繁訪問的用戶 ID 進行限制

熱點參數限流會統計傳入的熱點參數,並根據配置的限流閾值與模式,對包含熱點參數的資源調用進行限流。熱點參數限流能夠看做是一種特殊的流量控制,僅對包含熱點參數的資源調用生效。

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

image-20210306223814861

回顧 - 兜底方法

兜底方法分爲 系統默認 和 客戶自定義 兩種,以前限流出現問題後,都是用sentinel 系統默認的提示:Blocked by Sentinel (flow limiting)。

咱們能不能自定義?相似hystrix,某個方法出問題了,就找對應的兜底降級方法!

=> 在sentinel中經過 @SentinelResource 註解可以實現 (相似於hystrix的HystrixCommand)。

/**
 * value: 惟一標識,對應熱點規則資源名
 * blockHandler:違背了熱點規則後指向的兜底方法,若是不配置,異常就會直接打到前臺去(不友好)
*/
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler="deal_testHotKey")
public String testHotKey(@RequestParam(value="p1",required=false) String p1,
                         @RequestParam(value="p2",required=false) String p2){
    return "==> testHotKey";
}

public String deal_testHotKey(String p1, String p2, BlockException e) {
    return "==> deal_testHotKey";
}

配置

方法testHotKey裏面第一個參數只要QPS超過每秒1次,立刻降級處理。且用了咱們自定義的兜底方法。

image-20210306230648108

測試

# QPS超過每秒1次會限流 
http://localhost:8401/testHotKey?p1=0
# QPS超過每秒1次會限流
http://localhost:8401/testHotKey?p1=0&p2=1
# 無限制,不會限流
http://localhost:8401/testHotKey?p2=1

參數例外項

上述案例演示了第一個參數p1,當QPS超過每秒1次馬上就會被限流。

特殊狀況:咱們指望 p1 參數當它是某個特殊值時,它的限流和平時不同,例如當 p1 的值等於 5 時,它的閾值能夠達到 200。怎樣實現呢?=> 配置參數例外項。

前提條件:熱點參數必須是 基本類型 或者 String

image-20210306231806143

其餘注意點

@SentineResource 處理的是Sentinel控制檯配置的違規狀況,有blockHandler方法配置的兜底方法處理。假如程序代碼出錯如 int age = 10/0,這個是Java運行時爆出的運行時異常RunTimeException,@SentineResource 無論。

總結:@SentineResource 主管配置出錯,運行出錯該走異常仍是會走異常!

5.7 系統規則

官網說明

各項配置說明

系統保護規則是從應用級別的入口流量進行控制,從單臺機器的 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爲例:

image-20210306233546408

系統規則通常少用,使用危險,一竹竿打死一船人!

5.8 @SentinelResource

按資源名稱限流 + 後續處理

啓動nacos,sentinel

修改8401pom.xml,將公共模塊也引入

<!-- 公共模塊 -->
<dependency>
    <groupId>com.polaris</groupId>
    <artifactId>cloud-api-common</artifactId>
    <version>${project.version}</version>
</dependency>

新增業務類RateLimitController,便於演示

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

配置流控規則:表示1秒鐘內查詢次數大於1,就跑到咱們自定義的限流處進行限流

image-20210307094516641

測試:出現了咱們自定義的限流兜底方案!

發現問題:此時若是關閉服務8401會怎樣?=> Sentinel控制檯流控規則消失了?臨時存儲?

按照Url地址限流 + 後續處理

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

業務類增長

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

配置流控規則

image-20210307095409609

測試:會返回Seninel自帶的限流處理結果:Blocked by Sentinel (flow limiting)

上述兜底方案面臨的問題

系統默認的方案,沒有體現咱們本身的業務需求

依照現有條件,咱們自定義的處理方法又和業務代碼耦合在一塊兒,不直觀

每一個業務方法都添加一個兜底方法,會致使代碼膨脹加重

全局統一的處理方法沒有體現

客戶自定義限流處理邏輯

建立CustomerBlockHandler類用於自定義限流處理邏輯

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

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

RateLimitController增長方法

@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
                  blockHandlerClass = CustomerBlockHandler.class,
                  blockHandler = "handlerException2")
	public CommonResult customerBlockHandler() {
    	return new CommonResult(200, "按客戶自定義",
                            new Payment(2020L, "serial003"));
}

測試:出現了咱們自定義的限流兜底方案!

@SentinelResource的其餘屬性

屬性 做用 是否必須
value 資源名稱
entryType entry類型,標記流量的方向,取值IN/OUT,默認是OUT
blockHandler 處理BlockException的函數名稱。函數要求:
1. 必須是 public
2.返回類型與原方法一致
3. 參數類型須要和原方法相匹配,並在最後加 BlockException 類型的參數
4. 默認需和原方法在同一個類中。若但願使用其餘類的函數,可配置 blockHandlerClass ,並指定blockHandlerClass裏面的方法。
blockHanderClass 存放blockHandler的類。對應的處理函數必須static修飾,不然沒法解析,其餘要求:同blockHandler。
fallback 用於在拋出異常的時候提供fallback處理邏輯。fallback函數能夠針對全部類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理。函數要求:
1. 返回類型與原方法一致
2. 參數類型須要和原方法相匹配,Sentinel 1.6開始,也可在方法最後Throwable 類型的參數。
3.默認需和原方法在同一個類中。若但願使用其餘類的函數,可配置 fallbackClass ,並指定fallbackClass裏面的方法。
fallbackClass (1.6) 存放fallback的類。對應的處理函數必須static修飾,不然沒法解析,其餘要求:同fallback。
defaultFallback 用於通用的 fallback 邏輯。默認fallback函數能夠針對全部類型的異常(除了 exceptionsToIgnore 裏面排除掉的異常類型)進行處理。若同時配置了 fallback 和 defaultFallback,以fallback爲準。函數要求:
\1. 返回類型與原方法一致
\2. 方法參數列表爲空,或者有一個 Throwable 類型的參數。
\3. 默認須要和原方法在同一個類中。若但願使用其餘類的函數,可配置 fallbackClass ,並指定 fallbackClass 裏面的方法。
exceptionTolgnore (1.6) 指定排除掉哪些異常。排除的異常不會計入異常統計,也不會進入fallback邏輯,而是原樣拋出。
exceptionsToTrace 須要trace的異常 Throwable

注意:

1.6.0 以前的版本 fallback 函數只針對降級異常(DegradeException)進行處理,不能針對業務異常進行處理

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

從 1.4.0 版本開始,註解方式定義資源支持自動統計業務異常,無需手動調用 Tracer.trace(ex) 來記錄業務異常。Sentinel 1.4.0 之前的版本須要自行調用 Tracer.trace(ex) 來記錄業務異常。

5.9 服務熔斷功能

sentinel整合Ribbon + openFeign + fallback

image-20210307102021850

啓動nacos和sentinel

新建服務提供者9004/9005

  • pom重要依賴
<!--SpringCloud ailibaba nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • yml
server:
  port: 9004

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

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

業務類

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L, "28a8c1e3bc2742d8848569891fb42181"));
        hashMap.put(2L, new Payment(2L, "bba8c1e3bc2742d8848569891ac32182"));
        hashMap.put(3L, new Payment(3L, "6ua8c1e3bc2742d8848569891xt92183"));
    }

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

新建服務消費者84

  • pom重要依賴
<!--SpringCloud openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--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>
  • 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
  
# 激活Sentinel對Feign的支持
feign:
  sentinel:
    enabled: true
  • 主啓動
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84 {
    public static void main(String[] args) {
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}
  • 配置類
@Configuration
public class ApplicationContextConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  • service
@Component
public class PaymentFallbackService implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444, 
                "服務降級返回,=> PaymentFallbackService", 
                new Payment(id, "errorSerial"));
    }
}
  • controller
    • fallback管運行異常
    • blockHandler管配置違規
    • 若 blockHandler 和 fallback 都進行了配置,則被限流降級而拋出 BlockException 只會進入 blockHandler處理邏輯
@RestController
@Slf4j
public class CircleBreakerController {
    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    //① 沒有配置
    //  @SentinelResource(value = "fallback") 
    //② fallback只負責業務異常
    //  @SentinelResource(value = "fallback",fallback = "handlerFallback")
    //③ blockHandler只負責sentinel控制檯配置違規
    //  @SentinelResource(value = "fallback",blockHandler = "blockHandler") 
    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback",
            blockHandler = "blockHandler",
            exceptionsToIgnore = {IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL
                + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
            throw new IllegalArgumentException(
                "IllegalArgumentException,非法參數異常!");
        } else if (result.getData() == null) {
            throw new NullPointerException(
                "NullPointerException,該ID沒有對應記錄,空指針異常");
        }

        return result;
    }

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

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

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

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

測試

  • 測試輪詢:http://localhost:84/consumer/fallback/1
  • 測試@SentinelResource
    • 沒有任何配置:給客戶error頁面,不友好
    • 添加fallback,blockHandler,exceptionsToIgnore:① 忽略了非法參數異常,出現非法參數異常還是系統錯誤頁面,② 其餘狀況都有了兜底方案
5.10 規則持久化

是什麼

一旦咱們重啓應用,Sentinel規則消失,生產環境須要將配置規則進行持久化

怎麼作

將限流規則持久進Nacos保存,只要刷新8401某個rest地址,Sentinel控制檯的流控規則就能看的到,只要Nacos裏面的配置不刪除,針對8401上的流控規則持續有效。

步驟演示

現象:咱們先在Sentinel配置一條流控規則,當重啓8401後,該流控規則就會失效消失了!

image-20210307115016477

修改8401

  • pom
<!--SpringCloud ailibaba sentinel-datasource-nacos 作持久化用 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • yml (添加Nacos數據源配置)
image-20210307115227512

添加Nacos業務規則配置(規則便可以保存在Sentinel中,也能夠保存在Nacos中等,只要是一個持久化媒介就行)

image-20210307114306781
[
    {
        "resource":"/rateLimit/byUrl", //資源名稱
        "limitApp":"default", //來源應用
        "grade":1, //閾值類型,0表示線程數,1表示QPS
        "count":1, //單機閾值
        "strategy":0, //流控模式,0表示直接,1表示關聯,2表示鏈路
        "controlBehavior":0, //流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等候
        "clusterMode":false //是否集羣
    }
]

啓動8401 刷新Sentinel發現業務規則有了

image-20210307115842894

中止8401,再看sentinel,發現停機後流控規則消失了!

重啓8401,再看sentinel,屢次調用接口,屢次刷新sentinel後,發現流控規則從新出現了,持久化驗證成功!

6. Seata處理分佈式事務

6.1 分佈式事務問題

分佈式以前

單機庫存沒有這個問題

從1:1 => 1:N => N:N,開始出現分佈式事務問題

image-20210307121324123

分佈式以後

單機應用被拆分紅微服務應用,原來的三個模塊被拆分紅 三個獨立的應用,分別使用 三個獨立的數據源 ,業務操做須要調用 三個服務來完成。

此時 每一個服務內部的數據一致性由 本地 事務來保證,可是 全局 的數據一致性問題無法保證

image-20210307121857129

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

6.2 Seata簡介

官網

下載地址

是什麼

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

做用

一個典型的分佈式事務過程:ID + 三組件模型

  • Transaction ID(XID):全局惟一的事務ID
  • 三組件概念
    • Transaction Coordinator(TC):事務協調器,維護全局事務的運行狀態,負責協調並驅動全局事務的提交或回滾
    • Transaction Manager(TM):控制全局事務的邊界,負責開啓一個全局事務,並最終發起全局提交或全局回滾的決議
    • Resource Manage(RM):控制分支(本地)事務,負責分支註冊,狀態彙報,並接受事務協調的指令,驅動分支(本地)事務的提交和回滾

處理過程(咱們能夠用這種方式幫助理解:RM-學生,TM-班主任,TC-授課老師)

  • TM向TC申請開啓一個全局事務,全局事務建立成功並生成一個全局惟一的XID
  • XID在微服務調用鏈路的上下文中傳播
  • RM向TC註冊分支事務,將其歸入XID對應全局事務的管轄
  • TM向TC發起針對XID的全局提交或回滾決議
  • TC調度XID下管轄的所有分支事務完成提交或回滾請求
image-20210307122934698

怎樣用

本地 @Transational

全局 @GlobalTransational

  • seata的分佈式交易解決方案
image-20210307123147388
6.3 Seata-Server安裝

下載版本

修改conf目錄下的file.conf配置文件

  • 先備份原始file.conf文件

  • 主要修改:自定義事務組名稱 + 事務日誌存儲模式爲db + 數據庫鏈接

    • service模塊 / store模塊
image-20210307161625252 image-20210307150715252

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

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

目的是指明註冊中心爲nacos,及修改nacos鏈接信息。

image-20210307151534874

先啓動Nacos,端口號爲8848

再啓動seata-server:bin目錄下seata-server.bat

6.4 訂單/庫存/帳戶業務數據庫準備

分佈式事務業務說明

這裏咱們建立三個服務,一個訂單服務,一個庫存服務,一個帳戶服務。

當用戶下單時,會在訂單服務中建立一個訂單,而後經過遠程調用庫存服務來扣減下單商品的庫存,再經過遠程調用帳戶服務來扣減用戶帳戶裏面的餘額,最後在訂單服務中修改訂單狀態爲已完成。

該操做跨越三個數據庫,有兩次遠程調用,很明顯會有分佈式事務問題。

建立業務數據庫

  • 建庫sql
create database seata_order;
create database seata_storage;
create database seata_account;
  • 按照上訴3庫分別創建對應業務表
# seata_order庫下新建t_order表
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `int` bigint(11) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) 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:已完結',
  PRIMARY KEY (`int`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '訂單表' ROW_FORMAT = Dynamic;

# seata_storage庫下新建t_storage表
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage`  (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `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 '剩餘庫存',
  PRIMARY KEY (`int`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '庫存' ROW_FORMAT = Dynamic;
INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);

# seata_account庫下新建t_account表
CREATE TABLE `t_account`  (
  `id` bigint(11) NOT NULL 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 NULL COMMENT '剩餘可用額度',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '帳戶表' ROW_FORMAT = Dynamic;
INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000);
  • 按照上述3庫分別創建對應的回滾日誌表(三個庫都須要創建各自獨立的回滾日誌表)
# 該sql在seata\conf目錄下的db_undo_log.sql中
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;
6.5 訂單/庫存/帳戶業務微服務準備

業務需求

下訂單 => 減庫存 => 扣餘額 => 改(訂單)狀態

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

pom

<dependencies>
    <!--SpringCloud ailibaba 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>
                <groupId>io.seata</groupId>
                <artifactId>seata-all</artifactId>
            </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>
    <!-- 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>
    <!-- MyBatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--mysql-connector-java-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</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>

yml

server:
  port: 2001

spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        #自定義事務組名稱須要與seata-server中的對應
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://mpolaris.top:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 1234321

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapperLocations: classpath:mapper/*.xml

domain

//CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>{
    private Integer code;
    private String  message;
    private T       data;

    public CommonResult(Integer code, String message)
    {
        this(code,message,null);
    }
}
//order
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order{
    private Long id;

    private Long userId;

    private Long productId;

    private Integer count;

    private BigDecimal money;

    private Integer status; //訂單狀態:0:建立中;1:已完結
}

Dao

@Mapper
public interface OrderDao{
    //1 新建訂單
    void create(Order order);

    //2 修改訂單狀態,從零改成1
    void update(@Param("userId") Long userId,
                @Param("status") Integer status);
}

service

public interface OrderService{
    void create(Order order);
}
@FeignClient(value = "seata-storage-service")
public interface StorageService{
    @PostMapping(value = "/storage/decrease")
    CommonResult decrease(@RequestParam("productId") Long productId,
                          @RequestParam("count") Integer count);
}
@FeignClient(value = "seata-account-service")
public interface AccountService{
    @PostMapping(value = "/account/decrease")
    CommonResult decrease(@RequestParam("userId") Long userId, 
                          @RequestParam("money") BigDecimal money);
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService{
    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

    /**
     * 建立訂單->調用庫存服務扣減庫存->調用帳戶服務扣減帳戶餘額->修改訂單狀態
     * 簡單說:下訂單->扣庫存->減餘額->改狀態
     */
    @Override
    @GlobalTransactional(name="fsp-create-order",rollbackFor=Exception.class)
    public void create(Order order)
    {
        log.info("==> 開始新建訂單");
        //1 新建訂單
        orderDao.create(order);

        //2 扣減庫存
        log.info("==> 訂單微服務開始調用庫存,作扣減Count");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("==> 訂單微服務開始調用庫存,作扣減end");

        //3 扣減帳戶
        log.info("==> 訂單微服務開始調用帳戶,作扣減Money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("==> 訂單微服務開始調用帳戶,作扣減end");

        //4 修改訂單狀態,從零到1,1表明已經完成
        log.info("==> 修改訂單狀態開始");
        orderDao.update(order.getUserId(),0);
        log.info("==> 修改訂單狀態結束");

        log.info("==> 下訂單結束了,O(∩_∩)O哈哈~");

    }
}

controller

@RestController
public class OrderController{
    @Resource
    private OrderService orderService;


    @GetMapping("/order/create")
    public CommonResult create(Order order)
    {
        orderService.create(order);
        return new CommonResult(200,"訂單建立成功");
    }
}

config

@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();
    }

}
@Configuration
@MapperScan({"com.polaris.springcloud.dao"})
public class MyBatisConfig {
}

主啓動

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消數據源的自動建立
public class SeataOrderMainApp2001 {
    public static void main(String[] args) {
        SpringApplication.run(SeataOrderMainApp2001.class, args);
    }
}

mapper

<mapper namespace="com.polaris.springcloud.dao.OrderDao">

    <resultMap id="BaseResultMap" type="com.polaris.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>

將seata-server/conf目錄下的file.conf和registry.conf拷貝到項目模塊下的resource中(seata-server/conf下的是總控)

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

新建帳戶Account-Module seata-account-service2003

參考訂單Module,源碼參考我的GitHub

6.6 測試

數據庫初始狀況

image-20210307171841386

正常下單

超時異常,沒加@GlobalTransactional

  • AccountServiceImpl設置超時
@Override
    public void decrease(Long userId, BigDecimal money) {
        LOGGER.info("==> account-service中扣減帳戶餘額開始");
        //模擬超時異常,全局事務回滾
        //暫停幾秒鐘線程
        try {
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        accountDao.decrease(userId, money);
        LOGGER.info("==> account-service中扣減帳戶餘額結束");
    }
  • 數據庫狀況:① 加進來了可是訂單狀態爲0,表示未支付 ②庫存減小了 ③ 錢也被扣了
  • 故障狀況:
    • 當庫存和帳戶金額扣減後,訂單狀態並無設置爲已經完成,沒有從0改成1
    • 並且因爲feign的重試機制,帳戶餘額還有可能被屢次扣減

超時異常,添加了@GlobalTransactional

  • AccountServiceImpl設置超時
  • 三個數據庫都未增長數據,即全局事務回滾了
6.7 Seata原理淺析
image-20210307181746642

再次理解TC/TM/RM三個組件 - 分佈式事務的執行流程:

  • TM開啓分佈式事務(TM向TC註冊全局事務記錄)
  • 按業務場景,編排數據庫,服務等事務內資源(RM向TC彙報資源準備狀態)
  • TM結束分佈式事務,事務一階段結束(TM通知TC提交/回滾分佈式事務)
  • TC彙報事務信息,決定分佈式事務是否提交仍是回滾
  • TC通知全部RM提交/回滾資源,事務二階段結束

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

  • AT模式
image-20210307182243617
  • 一階段加載
image-20210307182347457
  • 二階段提交
image-20210307182415129
  • 二階段回滾
image-20210307182436598
相關文章
相關標籤/搜索