【微服務的數據一致性分發問題】究極解決方案

文章目錄

  • 一、微服務數據分發
    • 1、簡介
    • 2、典型場景
      • (1)跨服務業務流程協同
      • (2)數據副本同步(讀寫分離)
      • (3)實時狀態通知
      • (4)數據聚合與統計分析
      • (5)多端數據一致性
    • 3、什么是數據一致性分發
      • (1)核心挑戰
      • (2)典型問題
      • (3)設計原則
  • 二、解決方案
    • 1、雙寫(容易產生問題)
    • 2、事務性發件箱模式(類似本地消息表)
      • (1)簡介
      • (2)設計發件箱表
      • (3)本地事務記錄消息
      • (4)消息發送器:發送并確認
      • (5)處理異常與重試
      • (6)關鍵要點
    • 3、變更數據捕獲(Change Data Capture,CDC)
      • (1)簡介
      • (2)與發件箱模式的對比
  • 參考資料

一、微服務數據分發

1、簡介

在微服務架構中,“數據分發”指的是當一個服務(源服務)的數據發生變更時,通過特定機制將變更信息傳遞給其他依賴該數據的服務(目標服務),以保證各服務間數據協同的過程。它是解決微服務“數據隔離”與“業務協同”矛盾的核心手段。
在這里插入圖片描述

2、典型場景

微服務的數據分發場景本質上是“服務間數據依賴”的具體體現,以下是最常見的幾類場景:

(1)跨服務業務流程協同

場景描述:一個完整業務流程需要多個服務協作完成,源服務的數據變更會觸發其他服務的業務操作。
典型案例:
電商“下單流程”:訂單服務創建訂單后,需將“訂單創建”信息分發給庫存服務(扣減庫存)、支付服務(發起支付)、物流服務(預約配送);
金融“轉賬流程”:賬戶服務扣減轉出方金額后,需將“轉賬成功”信息分發給交易記錄服務(記錄流水)、通知服務(發送短信給用戶)。

核心特點:數據分發是業務流程的“串聯者”,若分發失敗,整個流程會中斷或出現數據不一致(如訂單創建了但庫存未扣減,導致超賣)。

(2)數據副本同步(讀寫分離)

場景描述:目標服務為了避免頻繁調用源服務查詢數據(減少網絡開銷、提高響應速度),會在本地存儲源服務數據的“副本”,需通過數據分發同步副本更新。
典型案例:
用戶中心服務存儲用戶基礎信息(姓名、手機號),商品評價服務為了快速展示“評價者昵稱”,會在本地存儲用戶信息副本,當用戶中心的昵稱更新時,需將變更分發給評價服務;
商品服務存儲商品詳情(名稱、價格),搜索服務為了加速商品搜索,會在本地索引庫存儲商品信息副本,當商品價格調整時,需將變更分發給搜索服務。

核心特點:數據副本是“只讀”的,分發目的是保證副本與源數據的最終一致,避免目標服務使用過期數據(如用戶改了昵稱但評價區仍顯示舊昵稱)。

(3)實時狀態通知

場景描述:源服務的關鍵狀態變更需要實時通知給關注該狀態的目標服務,以觸發即時響應。
典型案例:
訂單服務的訂單狀態從“待支付”變為“已支付”時,需實時通知庫存服務(鎖定庫存→確認扣減)、客服服務(生成待處理工單);
物聯網設備服務監測到設備“離線”時,需實時通知運維服務(觸發告警)、用戶服務(推送設備離線通知)。

核心特點:對實時性要求高(通常毫秒級或秒級),若分發延遲會導致業務響應滯后(如用戶已支付但庫存未確認扣減,可能被其他訂單占用)。

(4)數據聚合與統計分析

