數據一致性(一) - 接口調用一致性

githubjava

場景

客戶端調用A服務的接口,A服務接口中又調用了B服務。 若是A服務和B服務都執行成功,則成功,而且兩者事務都應提交; 若是A服務或B服務任意一個失敗,則失敗,且兩者事務都不執行或回滾。 由於網絡請求的不可靠性,若是A調用B失敗,可能:1. B沒有接收到網絡請求;2. B收到後執行失敗; 3. B執行成功,請求返回時異常。 4. B調用超時,B可能執行成功也可能失敗。所以當A調用B時,可能出現不一致。git

接口調用幾種方式

方式一:feign直接調用

A服務接口:github

try {
    業務代碼A
    feign調用B服務接口
    事務提交
} catch (Exception e) {
    事務回滾
}
複製代碼

B服務接口:bash

try {
    業務代碼B
    事務提交
    //此時接口狀態碼2XX
} catch (Exception e) {
    事務回滾
    //此時接口狀態碼非2XX
}
複製代碼

一致性分析:網絡

  1. feign調用B服務接口成功,狀態碼爲2XX。則B服務事務已經提交,A進行事務提交。 若A事務提交成功,則一致; 若A事務提交失敗但此時B中事務已經提交,則不一致。
  2. B沒有接收到網絡請求。B未被執行,feign調用拋出異常,A事務不進行提交,進入回滾,數據一致。
  3. B執行成功,請求返回時異常。B事務已經提交,feign調用B服務接口異常,A事務回滾,數據不一致。
  4. B調用超時。可能爲B沒有接收到網絡請求,也可能B執行成功,請求返回時異常,也可能B收到請求響應緩慢。一致性狀態不肯定,都有可能。

方式二:基於可靠消息

服務A業務代碼執行完後發送消息到消息隊列,如rabbitmq,並用ack等方式確保發送成功; B服務接收消費成功後,手動確認消息,kafka則用手動提交位移的方式。異步

A服務生產者:優化

try {
    業務代碼A
    ack = rabbitmqProducer.sendAndGetAck
    if !ack {
        //發送失敗能夠設置自動重試,不重試就拋出異常
        throws new RabbitmqSendException
    }
    事務提交
} catch (Exception e) {
    //事務回滾
}
複製代碼

B服務消息隊列消費端一:ui

//
try {
    業務代碼B
    channel.basicAck手動確認//kafka則手動提交位移
    事務提交
    //此時接口狀態碼2XX
} catch (Exception e) {
    事務回滾
    //此時接口狀態碼非2XX
}
複製代碼

B服務消息隊列消費端二:spa

//
try {
    業務代碼B
    事務提交
    //此時接口狀態碼2XX
} catch (Exception e) {
    事務回滾
    //此時接口狀態碼非2XX
}
channel.basicAck手動確認//kafka則手動提交位移
複製代碼

一致性分析:code

  1. A服務發送消息到消息隊列成功,卻提交事務失敗,出現數據不一致。
  2. B消費端一:手動確認後,消息從消息隊列刪除,B事務提交失敗,出現數據不一致。
  3. B消費端二:B事務提交成功,手動確認失敗,可能會重複收到該條消息,出現不一致。 此時可在消息中添加uuid,服務B收到消息後根據uuid進行一次去重再處理等方式來實現冪等性。

方式三:預執行+確認+回查,相似TCC

A服務須要插入一個表transaction_record記錄調用狀態,提供給B服務回調。

A服務業務接口:

String uuid = generateUUID()//生成一個uuid
try{
    feign.preCreate(uuid,...)//feign調用B預執行,好比B服務爲建立訂單服務,預建立一個訂單,但狀態爲待確認
    業務代碼A
    將uuid插入transaction_record表中
    事務提交
}catch (Exception e) {
    事務回滾
    feign.cancel(uuid)//feign調用B取消,好比B服務爲建立訂單服務,設置該訂單狀態爲取消
}

feign.confirm(uuid)//feign調用B確認,好比B服務爲建立訂單服務,設置該訂單狀態爲確認,此時訂單可用 
複製代碼

A服務服務回查接口,提供給B服務回查狀態:

get /v1/transaction/{uuid}

從transaction_record表中查詢,有則返回確認,沒有則返回取消
複製代碼

B服務須要提供預執行、確認、取消接口。若預執行後遲遲沒有執行確認或取消,則B向A回查,根據結果確認或取消。

一致性分析:

  1. A中preCreate執行異常。應拋出異常,再也不執行業務代碼和事件表插入uuid,去執行cancel。若B執行則狀態也爲未確認,不影響一致性; 若cancel也執行失敗,好比此時B掛掉,B重啓後應去調用服務A的回查接口,肯定狀態。狀態一致。
  2. 業務代碼A執行失敗,事務回滾,feign調用B取消。若取消成功,則狀態一致;若取消失敗,應拋出異常,confirm再也不執行。狀態一致。
  3. 業務代碼A執行成功,事務提交失敗,執行事務回滾。若回滾失敗,拋出異常,再也不執行cancel和confirm,B會執行超時回查肯定狀態;若回滾成功,則執行取消。狀態一致。
  4. A事務提交成功,確認失敗(好比A執行確認時,服務A或者B恰好掛掉)。則服務B超時回查,發現uuid存在,修改狀態爲確認。狀態一致。
  5. 預處理完成後,去執行業務代碼,若業務代碼執行緩慢,B服務認爲超時,則服務B超時回查,若A的事務還未提交,A的回查接口返回取消, 則B被取消,A卻提交了事務,此時出現事務狀態的不一致。此時能夠經過設置B稍微大的超時時間來調整,可讓服務A在預處理的feign調用時傳入期待的超時時間。

總結

以上是接口間調用的幾種方式,這裏只提供一種大概的思路,應用時能夠本身優化,同步發送也可修改成異步+重試等方式。 若一致性要求可採用方式三,uuid+插入表也可採用其餘的方式實現。爲了提升一致性,接口調用也要儘可能是冪等的,可經過業務邏輯的冪等性或 uuid實現。

相關文章
相關標籤/搜索