談談對分佈式事務的一點理解和解決方案

前提

最近,工做中要爲如今的老系統作拆分和升級,恰好遇到了分佈式事務、冪等控制、異步消息亂序和補償方案等問題,恰好基於實踐結合我的的見解記錄一下一些方案和思路。前端

分佈式事務

首先,作系統拆分的時候幾乎都會遇到分佈式事務的問題,一個仿真的案例以下:java

j-t-s-i-a-1.png

項目初期,因爲用戶體量不大,訂單模塊和錢包模塊共庫共應用(大war包時代),模塊調用能夠簡化爲本地事務操做,這樣作只要不是程序自己的BUG,基本能夠避免數據不一致。後面由於用戶體量愈加增大,基於容錯、性能、功能共享等考慮,把原來的應用拆分爲訂單微服務和錢包微服務,兩個服務之間經過非本地事務操(這裏能夠是HTTP或者消息隊列等)做進行數據同步,這個時候就頗有可能因爲異常場景出現數據不一致的狀況。git

事務中直接RPC調用達到強一致性

以上面的訂單微服務請求錢包微服務進行扣款並更新訂單狀態爲扣款這個調用過程爲例,假設採用HTTP同步調用,項目若是由經驗不足的開發者開發這個邏輯,可能會出現下面的僞代碼:github

[訂單微服務請求錢包微服務進行扣款並更新訂單狀態]

處理訂單微服務請求錢包微服務進行扣款並更新訂單狀態方法(){
    [開啓事務]
    一、查詢訂單
    二、HTTP調用錢包微服務扣款
    三、更新訂單狀態爲扣款成功
    [提交事務]
}

這是一個從肉眼上看起來沒有什麼問題的解決方法,HTTP調用直接嵌入到事務代碼塊內部,猜測最初開發者的想法是:HTTP調用失敗拋出異常會致使事務回滾,用戶重試便可;HTTP調用成功,事務正常提交,業務正常完成。這種作法看似可取,可是帶來了極大的隱患,根本緣由是:事務中嵌入了RPC調用。假設兩種比較常見的狀況:redis

  • 一、上面方法中第2步因爲錢包微服務自己各類緣由致使扣款接口響應極慢,會致使上面的處理方法事務(準確來講是數據庫鏈接)長時間掛起,持有的數據庫鏈接沒法釋放,會致使數據庫鏈接池的鏈接耗盡,很容易致使訂單微服務的其餘依賴數據庫的接口沒法響應。
  • 二、錢包微服務是單節點部署(並非全部的公司微服務都作得很完善),升級期間應用停機,上面方法中第2步接口調用直接失敗,這樣會致使短期內全部的事務都回滾,至關於訂單微服務的扣款入口是不可用的。
  • 三、網絡是不可靠的,HTTP調用或者接受響應的時候若是出現網絡閃斷有可能出現了服務間狀態不能互相明確的狀況,例如訂單微服務調用錢包微服務成功,接受響應的時候出現網絡問題,會出現扣款成功可是訂單狀態沒有更新的可能(訂單微服務事務回滾)。

j-t-s-i-a-2.png

儘管如今有Hystrix等框架能夠基於線程池隔離調用或者基於熔斷器快速失敗,可是這是收效甚微的。所以,我的認爲事務中直接RPC調用達到強一致性是徹底不可取的,若是使用了這種方式實現"分佈式事務"建議整改,不然只能天天祈求下游服務或者網絡不出現任何問題。算法

事務中進行異步消息推送

使用消息隊列進行服務之間的調用也是常見的方式之一,可是使用消息隊列交互本質是異步的,沒法感知下游消息消費方是否正常處理消息。用前一節的例子,假設採用消息隊列異步調用,項目若是由經驗不足的開發者開發這個邏輯,可能會出現下面的僞代碼:spring

[訂單微服務請求錢包微服務進行扣款並更新訂單狀態]

處理訂單微服務請求錢包微服務進行扣款並更新訂單狀態方法(){
    [開啓事務]
    一、查詢訂單
    二、推送錢包微服務扣款消息(推送消息)
    三、更新訂單狀態爲扣款成功
    [提交事務]
}

上面的處理方法若是抽象一點表示以下:sql

