Distributed System : Distributed cache and Distributed Transaction

Distributed cache

緩存選型

Memcache

memcache 提供簡單的 kv cache 存儲,value 大小不超過1mb。 我使用 memcache 作為大文本或者簡單的 kv結構使用。因為是多線程 memcache 使用了slab 方式做內存管理,存在一定的浪費, 如果大量接近的 item,建議調整 memcache 參數來優化每一個 slab 增長的 ratio、可以通過設置 slab_automove & slab_reassign 開啟memcache 的動態/手動 move slab,防止某些 slab 熱點導致內存足夠的情況下引發 LRU。 大部分情況下,簡單 KV 推薦使用 Memcache,吞吐和相應都足夠好。

每個 slab 包含若干大小為1M的內存頁,這些內存又被分割成多個 chunk,每個 chunk存儲一個 item;

在 memcache 啟動初始化時,每個 slab 都預分配一個 1M 的內存頁, 由slabs_preallocate 完成(也可將相應代碼註釋掉關閉預分配功能)。

chunk 的增長因子由 -f 指定,默認1.25,起始大小為48字節。

內存池有很多種設計,可以參考下: nginx ngx_pool_t,tcmalloc 的設計等等。

Redis

redis 有豐富的數據類型,支持增量方式的修改部分數據,比如排行榜,集合,數組等。 比較常用的方式是使用 redis 作為數據索引,比如評論的列表 ID,播放歷史的列表 ID 集合,我們的關係鏈列表 ID。

redis 因為沒有使用內存池,所以是存在一定的內存碎片的, 一般會使用 jemalloc 來優化內存分配,需要編譯時候使用 jemalloc 庫代替 glib 的 malloc 使用。

為什麼會產生內存碎片, 內存對齊?

內存碎片: 簡單來說就是可分配的連續記憶體不足

若果不斷地重複、频繁、大量使用 malloc() 和 free() , 很容易使分配的空間切割了內存,令連續記憶體減少

如何避免記憶體碎片化:

Source from: Days 20: 垃圾回收器系列:記憶體碎片化、內存池

常見方法如下:

  1. 一次性分配比較大的空間,並且為 2 的指數
  2. 使用內存分頁
  3. 使用內存池
  4. 記憶體緊縮
內存對齊

Rediv vs Memcache

Redis 和 Memcache 最大的區別其實是 redis 單線程(新版本雙線程),memcache 多線程,所以 QPS 可能兩者差異不大,但是吞吐會有很大的差別,比如大數據 value 返回的時候,redis qps 會抖動下降的的很厲害,因為單線程工作,其他查詢進不來(新版本有不少的改善)。 所以建議純 kv 都走 memcache,比如我們的關係鏈服務中用了 hashs 存儲雙向關係,但是我們也會使用 memcache 檔一層來避免hgetall 導致的吞吐下降問題。 我們系統中多次使用 memcache + redis 雙緩存設計。

Proxy

早期使用 twemproxy 作為緩存代理,但是在使用上有如下一些痛點:

  1. 單進程單線程模型和 redis 類似,在處理一些大 key 的時候可能出現 io 瓶頸;
  2. 二次開發成本難度高,難以於公司運維平台進行深度集成;
  3. 不支持自動伸縮,不支持 autorebalance 增刪節點需要重啟才能生效;
  4. 運維不友好,沒有控制面板;

業界開源的的其他代理工具:

  1. codis: 只支持 redis 協議,且需要使用 patch版本的 redis;
  2. Omcrouter: 只支持 memcache 協議,C 開發,與運維集成開發難度高;

從集中式訪問緩存到 Sidecar 訪問緩存:

  • 微服務強調去中心化;
  • LVS 運維困難,容易流量熱點,隨下游擴容而擴容,連接不均衡等問題;
  • Sidecar 伴生容器隨 App 容器啟動而啟動,配置簡化;

一致性 Hash

一致性 hash 是將數據按照特徵值映射到一個首尾相接的 hash 環上, 同時也將節點(按照 IP 地址或者機器名 hash)映射到這個環上。 對於數據,從數據在環上的位置開始,順時針找到的第一個節點即為數據的存儲節點。 餘數分佈式算法由於保存鍵的服務器會發生巨大變化而影響緩存的命中率, 但Consistent Hashing 中,只有在園(continuum)上增加服務器的地點逆時針方向的第一台服務器上的鍵會受到影響。