場景描述:數據平臺或分析服務需要匯總多個源服務的數據,進行統計、報表生成或業務分析,需通過數據分發獲取各服務的原始數據變更。
典型案例:
電商平臺的“銷售報表服務”需要聚合訂單服務(訂單金額)、支付服務(支付成功金額)、退款服務(退款金額)的數據,生成每日銷售額報表,需各服務將數據變更分發給報表服務;
風控服務需要實時獲取用戶服務(用戶行為)、訂單服務(下單頻率)、支付服務(支付渠道)的變更數據,構建風控模型。

核心特點:數據分發的頻率和時效性取決于分析需求(如實時風控需秒級,日報表可T+1),但需保證數據完整性(不能遺漏關鍵變更)。

(5)多端數據一致性

場景描述:同一業務數據需在多個終端(Web、APP、小程序)或多地域服務節點間保持一致,需通過數據分發同步變更。
典型案例:
社交平臺的“用戶動態”:用戶在APP發布動態后,需將動態數據分發給Web服務、小程序服務,確保多端展示一致;
跨國電商的“商品庫存”:美國倉庫的庫存變更需分發給歐洲、亞洲的區域服務,確保全球用戶看到的庫存狀態一致。

核心特點:數據分發需覆蓋所有依賴端,避免“信息孤島”(如用戶在APP刪了動態,Web端仍顯示)。

3、什么是數據一致性分發

在微服務架構中,數據一致性分發是指當一個服務的數據發生變更時,如何將這一變更可靠、準確地同步到依賴該數據的其他服務,確保各服務間數據最終一致的問題。由于微服務的分布式特性(獨立數據庫、網絡不可靠、服務自治),數據一致性分發面臨諸多挑戰,是微服務設計中的核心難題之一。

(1)核心挑戰

微服務架構下,每個服務通常維護獨立的數據庫(“數據私有”原則),服務間通過API或消息隊列通信。當某個服務的數據變更需要同步到其他服務時,會面臨以下核心挑戰:

1.分布式環境的不可靠性
網絡延遲、中斷、服務宕機等問題可能導致:

  • 數據變更通知丟失(如服務A更新數據后,通知服務B時網絡中斷,B未收到通知);
  • 通知重復(如服務A重試機制導致服務B收到多條相同通知);
  • 通知亂序(如服務A的兩次更新通知,服務B先收到后發的通知)。

2.本地事務與跨服務同步的原子性沖突
服務A的本地數據變更(如創建訂單)與“通知服務B”這兩個操作無法天然保證原子性:

  • 若先更新本地數據,再通知B:本地更新成功但通知失敗→B數據不一致;
  • 若先通知B,再更新本地數據:通知成功但本地更新失敗→B數據冗余。

3.數據語義的一致性
不同服務對同一業務概念可能有不同的數據模型(如服務A的“訂單狀態”與服務B的“訂單狀態”定義差異),導致同步的數據語義不一致。

4.性能與一致性的平衡
強一致性方案(如分布式事務)會犧牲性能和可用性(需服務間頻繁協調);而追求高性能的方案可能導致數據長期不一致。

(2)典型問題

1.雙寫不一致
服務A和服務B需同時更新關聯數據(如A創建訂單,B扣減庫存),若未通過可靠機制同步,可能出現:

  • A訂單創建成功,但B庫存扣減失敗→超賣;
  • B庫存扣減成功,但A訂單創建失敗→庫存無故減少。

2.數據孤島與同步延遲
服務C依賴服務D的用戶信息,若D的用戶信息更新后未及時同步到C,C會使用舊數據處理業務(如用戶手機號變更后,C仍發送短信到舊號碼)。

3.重復數據與數據污染
因網絡重試,服務B多次收到服務A的同一數據變更通知,若B未做冪等處理,可能重復執行操作(如重復扣減庫存)。

4.因果關系破壞
服務A先執行“訂單創建”,再執行“訂單取消”,但兩個通知因網絡原因倒序到達服務B,B先處理“取消”再處理“創建”,導致數據邏輯錯誤。

(3)設計原則