方法(){
    DataSource  dataSource = xx;
    Connection con = dataSource.getConnection();
    con.setAutoCommit(false);
    try{
       一、SQL操做;
       二、推送消息;
       三、SQL操做;
       con.commit();
    }catch(Exception e){
        con.rollback();
    }finally{
        釋放其餘資源;
        release(con);
    }
}

這樣作,在正常狀況下,也就是可以正常調用消息隊列中間件推送消息成功的狀況下,事務是可以正確提交的。可是存在兩個明顯的問題:數據庫

  • 一、消息隊列中間件出現了異常,沒法正常調用,常見的狀況是網絡緣由或者消息隊列中間件不可用,會致使異常從而使得事務回滾。這種狀況看起來彷佛合情合理,可是仔細想:爲何消息隊列中間件調用異常會致使業務事務回滾,若是中間件不恢復,這個接口調用豈不是至關於不可用?
  • 二、若是消息隊列中間件正常,消息正常推送,可是第3步因爲SQL存在語法錯誤致使事務回滾,這樣就會出現了下游微服務被調用成功,本地事務卻回滾的問題,致使了上下游系統數據不一致。

j-t-s-i-a-3.png

總的來講:事務中進行異步消息推送是一種並不可靠的實現編程

目前業界提供的解決方案

業界目前主流的分佈式事務解決方案主要有:多階段提交方案(2PC、3PC)、補償事務(TCC)和消息事務(主要是RocketMQ,基本思想也是多階段提交方案,而且基於中間提供件輪詢和重試,其餘消息隊列中間件並無實現分佈式事務)。這些方案的原理在此處不展開,目前網絡中相應資料比較多,小結一下它們的特色:

  • 多階段提交方案:常見的有二階段和三階段提交事務,須要額外的資源管理器來協調事務,數據一致性強,可是實現方案比較複雜,對性能的犧牲比較大(主要是須要對資源鎖定,等待全部事務提交才能解鎖),不適用於高併發的場景,目前比較知名的有阿里開源的fescar
  • 補償事務:通常也叫TCC,由於每一個事務操做都須要提供三個操做嘗試(Try)、確認(Confirm)和補償/撤銷(Cancel),數據一致性的強度比多階段提交方案低,可是實現的複雜度會有所下降,比較明顯的缺陷是每一個業務事務須要實現三組操做,有可能出現過多的補償方案的代碼;另外有不少輸完液場景TCC是不合適的。
  • 消息事務:這裏只談RocketMQ的實現,一個事務的執行流程包括:發送預消息、執行本地事務、確認消息發送成功。它的消息中間件存儲了下游沒法消費成功的消息,而且不斷重試推送下游消費消息,而生產者(上游)須要提供一個check接口,用於檢查成功發送預消息可是未確認最終消息發送狀態的事務的狀態。

項目實踐中最終使用的方案

我的所在的公司的技術棧中沒有使用RocketMQ,主要使用RabbitMQ,因此須要針對RabbitMQ作消息事務的適配。目前業務系統中消息異步交互存在三種場景:

  • 一、消息推送實時性高,能夠接受丟失。
  • 二、消息推送實時性低,不能丟失。
  • 三、消息推送實時性高,不能丟失。

最終敲定使用了本地消息表的解決方案,這個方案十分簡單:

j-t-s-i-a-4.png

主要思路是:

  • 一、須要發送到消費方的消息的保存和業務處理綁定在同一個本地事務中,須要額外創建一張本地消息表。
  • 二、本地事務提交以後,能夠在事務外對本地消息表進行查詢而且進行消息推送,或者採用定時調度輪詢本地消息表進行消息推送。
  • 三、下游服務消費消息成功能夠回調一個確認到上游服務,這樣就能夠從上游服務的本地消息表刪除對應的消息記錄。

僞代碼以下:

[消息推送實時性高,能夠接受丟失-這種狀況下能夠不須要寫入本地消息表 - start]
處理方法(){
    [本地事務開始]
    一、處理業務操做
    [本地事務提交]
    二、組裝推送消息而且進行推送
}
[消息推送實時性高,能夠接受丟失-這種狀況下能夠不須要寫入本地消息表 - end]

[消息推送實時性低,不能丟失 - start]
處理方法(){
    [本地事務開始]
    一、處理業務操做
    二、組裝推送消息而且寫入到本地消息表
    [本地事務提交]
}