平衡性(Balance):盡可能分佈到所有的緩衝中去 單調性(Monotonicity):單調性是指如果已經有一些內容通過哈希分派到了相應的緩衝中,又有新的緩衝區加入到系統中,那麼哈希的結果應能夠保證原有已分配的內容可以被映射到新的緩衝區中去,而不會被映射到舊的緩衝集合中的其他緩衝區。 分散性(Spread):相同內容被存儲到不同緩衝中去,降低了系統存儲的效率,需要盡量降低分散性。 負載(Load):哈希算法應能夠盡量降低緩衝的負荷。 平滑性(Smoothness):緩存服務器的數目平滑改變和緩存對象的平滑改變是一致的。

一致性哈希算法在服務節點太少時,容易因為節點分部不均勻而造成數據傾斜問題。 此時必然造成大量數據集中到 Node A 上,而只有極少量會定位到 Node B 上。為了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱為虛擬節點。

具體做法可以在服務器 ip 或主機名的後面增加編號來實現。 例如上面的情況,可以為每台服務器計算三個虛擬節點,於是可以分別計算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,於是形成六個虛擬節點。 同時數據定位算法不變,只是多了一步虛擬節點到實際節點的映射,例如定位到 “Node A#1”、“Node A#2”、“Node A#3”三個虛擬節點的數據均定位到 Node A 上。這樣就解決了服務節點少時數據傾斜的問題。

參考微信紅包的寫合併優化: https://www.cnblogs.com/chinanetwind/articles/9460820.html 在網關層,使用一致性 hash,對紅包 id 進行分片,命中到某一個邏輯服務器處理,在進程內做寫操作的合併,減少存儲層的單行鎖爭用。 我認為更好的做法是 有界負載一致性 hash。

數據分片的 hash 方式也是這個思想,即按照數據的某一特徵(key)來計算哈希值, 並將哈希值與系統中的節點建立映射關係,從而將哈希值不同的數據分佈到不同的節點上。 按照 hash 方式做數據分片,映射關係非常簡單;需要管理的元數據也非常之少,只需要記錄節點的數目以及 hash 方式就行了。 當加入或者刪除一個節點的時候,大量的數據需要移動。比如在這裡增加一個節點 N3,因此 hash 方式變為了 mod 4。

均衡問題:原始數據的特徵值分佈不均勻,導致大量的數據集中到一個物理節點上;第二,對於可修改的記錄數據,單條記錄的數據變大。

高級玩法是抽象 slot,基於 Hash 的 Slot Sharding,例如 Redis-Cluster。

Slot

redis-cluster 把16384 槽(slot)按照節點數量進行平均分配,由節點進行管理。 對每個 key 按照 CRC16 規則進行 hash 運算,把 hash 結果對16383進行取餘,把餘數發送給 Redis 節點。

需要注意的是:Redis Cluster 的節點之間會共享消息,每個節點都會知道是哪個節點負責哪個範圍內的數據槽

Redis 集群中的纪元(epoch) : https://zhuanlan.zhihu.com/p/44658603

緩存模式

數據一致性 (Data Consistency)

Storage 和 Cache 同步更新容易出現數據不一致。 模擬 MySQL Slave 做數據複製,再把消息投遞到 Kafka,保證至少一次消費:

  1. 同步操作DB;
  2. 同步操作Cache;
  3. 利用Job消費消息,重新補償一次緩存操作 保證時效性和一致性。

Cache Aside 模型中,讀緩存 Miss 的回填操作,和修改數據同步更新緩存, 包括消息隊列的異步補償緩存,都無法滿足 "Happens Before",會存在相互覆蓋的情況。

Cache-Aside是最廣泛使用的緩存模式之一,如果能正確使用Cache-Aside的話,能極大的提升應用性能,Cache-Aside可用來讀或寫操作。 cache-aside https://zhuanlan.zhihu.com/p/150740291

以下步驟導致數據不一致 讀/寫同時操作:

  1. 讀操作,讀緩存,緩存 MISS
  2. 讀操作,讀 DB,讀取到數據
  3. 寫操作,更新 DB 數據
  4. 寫操作 SET/DELETE Cache(可 Job 異步操作)
  5. 讀操作,SET操作數據回寫緩存(可 Job 異步操作) 這種交互下,由於4和5操作步驟都是設置緩存,導致寫入的值互相覆蓋;並且操作的順序性不確定,從而導致 cache 存在髒緩存的情況