1.優先追求最終一致性
微服務中強一致性代價過高(可用性低、性能差),多數場景下“最終一致”即可滿足需求(如訂單狀態最終同步到物流系統)。

2.設計冪等接口
接收端必須支持冪等操作(即多次執行同一操作結果相同),通過唯一標識(如消息ID、業務單號)避免重復處理。

3.異步優先,同步兜底
優先用異步消息(如Kafka、RabbitMQ)分發數據,減少服務間耦合;同步調用僅用于必須實時響應的場景(如查詢當前庫存)。

4.明確數據所有權
一個數據實體(如訂單)應有唯一的“owner服務”(如訂單服務),其他服務僅存儲副本或視圖,避免多服務同時修改同一數據。

5.監控與可觀測性
對數據分發鏈路(消息發送、消費、補償)進行監控,記錄關鍵指標(如消息延遲、失敗率),及時發現不一致問題。

二、解決方案

1、雙寫(容易產生問題)

以下偽代碼:

@Transactional
public void updateDbAndSendMsg() {try {// 1、先修改數據庫boolean result = dao.update(model);if (result) {// 2、數據庫修改 mq.send(model);}}}

通過編碼的方式,確實可以一定程度上規避數據不一致的問題。
但是極端場景下,沒有重試機制、網絡延遲等原因,還是有可能出問題的。

2、事務性發件箱模式(類似本地消息表)

中小規模的企業應用,用基于DB的事務性發件箱來實現異步可靠消息,比較簡單。

(1)簡介

其核心思想是,將 “消息發送” 與 “本地業務事務” 綁定為一個原子操作:
1、先將消息暫存到本地數據庫的 “發件箱表” 中,這一步與本地業務操作在同一個事務內完成(要么都成功,要么都失敗);
2、再通過獨立的 “消息發送器”(或者定時任務) 從發件箱表中讀取消息,發送到消息隊列;
3、發送成功后,標記或刪除發件箱中的消息,確保消息僅被發送一次。
通過這種方式,保證 “業務操作成功” 與 “消息被記錄” 的強一致性,再通過可靠的發送器確保消息最終能到達消息隊列。
在這里插入圖片描述

(2)設計發件箱表

在本地數據庫中創建專門的發件箱表(如outbox_messages),存儲待發送的消息,典型字段包括:

字段名作用示例值
id唯一標識(主鍵)123e4567-e89b-12d3-a456-426614174000
aggregate_type業務聚合根類型(如訂單)“order”
aggregate_id業務聚合根ID(如訂單ID)“ORD-20240822-001”
event_type事件類型(如訂單創建)“order_created”
payload消息內容(JSON格式){"orderId":"ORD-xxx", "amount":100}
status消息狀態(未發送/已發送/失敗)“PENDING”(未發送)
created_at創建時間2024-08-22 10:00:00
sent_at發送成功時間NULL(未發送時)

(3)本地事務記錄消息

在業務邏輯的本地事務中,完成業務操作后,同步將消息插入發件箱表。例如:

// 偽代碼:訂單創建事務
@Transactional
public void createOrder(Order order) {// 1. 執行本地業務操作(如保存訂單到數據庫)orderRepository.save(order);// 2. 生成消息并插入發件箱表(與訂單保存同屬一個事務)OutboxMessage message = new OutboxMessage(UUID.randomUUID(), "order", order.getId(), "order_created", JSON.toJSONString(order), "PENDING", LocalDateTime.now());outboxRepository.save(message);
}

若訂單保存失敗(如數據庫異常),事務回滾,消息不會被插入發件箱;
若訂單保存成功,消息必然被插入發件箱,確保“業務成功→消息記錄成功”的原子性。

(4)消息發送器:發送并確認

啟動獨立的“消息發送器”(可通過定時任務、后臺線程或數據庫觸發器實現),定期從發件箱表讀取“未發送”(status = PENDING)的消息,發送到消息隊列(如Kafka、RabbitMQ)。

發送流程:
1.讀取消息:按created_at升序讀取未發送消息(避免消息亂序);
2.發送消息:調用消息隊列的發送API,將payload發送到指定主題/隊列;
3.確認發送:若發送成功,更新消息狀態為“已發送”(status = SENT)并記錄sent_at;若失敗,可標記為“失敗”(status = FAILED),等待重試。

示例偽代碼

// 消息發送器定時任務(每10秒執行一次)
@Scheduled(fixedRate = 10000)
public void sendOutboxMessages() {// 1. 讀取未發送的消息List<OutboxMessage> pendingMessages = outboxRepository.findByStatus("PENDING");for (OutboxMessage msg : pendingMessages) {try {// 2. 發送到消息隊列messageQueue.send("order-events", msg.getPayload());// 3. 標記為已發送msg.setStatus("SENT");msg.setSentAt(LocalDateTime.now());outboxRepository.save(msg);} catch (Exception e) {// 發送失敗,標記為失敗(后續可重試)msg.setStatus("FAILED");outboxRepository.save(msg);}}
}

(5)處理異常與重試

發送失敗重試:對status = FAILED的消息,可設置重試次數(如最多3次),超過次數后觸發告警(人工介入);
發送器崩潰:發送器重啟后,通過status = PENDINGFAILED的消息繼續處理,不丟失消息;
消息隊列崩潰:發送器會因發送失敗標記消息為FAILED,待隊列恢復后重試。

(6)關鍵要點

1.消息冪等性:
發送器可能因網絡波動重試發送,接收方需通過id(消息唯一標識)處理重復消息(如“先查后處理”或“樂觀鎖”)。

2.發件箱表清理:
已發送(SENT)的消息可定期清理(如保留7天),避免表數據過大影響查詢性能。

3.發送器可靠性:
發送器需保證高可用(如多實例部署),避免單點故障導致消息積壓。

4.與事件溯源結合:
若系統采用事件溯源(Event Sourcing)模式,發件箱表可直接復用事件日志(Event Log),無需額外存儲。

5.有一定的消息延時
如果使用定時任務,需要接受一定時間的消息延時

3、變更數據捕獲(Change Data Capture,CDC)

(1)簡介

變更數據捕獲(Change Data Capture,CDC) 是一種核心的數據同步與分發技術,其本質是實時捕獲數據庫中數據的增刪改(INSERT/DELETE/UPDATE)變更,并將這些變更以結構化格式傳遞給下游系統,無需侵入業務代碼即可實現數據的實時流轉。

這里建議使用Canal、FlinkCDC。
Flink從入門到實踐(三):數據實時采集 - Flink MySQL CDC
docker使用canal訂閱mysql的binlog,springboot使用canal訂閱mysql的binlog
在這里插入圖片描述

(2)與發件箱模式的對比

Transaction OutboxCDC
復雜性相對簡單復雜(高可用/監控)
Pulling延遲和開銷近實時,有一定性能開銷較實時,性能開銷小
應用侵入性
適用場合早期/中小規模中大規模,有獨立框架團隊治理維護

參考資料

楊波老師《分布式系統案例課》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/94481.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/94481.shtml
英文地址,請注明出處:http://en.pswp.cn/web/94481.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

挖幣與區塊鏈技術有怎樣的聯系?

挖幣&#xff08;通常指加密貨幣挖礦&#xff09;與區塊鏈技術有著緊密的聯系&#xff0c;挖礦是區塊鏈網絡維持運行和安全的重要機制之一&#xff0c;具體聯系如下&#xff1a;1. 挖礦是區塊鏈共識機制的核心環節區塊鏈通過“共識機制”確保全網節點對交易記錄達成一致&#x…

C數據結構:二叉樹(下)

C數據結構&#xff1a;二叉樹&#xff08;下&#xff09; 1.二叉樹遞歸結構遍歷 2.例題 3.二叉樹的性質 1.二叉樹遞歸結構遍歷 我們先創建一個如下圖所示的二叉樹。typedef int BTDataType; typedef struct BinaryTreeNode {BTDataType data;struct BinaryTreeNode* left;struc…

Linux系統的網絡管理(一)

一、網絡參數配置&#xff1a;搭建穩定網絡基礎網絡參數配置是 Linux 網絡管理的起點&#xff0c;根據操作方式可分為圖形化配置、命令行配置和配置文件配置&#xff0c;不同方式適用于不同場景&#xff08;臨時調試 / 永久生效&#xff09;。1. 圖形化配置&#xff1a;依賴 Ne…

Web程序設計

一、控件基礎 文本框、按鈕事件的使用 <% Page Language"C#" AutoEventWireup"true" CodeFile"User_Login.aspx.cs" Inherits"User_Login" %><!DOCTYPE html><html xmlns"http://www.w3.org/1999/xhtml"&g…

復合設計模式

復合設計模式復合設計模式是一種結構模式&#xff0c;可讓您統一處理單個對象和對象的組合。它允許您構建樹狀結構&#xff08;例如&#xff0c;文件系統、UI 層次結構、組織結構&#xff09;&#xff0c;客戶端可以使用同一界面處理單個元素和元素組。它在以下情況下特別有用&…

使用 Prometheus 監控服務器節點:Node Exporter 詳解與配置

前言 在上一篇文章中&#xff0c;我們介紹了如何在 CentOS 上部署 Prometheus 并使用 systemd 進行管理。本文將繼續深入&#xff0c;講解如何使用 Prometheus 監控服務器節點&#xff0c;重點介紹 Node Exporter 的作用、安裝和配置方法。 Node Exporter 是 Prometheus 生態…

C# 編寫一個XmlToDota的轉換工具

以下代碼可以將Labelme標注的旋轉框Xml格式文件轉換為Yolo標注格式的txt文件&#xff0c;以便用Yolo OBB訓練自己的數據集&#xff1a;using System; using System.Collections.Generic; using System.IO; using System.Xml; using System.Linq; using System.Globalization;na…

[Android] 人體細胞模擬器1.5

[Android] 人體細胞模擬器1.5 鏈接&#xff1a;https://pan.xunlei.com/s/VOYVUieTpjNVJq-bMys4EEDGA1?pwdm7m6# 省流:這個軟件的開發者有點逆天&#xff0c;一個模擬人體器官的軟件&#xff0c;細致到有血液報告&#xff0c;還縫合了生理學和病理學&#xff0c;甚至還能做切…

【Linux基礎知識系列】第一百一十篇 - 使用Nmap進行網絡安全掃描

在網絡安全管理中&#xff0c;了解網絡中的設備、開放的端口以及運行的服務是至關重要的。Nmap&#xff08;Network Mapper&#xff09;是一個功能強大的開源工具&#xff0c;用于網絡發現和安全審計。它可以掃描網絡中的設備&#xff0c;識別開放的端口和運行的服務&#xff0…

【Linux倉庫】進程的“奪舍”與“飛升”:exec 驅動的應用現代化部署流水線

&#x1f31f; 各位看官好&#xff0c;我是egoist2023&#xff01; &#x1f30d; Linux Linux is not Unix &#xff01; &#x1f680; 今天來學習exec系列的進程程序替換,從"fork"的"克隆"到"exec"的"重生"。 &#x1f44d; 如果覺…

Reachability Query

題目分析 該代碼實現了一個動態集合管理系統&#xff0c;支持三種操作&#xff1a;合并集合、切換元素狀態、查詢集合中是否- 存在活躍元素。核心數據結構為并查集&#xff0c;結合狀態標記數組和計數器。關鍵數據結構與函數 初始化 fa[N]&#xff1a;并查集父節點數組&#xf…

SSL移動接入方案和移動資源發布

一、SSL VPN概述SSL VPN是一種基于SSL/TLS協議的遠程安全接入技術&#xff0c;因其廣泛兼容Web瀏覽器&#xff0c;支持“無客戶端”部署&#xff0c;具備易于使用和維護的特點。它通過插件系統支持非Web類TCP/UDP應用&#xff0c;并且支持對用戶的訪問可以做出限制&#xff0c;…

C++STL---count() 統計容器中特定元素出現次數

在 C 標準庫中&#xff0c;count 是一個用于統計容器中特定元素出現次數的函數&#xff0c;定義在 <algorithm> 頭文件中。它可以快速計算某個值在容器&#xff08;如數組、vector、list 等&#xff09;中出現的次數&#xff0c;避免手動編寫循環計數的麻煩。 一、函數原…

Tesla自動駕駛域控制器(AutoPilot HW)的系統化梳理

目前網絡上對Tesla自動駕駛硬件&#xff08;AP1-AP4、HW1.0-HW4.0&#xff09;迭代的相關介紹比較混亂&#xff0c;本文這里進行系統化梳理并澄清&#xff0c;并對一些錯誤進行更正。1、AutoPilot HW迭代圖圖1 AutoPilot HWMCU迭代圖圖2 AutoPilot HW 散熱設計迭代圖&#xff0…

C 語言:第 20 天筆記:typedef(類型重命名規則、應用場景與實戰案例)

C語言&#xff1a;第20天筆記 內容提要 構造類型枚舉類型typedef綜合案例:斗地主預處理 構造類型&#xff1a;枚舉類型 使用建議 如果定義不相干的常量&#xff0c;使用宏定義&#xff08;符號常量&#xff09;&#xff1b;如果需要定義一組相關聯的常量&#xff08;如月份011、…

在 vue3 和 vue2 中,v-for 和 v-if 可以一起用嗎,區別是什么

在 Vue 2 和 Vue 3 中&#xff0c;v-for 和 v-if 可以一起使用&#xff0c;但兩者在處理順序和推薦用法上存在明顯區別&#xff0c;主要體現在優先級和最佳實踐上&#xff1a; 1. Vue 2 中的 v-for 與 v-if優先級&#xff1a;v-for 的優先級高于 v-if。 這意味著 Vue 會先循環渲…

Linux-進程相關函數

文章目錄Linux-進程相關函數父子進程關系父子進程地址空間getpid函數 獲取本進程號getppid函數 獲取當前進程的進程的父進程號getpgid函數 獲取進程組號示例fork函數 創建進程區分父子進程exit函數 進程退出wait函數 等待子進程退出waitpid函數Linux-進程相關函數 每個進程都由…

數據挖掘 6.1 其他降維方法(不是很重要)

6.1 Other dimensionality reduction methods 6.1 其他降維方法 其他降維方法前言問題答案流形3 降維大綱3.1 線性方法3.2 非線性方法3.2.1 流形學習方法&#xff08;Manifold Learning&#xff09;3.2.2 概率方法&#xff08;Probabilistic Approaches&#xff09;3.2.3 拓撲數…

Unity中的特殊文件夾

一.工程路徑獲取print(Application.dataPath);只用于游戲開發編輯器模式下&#xff0c;游戲發布后此路徑就不存在了二.Resources 資源文件夾//路徑獲取: //一般不獲取 //只能使用Resources相關API進行加載 //如果硬要獲取 可以用工程路徑拼接print(Application.dataPath "…

Seaborn數據可視化實戰:Seaborn高級使用與性能優化教程

Seaborn最佳實踐與技巧 學習目標 本課程將深入探討Seaborn庫的高級使用技巧&#xff0c;包括性能優化、常見問題解決方法等&#xff0c;旨在幫助學員掌握如何高效地使用Seaborn進行數據可視化&#xff0c;提升圖表的美觀度和信息傳達效率。 相關知識點 Seaborn最佳實踐與技巧 學…