消息推送調度模塊(){
    三、查詢本地消息表待推送數據進行推送
}
[消息推送實時性低,不能丟失 - end]

[消息推送實時性高,不能丟失 - start]
處理方法(){
    [本地事務開始]
    一、處理業務操做
    二、組裝推送消息而且寫入到本地消息表
    [本地事務提交]
    三、消息推送
}

消息推送調度模塊(){
    四、查詢本地消息表待推送數據進行推送
}
[消息推送實時性高,不能丟失 - end]
  • 對於"消息推送實時性高,能夠接受丟失"這種狀況,實際上不用依賴本地消息表,只要在業務操做事務提交以後組裝和推送消息便可,這種狀況會存在由於消息隊列中間件不可用或者本地應用宕機致使消息丟失的問題(本質是由於數據是內存態,非持久化),可靠性不高,可是絕大多數狀況下是沒有問題的。若是使用spring-tx的聲明式事務@Transactional或者編程式事務TransactionTemplate,能夠使用事務同步器實現嵌入於業務操做事務代碼塊中的RPC操做延後到事務提交後執行,這樣子RPC調用的代碼物理位置就能夠放置在事務代碼塊內,例如:
@Transactional(rollbackFor = RuntimeException.class)
public void process(){
	1.處理業務邏輯
	TransactionSynchronizationManager.getSynchronizations().add(new TransactionSynchronizationAdapter() {
		@Override
		public void afterCommit() {
			2.進行消息推送
		}
	});
}

對於使用到本地消息表的場景,須要警戒下面幾個問題:

  • 一、注意本地消息表儘可能不要長時間積壓數據,推送成功的數據須要及時刪除。
  • 二、本地消息表的數據在查詢而且推送的時候,須要設計最大重試次數上限,達到上限仍然推送失敗的記錄須要進行預警和人爲干預。
  • 三、若是入庫的消息體比較大,查詢可能消耗的IO比較大,須要考慮拆分單獨的一張消息內容表用於存放消息體內容,而常常更變的列應該單獨拆分到另一張表。

例如本地消息表的設計以下:

CREATE TABLE `t_local_message`(
  id BIGINT PRIMARY KEY COMMENT '主鍵',
  module INT NOT NULL COMMENT '消息模塊',
  tag VARCHAR(20) NOT NULL COMMENT '消息標籤',
  business_key VARCHAR(60) NOT NULL COMMENT '業務鍵',
  queue VARCHAR(60) NOT NULL COMMENT '隊列',
  exchange VARCHAR(60) NOT NULL COMMENT '交換器',
  exchange_type VARCHAR(10) NOT NULL COMMENT '交換器類型',
  routing_key VARCHAR(60) NOT NULL COMMENT '路由鍵',
  retry_times TINYINT NOT NULL DEFAULT 0 COMMENT '重試次數',
  create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立日期時間',
  edit_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改日期時間',
  seq_no VARCHAR(60) NOT NULL COMMENT '流水號',
  message_status TINYINT NOT NULL DEFAULT 0 COMMENT '消息狀態',
  INDEX idx_business_key(business_key),
  INDEX idx_create_time(create_time),
  UNIQUE uniq_seq_no(seq_no)
)COMMENT '本地消息表';


CREATE TABLE `t_local_message_content`(
  id BIGINT PRIMARY KEY COMMENT '主鍵',
  message_id BIGINT NOT NULL COMMENT '本地消息表主鍵',
  message_content TEXT COMMENT '消息內容',
  UNIQUE uniq_message_id(message_id)
)COMMENT '本地消息內容表';

分佈式事務小結

我的認爲,解決分佈式事務的最佳實踐就是:

  • 規避使用強一致性的分佈式事務實現,基本觀念就是放棄ACID投奔BASE
  • 推薦使用消息隊列進行系統間的解耦,消息推送方爲了確保消息推送成功能夠獨立附加消息表把須要推送的消息和業務操做綁定在同一個事務內,使用異步或者調度的方式進行推送。
  • 消息推送方(上游)須要確保消息正確投遞到消息隊列中間件,消息消費或者補償方案由消息消費方(下游)自行解決,關於這一點後文一個章節專門解釋。

其實,對於一致性和實時性要求相對較高的分佈式事務的實現,使用消息隊列解耦也有對應的解決方案。

冪等控制

冪等(idempotence)這個術語原文來自於HTTP/1.1協議中的定義:

Methods can also have the property of 「idempotence」 in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

簡單來講就是:除了錯誤或者過時的請求(換言之就是成功的請求),不管屢次調用仍是單次調用最終獲得的效果是一致的。通俗來講,有一次調用成功,採用相同的請求參數不管調用多少次(重複提交)都應該返回成功。

下游服務對外提供服務接口,必須承諾實現接口的冪等性,這一點在分佈式系統中極其重要。

  • 對於HTTP調用,承諾冪等性能夠避免表單或者請求操做重複提交形成業務數據重複。
  • 對於異步消息調用,承諾冪等性經過對消息去重處理也是用於避免重複消費形成業務數據重複。

目前實踐中對於冪等的處理使用了下面三個方面的控制:

  • 一、實現冪等的接口調用時入口使用分佈式鎖,使用了主流的Redisson,控制鎖的粒度和鎖的等待、持有時間在合理範圍(筆者所在行業要求數據必須準確無誤,因此幾乎用悲觀鎖設計全部核心接口,寧願慢也不能錯,實際上若是衝突比較低的時候爲了性能優化能夠考慮使用樂觀鎖)。
  • 二、業務邏輯上的防重,例如建立訂單的接口先作一步經過訂單號查詢庫表中是否已經存在對應的訂單,若是存在則不作處理直接返回成功。
  • 三、數據庫表設計對邏輯上惟一的業務鍵作惟一索引,這個是經過數據庫層面作最後的保障。

舉一個基於消息消費冪等控制的僞代碼例子:

[處理消息消費]
listen(request){
    一、經過業務鍵構建分佈式鎖的KEY
    二、經過Redisson構建分佈式鎖而且加鎖
    三、加鎖代碼中執行業務邏輯(包括去重判斷、事務操做和非事務操做等)
    四、finally代碼塊中釋放分佈式鎖
}

補償方案

補償方案主要是HTTP同步調用的補償和異步消息消費失敗的補償。

HTTP同步調用補償

通常狀況下,HTTP同步調用會獲得下游系統的同步結果,對結果的處理存在下面幾種常見的狀況:

  • 一、同步結果返回正常,獲得了和下游約定的最終狀態,交互結束,通常認爲成功就是最終狀態,不須要補償。
  • 二、同步結果返回正常,獲得了和下游約定的最終狀態,須要定時補償到最終狀態或到達重試上限自行標記爲最終狀態。
  • 三、同步結果返回異常,最多見的是下游服務不可用返回HTTP狀態碼爲5XX。

首先要有一個簡單的認知:短期內的HTTP重試一般狀況下都是無效的。若是是瞬時的網絡抖動,短期內HTTP同步重試是可行的,大部分狀況下是下游服務沒法響應、下游服務重啓中或者複雜的網絡狀況致使短期內沒法恢復,這個時候作HTTP同步重試調用每每是無效的。

若是面對的場景是內部低併發量的系統之間的進行HTTP交互,能夠考慮使用基於指數退避的算法進行重試,舉個例子:

一、第一次調用失敗,立刻進行第二次重試
二、第二次重試失敗,線程休眠2秒
三、第三次重試失敗,線程休眠4秒(2^2)
四、第四次重試失敗,線程休眠8秒(2^8)
五、第五次重試失敗,拋出異常

若是上面的例子中使用了Hystrix控制超時爲1秒包裹着要執行的HTTP命令進行調用,上面的重試過程最大耗時小於20秒,在低併發的內部系統之間的交互是能夠接受的。

可是,若是面對的是併發比較高、用戶體驗優先級比較高的場景,這樣作顯然是不合理的。爲了穩妥起見,能夠採起相對傳統而有效的方案:HTTP調用的調用瞬時內容保存到一張本地重試表中,這個保存操做綁定在業務處理的事務中,經過定時調度對未調用成功的記錄進行重試。這個方案和上文提到保證消息推送成功的方案相似,舉一個仿真的例子:

[下單接口請求下游錢包服務扣錢的過程]
process(){
    [事務代碼塊-start]
    一、處理業務邏輯,保存訂單信息,訂單狀態爲扣錢處理中
    二、組裝將要向下遊錢包服務發起的HTTP調用信息,保存在本地表中
    [事務代碼塊-end]
    三、事務外進行HTTP調用(OkHttp客戶端或者Apache的Http客戶端),調用成功更新訂單狀態爲扣錢成功
}