解決方式: 讀/寫同時操作:

  1. 讀操作,讀緩存,緩存 MISS
  2. 讀操作,讀 DB,讀取到數據
  3. 寫操作,更新 DB 數據
  4. 寫操作 SET Cache(可異步 job 操作,Redis 可以使用 SETEX 操作, Redis Setex 命令為指定的 key 設置值及其過期時間。如果 key 已經存在, SETEX 命令將替換舊的值)
  5. 讀操作,ADD 操作數據回寫緩存(可 Job異步操作,Redis 可以使用 SETNX 操作, Redis Setnx(SET if N ot e X ists) 命令在指定的key 不存在時,為key 設置指定的值) 寫操作使用 SET 操作命令,覆蓋寫緩存;讀操作,使用 ADD 操作回寫 MISS 數據,從而保證寫操作的最新數據不會被讀操作的回寫數據覆蓋

多級緩存 (Multilevel Cache)

微服務拆分細粒度原子業務下的整合服務(聚合服務), 用於提供粗粒度的接口,以及二級緩存加速, 減少扇出的 rpc 網絡請求,減少延遲。

最重要是保證多級緩存的一致性:

  • 清理的優先級是有要求的,先優先清理下游再上游;
  • 下游的緩存expire要大於上游,裡面穿透回源;

天下大勢分久必合,適當的微服務合併也是不錯的做法,再使用 DDD 思路以及我們介紹的目錄結構組織方式,區分不同的 Usecase。

熱點緩存 (Hotkey Cache)

對於熱點緩存 Key,按照如下思路解決:

  • 小表廣播,從 RemoteCache 提升為LocalCache,App 定時更新,甚至可以讓運營平台支持廣播刷新 LocalCache;
  • 主動監控防禦預熱,比如直播房間頁高在線情況下直接外掛服務主動防禦;
  • 基礎庫框架支持熱點發現,自動短時的 short-live cache;
  • 多 Cluster 支持;
    • 多 Key 設計: 使用多副本,減小節點熱點的問題
      • 使用多副本 ms_1,ms_2,ms_3 每個節點保存一份數據,使得請求分散到多個節點,避免單點熱點問題。

建立多個 Cluster ,和微服務、存儲等一起組成一個 Region。 這樣相當於是用空間換時間: 同一個 key 在每一個 frontend cluster 都可能有一個 copy,這樣會帶來 consistency 的問題,但是這樣能夠降低 latency 和提高 availability。利用 MySQL Binlog 消息 anycast 到不同集群的某個節點清理或者更新緩存;

當業務頻繁更新時候,cache頻繁過期,會導致命中率低: stale sets 如果應用程序層可以忍受稍微過期一點的數據,針對這點可以進一步降低系統負載。當一個key 被刪除的時候(delete 請求或者 cache 爆棚清空間了),它被放倒一個臨時的數據結構裡,會再續上比較短的一段時間。當有請求進來的時候會返回這個數據並標記為“Stale”。對於大部分應用場景而言,Stale Value 是可以忍受的。 (需要改 memcache、redis 源碼,或者基礎庫支持);

穿透緩存 (Through Cache)

  • singlefly 對關鍵字進行一致性 hash,使其某一個維度的 key 一定命中某個節點,然後在節點內使用互斥鎖,保證歸併回源,但是對於批量查詢無解;
  • 分佈式鎖 (不建議) 設置一個 lock key,有且只有一個人成功,並且返回,交由這個人來執行回源操作,其他候選者輪訓 cache 這個 lock key,如果不存在去讀數據緩存,hit 就返回,miss 繼續搶鎖;
  • 隊列 如果 cache miss,交由隊列聚合一個key,來 load 數據回寫緩存,對於 miss 當前請求可以使用 singlefly 保證回源,如評論架構實現。適合回源加載數據重的任務,比如評論 miss 只返回第一頁,但是需要構建完成評論數據索引。
  • lease (租) 通過加入 lease 機制,可以很好避免這兩個問題,lease 是 64-bit 的 token,與客戶端請求的 key 綁定,對於過時設置,在寫入時驗證 lease,可以解決這個問題;對於 thundering herd (驚群問題),每個key 10s 分配一次,當 client 在沒有獲取到 lease 時,可以稍微等一下再訪問 cache,這時往往cache 中已有數據。 (基礎庫支持 & 修改 cache 源碼);