定時調度(){
    四、定時查詢訂單狀態爲扣錢處理中的訂單進行HTTP調用,調用成功更新訂單狀態爲扣錢成功
}

異步消息消費失敗補償

異步消息消費失敗的場景發生只能在消息消費方,也就是下游服務。從下降成本的目的上看,消息消費失敗的補償應該由消息處理的一方(消費者)自行承擔,畫一個系統交互圖理解一下:

j-t-s-i-a-5.png

若是由上游服務進行補償,存在兩個明顯的問題:

  • 一、消息補償模塊須要在全部的上游服務中編寫,這是不合理的。
  • 二、一旦下游消費出現生產問題須要上游補償,須要先定位出對應的消息是哪一個上游服務推送,而後經過該上游服務進行補償,處理生產問題的複雜度提升。

在最近的一些項目實踐中,肯定在使用異步消息交互的時候,補償統一由消息消費方實現。最簡單的方式也是使用相似本地消息表的方式,把消費失敗的消息入庫,而且進行重試,到達重試上限依然失敗則進行預警和人工介入便可。簡單的流程圖以下:

j-t-s-i-a-6.png

異步消息亂序解決

異步消息亂序是使用消息隊列進行異步交互場景中須要考慮和解決的問題。下面舉一些可能不合乎實際可是可以說明問題的例子。

場景一:上游某個服務向用戶服務經過消息隊列異步修改用戶的性別信息,假設消息簡化以下:

隊列:user-service.modify.sex.qeue
消息:
{
   "userId": 長整型,
   "sex": 字符串,可選值是MAN、WOMAN和UNKNOW
}

用戶服務一共使用了10個消費者線程監聽user-service.modify.sex.qeue隊列。假設上游服務前後向user-service.modify.sex.qeue隊列推送下面兩條消息:

第一條消息:
{
   "userId": 1,
   "sex": "MAN"
}  

第二條消息:
{
   "userId": 1,
   "sex": "WOMAN"
}

上面的消息推送和下游處理有比較高概率出現下面的狀況:

j-t-s-i-a-7.png

本來用戶ID爲1的用戶先把性別改成MAN(第一次請求),後來改成WOMAN(第二次請求),最終看到更新後的性別有多是MAN,這顯然是不合理的。這個不是很合理的例子想說明的問題是:經過異步消息交互,下游服務處理消息的時序有可能和上游發送消息的時序並不一致,這樣有可能致使業務狀態錯亂。對於解決這個問題,提供幾個可行的思路:

  • 方案一:併發要求不高的狀況下,能夠充分利用消息隊列FIFO的特性(這一點RabbitMQ實現了,其餘消息隊列中間件不肯定),把下游服務的消費線程設置爲1便可,那麼上游推送的消息和下游消費消息的時序是一致的。
  • 方案二:使用HTTP調用,這個要前端或者APP客戶端配合,請求設計成串行的便可。

場景二:沒有時序要求的異步消息處理,可是要求最終展現的時候是有時序的。這樣說可能有點抽象,舉個例子:在借唄上借了10000元,還款的時候,用戶是分屢次還清(例如還款方案一:2000,3000,5000;還款方案二:1000,1000,1000,7000等等),每次還的錢都不同,最終要求帳單展現的時候是按照用戶的還款操做順序。

假設借唄的上游服務和它經過異步消息交互。詳細分析一下:這個場景其實對於借唄(主要是考慮收回用戶的還款這個目的)來講,對用戶還款的順序並不須要感知,只須要考慮用戶是否還清,可是使用異步交互,有可能致使下游沒法正確得知用戶還款的操做順序。

解決方案很簡單:推送消息的時候附加一個帶有增加或者減小趨勢的標記位便可,例如使用帶有時間戳的標記位或者使用Snowflake算法生成自增趨勢的長整型數做爲流水號,以後按照流水號排序便可獲得消息操做的順序(這個流水號下游須要保存),可是實際消息處理的時候並不須要感知消息的時序。

異步消息結合狀態驅動

我的認爲:異步消息結合狀態驅動是能夠相對完善地解決分佈式事務,結合預處理(例如預扣除或者預增加)能夠知足比較高一致性和實時性。先引出一個常常用來討論分佈式事務強一致性的轉帳場景。

j-t-s-i-a-8.png

解決這個問題若是使用同步調用(其實像TCC2PC或者3PC等本質都是同步調用),在容許性能損失的狀況下是可以達到強一致性。這一節並不討論同步調用的狀況下怎麼作,重點研究一下在使用消息隊列的狀況下,如何從BASE的角度"達到比較高的一致性"。先把這個例子抽象化,假設兩個系統的帳戶表都設計成這樣:

CREATE TABLE `t_account`(
    id BIGINT PRIMARY KEY COMMENT '主鍵',
    user_id BIGINT NOT NULL COMMENT '用戶ID',
    balance DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '帳戶餘額',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
    edit_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改時間',
    version BIGINT NOT NULL DEFAULT 0 COMMENT '版本'
    // 省略索引
)COMMENT '帳戶表';

兩個系統均可以創建一張表結構類似的金額變動流水錶,上游系統用於作預扣操做和流水記錄,下游系統用於作流水記錄,接着咱們能夠梳理出新的交互時序邏輯以下:

[A系統本地事務-start]
一、A系統t_account表X用戶餘額減去1000
二、A系統流水錶寫入一條用戶X的預扣1000的記錄,標記狀態爲處理中,生成全局惟一的流水號記爲SEQ_NO
[A系統本地事務-end]
三、A系統經過消息隊列推送一條用戶X扣減1000的消息(必定要附帶流水號SEQ_NO)到消息隊列中間件(這裏能夠用上文提到的技巧確保消息推送成功)
[B系統本地事務-start]
四、B系統t_account表X用戶餘額加上1000
五、B系統流水錶寫入一條用戶X的餘額變動(增長)1000的記錄 <= 注意這裏B系統的流水只能insert不能update
[B系統本地事務-end]
六、B系統推送處理X用戶餘額處理成功的消息到消息隊列中間件,必定要附帶流水號SEQ_NO(這裏能夠用上文提到的技巧確保消息推送成功)
[A系統本地事務-start]
七、A系統更新流水錶中X用戶流水號爲SEQ_NO的預扣記錄的狀態爲處理成功(這一步必定要作好冪等控制,能夠考慮用SEQ_NO做爲分佈式鎖的KEY)
[A系統本地事務-end]

其餘:
[A系統流水錶處理中的記錄須要定時輪詢和重試]
一、定時調度重試A系統流水錶中狀態爲處理中的記錄

[A-B系統日切對帳模塊]
一、日切,用A系統中處理成功的T-1日流水記錄和B系統中的流水錶全部T-1日的記錄進行對帳

j-t-s-i-a-9.png

上面的步驟看起來比較多,並且還須要編寫對帳和重試模塊。其實,在上下游系統、消息隊列中間件都正常運做的狀況下,上面的這套交互方案可承受的併發量遠比同步方案高,出現了服務或者消息隊列中間件不可用的狀況下,因爲流水錶有未處理的本地記錄,在這些問題恢復以後能夠重試,可靠性也是比較高的。另外,重試和對帳的模塊,對於全部涉及金額交易的處理都是必須的,這一點其實選用同步或者異步交互方式並無關係。

小結

你會發覺,通篇文章有不少方案都是使用了待處理內容寫入本地表 + 事務外實時觸發 + 定時調度補償這個模式,其實我想表達的就是這個模式是目前分佈式解決方案中一個相對通用的模式,能夠基本知足分佈式事務、同步異步補償、實時非實時觸發等多種複雜場景的處理。這個模式也存在一些明顯的問題(若是實踐過的話通常會遇到):

  • 一、庫表(本地消息表)設計不合理或者處理不合理容易成爲數據庫的瓶頸。
  • 二、補償或者本地表入庫處理的邏輯代碼容易冗餘和腐化。
  • 三、極端狀況下,異常恢復的場景存在拖垮服務的隱患。

其實,更多的時候須要結合現有的系統或者場景進行分析,經過數據監控和分析進行後續優化。畢竟,架構是迭代出來,而不是設計出來的

(本文完 e-a-20190323 c-14-d 996 這是一篇2019年3月底寫的文章,如今發出來但願尚未過期)

技術公衆號《Throwable文摘》(id:throwable-doge),不按期推送筆者原創技術文章(毫不抄襲或者轉載):

相關文章
相關標籤/搜索