緩存技巧

Incast Congestion

如果在網路中的包太多,就會發生 Incast Congestion 的問題(可以理解為,network 有很多switch,router 啥的, 一但一次性發一堆包,這些包同時到達 switch,這些 switch 就會忙不過來)。

應對這個問題就是不要讓大量包在同一時間發送出去,在客戶端限制每次發出去的包的數量(具體實現就是客戶端弄個隊列)。

每次發送的包的數量稱為“Window size”。這個值太小的話,發送太慢,自然延遲會變高;這個值太大,發送的包太多把 network switch 搞崩潰了,就可能發生比如丟包之類的情況,可能被當作 cache miss,這樣延遲也會變高。所以這個值需要調,一般會在 proxy 層面實現。

小技巧

  • 易讀性的前提下,key 設置盡可能小,減少資源的佔用,redis value 可以用 int 就不要用 string,對於小於 N 的 value,redis 內部有 shared_object(共享對象池) 緩存。
  • 拆分 key。主要是用在 redis 使用 hashes 情況下。同一個 hashes key 會落到同一個 redis 節點,hashes 過大的情況下會導致內存及請求分佈的不均勻。考慮對 hash 進行拆分為小的hash,使得節點內存均勻及避免單節點請求熱點。
  • 空緩存設置。對於部分數據,可能數據庫始終為空,這時應該設置空緩存,避免每次請求都緩存 miss 直接打到 DB。 緩存穿透
  • 空緩存保護策略。
  • 讀失敗後的寫緩存策略(降級後一般讀失敗不觸發回寫緩存)。
  • 序列化使用 protobuf,盡可能減少 size。
  • 工具化澆水代碼 (處理讀寫緩存 cache miss 模板, 先查cache,cache不在再查DB, 再異步回 channel 再異步回寫cache) back to source

memcache 小技巧

  • flag 使用:標識 compress、encoding、large value 等;
  • memcache 支持 gets,盡量讀取,盡可能的 pipeline,減少網絡往返;
  • 使用二進制協議,支持 pipeline delete,UDP 讀取、TCP 更新;

redis 小技巧

  • 增量更新一致性:EXPIRE、ZADD/HSET 等,保證索引結構體務必存在的情況下去操作新增數據;
  • BITSET: 存儲每日登陸用戶,單個標記位置(boolean),為了避免單個 BITSET 過大或者熱點,需要使用 region sharding,比如按照mid求餘 %和/ 10000,商為 KEY、餘數作為offset;
  • List:抽獎的獎池、頂彈幕,用於類似 Stack PUSH/POP操作;
  • Sortedset: 翻頁、排序、有序的集合,杜絕 zrange 或者 zrevrange 返回的集合過大;
  • Hashs: 過小的時候會使用壓縮列表、過大的情況容易導致 rehash 內存浪費,也杜絕返回hgetall,對於小結構體,建議直接使用 memcache KV;
  • String: SET 的 EX/NX 等 KV 擴展指令,SETNX 可以用於分佈式鎖、SETEX 聚合了SET + EXPIRE;
  • Sets: 類似 Hashs,無 Value,去重等(去除重複);
  • 盡可能的 PIPELINE 指令,但是避免集合過大;
  • 避免超大 Value;

Distributed Transaction

分布式事務

講到事務,又得搬出經典的轉賬問題了: 支付寶賬戶表:A (id, user_id, amount) 餘額寶賬戶表:B (id, user_id, amount)

用戶的 user_id = 1,從支付寶轉帳1萬快到餘額寶分為兩個步驟:

  1. 支付寶表扣除1萬: UPDATE A SET amount = amount - 10000 WHERE user_id = 1;
  2. 餘額寶表增加1萬: UPDATE B SET amount = amount + 10000 WHERE user_id = 1; 如何保證數據一致性呢? 單個數據庫,我們保證 ACID 使用 數據庫事務。

隨著我們系統變大,我們進行了微服務架構的改造,因為每個微服務獨占了一個數據庫實例, 從 user_id = 1 發起的轉帳動作,跨越了兩個微服務:pay 和 balance 服務。 我們需要保證,跨多個服務的步驟數據一致性:

  1. 微服務 pay 的支付寶表扣除1萬;
  2. 微服務 balance 的餘額寶表增加1萬;

每個系統都對應一個獨立的數據源,且可能位於不同機房,同時調用多個系統的服務很難保證同時成功,這就是跨服務分佈式事務問題。 通常都先扣錢, 再加錢 我們系統應該能保證每個服務自身的 ACID,基於這個假設,我們事務消息解決分佈式事務問題。

事務消息

在北京很有名的姚記炒肝點了炒肝並付了錢後,他們並不會直接把你點的炒肝給你, 往往是給你一張小票,然後讓你拿著小票到出貨區排隊去取。

為什麼他們要將付錢和取貨兩個動作分開呢? 原因很多,其中一個很重要的原因是為了使他們接待能力增強(並發量更高)。

只要這張小票在,你最終是能拿到炒肝的。同理轉賬服務也是如此。

當支付寶賬戶扣除1萬後,我們只要生成一個憑證(消息)即可,這個憑證(消息)上寫著“讓余額寶賬戶增加 1萬”, 只要這個憑證(消息)能可靠保存,我們最終是可以拿著這個憑證(消息)讓余額寶賬戶增加1萬的, 即我們能依靠這個憑證(消息)完成最終一致性。

如何可靠的保存消息憑證?

要解決消息可靠存儲,我們實際上需要解決的問題是,本地的 mysql 存儲和 message 存儲的一致性問題。

  • Transactional outbox
  • Polling publisher
  • Transaction log tailing
  • 2PC Message Queue 事務消息一旦被可靠的持久化,我們整個分佈式事務,變為了最終一致性,消息的消費才能保障最終業務數據的完整性,所以我們要盡最大努力,把消息送達到下游的業務消費方,稱為:Best Effort。只有消息被消費,整個交易才能算是完整完結。

Best Effort

即盡最大努力交付,主要用於在這樣一種場景:不同的服務平台之間的事務性保證。 比如我們在電商購物,使用支付寶支付;又比如玩網游的時候,通過 App Store 充值。 拿購物為例,電商平台與支付平台是相互獨立的,隸屬於不同的公司,即使是同一個公司也很可能是獨立的部門。

"做過支付寶交易接口的同學都知道,我們一般會在支付寶的回調頁面和接口裡,解密參數, 然後調用系統中更新交易狀態相關的服務,將訂單更新為付款成功。 同時,只有當我們回調頁面中輸出了success 字樣或者標識業務處理成功相應狀態碼時,支付寶才會停止回調請求。 否則,支付寶會每間隔一段時間後,再向客戶方發起回調請求,直到輸出成功標識為止。"

Transactional outbox

Transactional outbox,支付寶在完成扣款的同時,同時記錄消息數據,這個消息數據與業務數據保存在同一數據庫實例裡(消息記錄表表名為 msg)。

BEGIN TRANSACTION
    UPDATE A SET amount = amount - 10000 WHERE user_id = 1;
    INSERT INTO msg(user_id, amount, status) VALUES(1, 10000, 1);
END TRANSACTION
COMMIT;

上述事務能保證只要支付寶賬戶裡被扣了錢,消息一定能保存下來。當上述事務提交成功後,我們想辦法將此消息通知餘額寶,餘額寶處理成功後發送回復成功消息,支付寶收到回復後刪除該條消息數據。

Polling Publisher

Polling publisher,我們定時的輪訓 msg 表,把 status = 1 的消息統統拿出來消費, 可以按照自增 id 排序,保證順序消費。 在這裡我們獨立了一個 pay_task 服務,把拖出來的消息 publish 給我們消息隊列, balance 服務自己來消費隊列,或者直接 rpc 發送給 balance 服務。

實際我們第一個版本的 archive-service 在使用 CQRS 時,就用的這個模型,Pull 的模型,從延遲來說不夠好,Pull 太猛對 Database 有一定壓力,Pull 頻次低了,延遲比較高。

Transaction log tailing ( bilibili 用這個方式)

Transaction log tailing, 上述(Polling Publisher)保存消息的方式使得消息數據和業務數據緊耦合在一起, 從架構上看不夠優雅,而且容易誘發其他問題。

有一些業務場景,可以直接使用主表被 canal 訂閱使用,有一些業務場景自帶這類 message 表, 比如訂單或者交易流水,可以直接使用這類流水表作為 message 表使用。

使用 canal 訂閱以後,是實時流式消費數據,在消費者 balance 或者 balance-job 必須努力送達到。

我們發現,所有努力送達的模型,必須是先預扣(預佔資源)的做法。

幕等 (idempotent、idempotence)

冪等, 簡單的來說,就是「反覆執行同樣的 ETL 排程,會得到一樣的結果。」 source from: [Data] Data Pipeline 101(五) — 冪等性

例如現在有一隻 ETL 排程每天早上的 8:00 會計算前一天營業額,這個排程在 4/1 號執行時,會讀取 3/31 號的資料,計算結果並寫入到結果表中。如果 4/2 號重新執行 4/1 的排程時,也能得到一模一樣的結果。我們希望這隻程式是 reliable(可信賴的) 的。

還有一個很嚴重的問題就是消息重複投遞, 如果相同的消息被重複投遞兩次,那麼我們餘額寶賬戶將會增加2萬而不是1萬了。

為什麼相同的消息會被重複投遞? 比如餘額寶處理完消息 msg 後,發送了處理成功的消息給支付寶, 正常情況下支付寶應該要刪除消息msg,但如果支付寶這時候悲劇的掛了, 重啟後一看消息 msg 還在,就會繼續發送消息 msg。

  • 全局唯一 ID+ 去重表 在餘額寶這邊增加消息應用狀態表 msg_apply,通俗來說就是個賬本,用於記錄消息的消費情況,每次來一個消息,在真正執行之前,先去消息應用狀態表中查詢一遍,如果找到說明是重複消息,丟棄即可,如果沒找到才執行,同時插入到消息應用狀態表(同一事務)。 idempotent
  • 版本號

二階段提交 (Two Phase Commitment, 2PC)

兩階段提交協議(Two Phase Commitment Protocol)中,涉及到兩種角色

  1. 一個事務協調者(coordinator):負責協調多個參與者進行事務投票及提交(回滾)
  2. 多個事務參與者(participants):即本地事務執行者 (如下圖的A支付寶數據庫,B餘額寶數據庫)

總共處理步驟有兩個

  1. 投票階段(voting phase):協調者將通知事務參與者準備提交或取消事務,然後進入表決過程。參與者將告知協調者自己的決策:同意(事務參與者本地事務執行成功,但未提交)或取消(本地事務執行故障);
  2. 提交階段(commit phase):收到參與者的通知後,協調者再向參與者發出通知,根據反饋情況決定各參與者是否要提交還是回滾;

2PC Message Queue

Seata 2PC

Seata 實現 2PC 與傳統 2PC 的差別

  • 架構層次方面:傳統 2PC 方案的 RM(Resource Manager) 實際上是在數據庫層,RM 本質上就是數據庫自身,通過 XA 協議實現,而 Seata 的 RM 是以 jar 包的形式作為中間件層部署在應用程序這一側的。

  • 兩階段提交方面:傳統 2PC無論第二階段的決議是 commit 還是 rollback ,事務性資源的鎖都要保持到 Phase2 完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。

TC (Transaction Coordinator) - 事務協調者:維護全局和分支事務的狀態,驅動全局事務提交或回滾。 TM (Transaction Manager) - 事務管理器:定義全局事務的範圍,開始全局事務、提交或回滾全局事務。 RM ( Resource Manager ) - 資源管理器:管理分支事務處理的資源( Resource ),與 TC 交談以註冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。

  1. TM 開啟全局事務
  2. RM 向 TC 註冊分支事務
  3. RM 向 TC 報告分支事務狀態
  4. TC 向 RM 發送 commit/rollback 請求
  5. TM 結束全局事務

6 张图带你彻底搞懂分布式事务 XA 模式

TCC

TCC 是 TryConfirmCancel 三個詞語的縮寫, TCC 要求每個分支事務實現三個操作:預處理 Try、確認 Confirm、撤銷 Cancel。

Try 操作做業務檢查及資源預留, Confirm 做業務確認操作, Cancel 實現一個與 Try 相反的操作即回滾操作。

TM 首先發起所有的分支事務的 Try 操作, 任何一個分支事務的 Try 操作執行失敗,TM 將會發起所有分支事務的 Cancel 操作, 若 Try 操作全部成功,TM 將會發起所有分支事務的 Confirm 操作, 其中 Confirm/Cancel 操作若執行失敗,TM 會進行重試。

需要注意:

  • 空回滾
  • 防懸掛

微服務

References

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""