MSMQ 還能用多久 ── 「連淘汰都稱不上」的遺留佇列遷移判斷

· · Windows, .NET, C#, MSMQ, 訊息佇列, 遺留技術, 遷移, 資訊系統

更新紀錄(僅初版,2026年07月29日 發布)
初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22175288)

以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。

Go Komura(2026)。〈MSMQ 還能用多久 ── 「連淘汰都稱不上」的遺留佇列遷移判斷〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/msmq-migration-decision-guide/

DOI(已登錄存檔)
10.5281/zenodo.22175288
DOI(上次登錄版本)
10.5281/zenodo.22175289

「聽說 MSMQ 已經廢除了。現在還在運作的系統,是不是也得馬上遷移?」──在既有系統的維護上,首先必須把這一點釐清。

MSMQ(Microsoft Message Queuing)作為作業系統功能是否存續,與從 .NET 使用它的 API 是否受支援,是兩回事。在本文的基準時點 2026 年 7 月,MSMQ 並未列入官方的淘汰清單,仍以 Windows 選用功能的形式保留著。另一方面,標準函式庫 System.Messaging 只支援 .NET Framework,並未移植到 .NET(Core 以後)。123

也就是說,問題不在於「明天 MSMQ 就會停掉」,而在於「把應用程式遷移到 .NET 時,佇列的實作會成為遷移的阻礙」

本文寫給負責維護與遷移 MSMQ 系統的開發者,以及決定方針的資訊系統部門。先確認事實關係與繼續使用的條件,再進入盤點、遷移目標的選定、資料表佇列的實作與切換程序。

從 VB6 或 .NET Framework 遷移的整體做法,請參考「VB6 遷移到 .NET 的實務做法」「從 .NET Framework 遷移到 .NET 的著手前檢核表」。本文只聚焦佇列的部分。

1. 先講結論:把作業系統的存續與應用程式的遷移分開看

要把應用程式遷移到 .NET,原則上佇列也要一起遷移。如果短期內仍以 .NET Framework 運作,那麼在確認作業系統的支援期限與維運條件之後,可以選擇繼續使用。不建議在新專案採用 MSMQ。4

從你現在要決定的事情讀起

現在要決定的事 判斷的出發點 說明
「已經廢除」的說法是真的嗎 把作業系統功能與受管理 API 的狀況分開看 第 1 章:事實關係
短期內能不能就這樣繼續用 除了作業系統的支援期限,還要確認能否備齊監控、復原與交接 第 2 章:繼續使用的條件
想隨著 .NET 遷移一起替換掉 盤點依賴、訊息格式、DTC 與離線耐受性 第 3 章:盤點第 4 章:遷移目標
能不能用 P/Invoke 或 CoreWCF 保留下來 把「呼叫得通」和「扛得住維護負擔」分開看 1.3 節:受管理 API 與 P/Invoke1.4 節:CoreWCF
要選 RabbitMQ 還是 Azure Service Bus 在這兩個選項之前,先評估同一個業務資料庫上的資料表佇列夠不夠用 第 4 章:依需求選型第 5 章:資料表佇列
想把取出用的 SQL 搬到自己的資料庫上 確認隔離等級、快照設定與鎖定提示 5.3 節:SQL 與資料庫設定
想維持佇列的處理順序 把取出順序與寫入業務資料的順序分開看 5.4 節:平行處理與順序
想把工作處理程序放進實際維運 確認交易邊界,以及回收、重試與清理 5.5 節:工作處理程序5.6 節:維運
運作中的系統該怎麼切換 決定訊息格式、接收端、重複對策與殘留訊息的處理 第 6 章:切換程序

如果要通讀全文,順序是:確認存續 → 繼續使用的條件 → 盤點 → 遷移目標 → 實作 → 切換。回頭檢視判斷時,請參考第 7 章的總結

1.1. 先確認在談哪一層

「MSMQ 還能不能用」的答案,會依層級而不同。狀態的基準時點與開頭相同,都是 2026 年 7 月。

層級 目前的狀態 實務上的意義
作業系統功能(Windows 的選用功能) 存續。隨附於現行的 Windows 用戶端/Server,也沒有發布淘汰宣告12 只要啟用,現在依然能運作。在作業系統的支援期限內可以繼續使用
Win32 原生 API(MQSendMessage 等) 有文件,可以使用5 從 .NET 以 P/Invoke 呼叫的路仍然存在。但格式器與交易整合要自己寫、自己維護
.NET Framework 的 System.Messaging 可以使用。但涵蓋範圍只到 .NET Framework 1.1~4.8.13 既有系統就跑在這裡。再往後沒有下文
.NET(Core 以後)的官方受管理 API 不存在。Windows 相容性套件中也沒有收錄6 把應用程式升級到 .NET 的那一刻,佇列部分就必須重做
WCF 的 MSMQ 繫結 → CoreWCF.MSMQ 存在社群主導的移植版本。CoreWCF 本身有支援政策,但 MSMQ 實作依賴 System.Messaging 的社群移植版本7 可以用來讓透過佇列被呼叫的 WCF 服務(接收端)延續使用,但既不是傳送端的替代,也不是通用的佇列 API
新專案採用 不建議 因為沒有官方的未來路徑

1.2. 沒有被淘汰的是作業系統功能

Windows 用戶端的「Deprecated features」中列有 NTLM、VBScript、WordPad 等項目,卻沒有 MSMQ 這一項。Windows Server 的「Features Removed or No Longer Developed」也一樣,包含 Windows Server 2025 的分頁在內都沒有。12

淘汰(deprecated)是官方的一個階段,表示已結束積極開發、未來有可能被移除,與移除(removed)不同。就 MSMQ 而言,連這個淘汰宣告都還沒有發布,這就是本文所確認到的狀況。

1.3. 阻礙遷移到 .NET 的是受管理 API

System.Messaging.MessageQueue 的參考文件所涵蓋的是 .NET Framework 1.1~4.8.1,沒有 .NET(Core 以後)的版本。Windows 相容性套件(Microsoft.Windows.Compatibility)提供登錄檔、WMI、Windows 服務、EventLog 等約兩萬個 API,但不包含 System.Messaging36

要用 P/Invoke 保留,就得連包裝層的維護一起扛下來

並不是連原生 API 都沒有了。MQSendMessageMQReceiveMessage 等 Win32 API,從 .NET 以 P/Invoke 呼叫本身是可行的。但原本由 System.Messaging 承擔的格式器與交易整合,都要自己寫成包裝層並持續維護。這不是遷移的首選,而是無論如何都要保留 MSMQ 時的延續使用手段。5

1.4. 把 CoreWCF 當成「WCF 接收端」的路徑來判斷

.NET Framework 的 WCF 有使用 MSMQ 的繫結。要在 .NET 上代管它,路徑就是 CoreWCF 的 MSMQ 傳輸(CoreWCF.MSMQ)。7

不過,它的用途是透過佇列被呼叫的 WCF 服務的接收端。它既不是可以取代 System.Messaging 的通用佇列 API,也不是傳送端用戶端的替代品。

CoreWCF 本身有 Microsoft 的支援政策。另一方面,MSMQ 傳輸的實作依賴 .NET Framework 版 System.Messaging 的社群移植版本,不能把這部分依賴視為享有同樣的保證。採用前要確認所用版本是否在支援範圍內、依賴部分由誰維護。7

由此可見,「MSMQ 已經廢除」和「無法原樣遷移到 .NET」不是同一件事。選擇 P/Invoke 或 CoreWCF,等於把佇列的替換往後延,代價是扛下這條路徑的維護負擔。

圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 28 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle

2. 是否繼續使用:由 .NET 遷移計畫與維運體制來決定

2.1. 為什麼光憑「還在動」無法判斷

.NET Framework 4.8 作為 Windows 的元件,依所安裝之作業系統的生命週期受到支援。因此,在支援期限內持續運作的計畫是可以擬定的。4

要確認的不只是能不能運作。如果因為依賴 MSMQ 而讓整個應用程式留在 .NET Framework 上,下列維護與遷移上的負擔也會一併留下。

負擔 對遷移判斷的影響
維護人員的確保與交接 能說明 System.Messaging 與 DTC 的技術人員越來越少,重新調查組態的成本也隨之增加
執行環境與函式庫的限制 有些新的 C# 語法只要更新編譯器就能用,但執行環境的效能改善與新的標準函式庫用不上。把 .NET Framework 排除在支援範圍外的套件也在增加
對 DTC 的依賴 如果保留「把佇列取出與資料庫更新一起確定」的部分,就算周邊都 .NET 化了,最難的一關還是會留到最後
舊的序列化格式 BinaryFormatter 的實作已在 .NET 9 從執行環境中移除,必須連格式一起遷移8

維護人員流失的問題,在「沒有原始碼也沒有文件的系統該怎麼維護」中也討論過。MSMQ 的諮詢之所以多半不是從故障處理、而是從遷移估算開始,正是因為真正的問題不在能不能運作,而在這類依賴與交接。

「作業系統還在支援期內就能運作」和「越等越容易遷移」是兩回事。即使短期內不遷移,也要先把難關盤點出來。

2.2. 短期內不遷移,就要滿足 4 個最低條件

沒有把應用程式升級到 .NET 的計畫,從成本效益看短期內也不打算改動,也就是所謂的「擱置不動」,在有條件的前提下是合理的。但前提是下列 4 項全部滿足。

確認 最低條件 具體要做的事 不滿足會發生什麼
在裝機與還原程序中明確記載 MSMQ MSMQ 是 Windows 的選用功能。把「開啟或關閉 Windows 功能」或以 DISM/PowerShell 啟用的步驟寫進環境建置手冊 更換電腦與伺服器時忘了啟用,遷移當天原因不明地跑不起來
監控佇列長度 對堆積筆數設定門檻監控與通知。無法送達佇列與日誌佇列也要納入對象 接收端停了也不會出錯,訊息只會一直堆積,業務在無人察覺中停擺
把組態文件化 記錄佇列路徑、權限、是否為交易佇列、格式器、日誌佇列設定(3.2 節的盤點表可以直接拿來用) 將來的遷移估算又要從調查重來一次,準確度與工時都會變差
每年重新檢視一次判斷 每年確認「是否被列入淘汰清單」「作業系統更新後行為有沒有改變」 等淘汰公告出來才手忙腳亂地行動

其中年度檢視,是為了不漏掉淘汰公告與作業系統更新影響的程序。為因應負責人異動,不只留下組態,還要留下復原程序。這些條件沒有補齊就維持現狀,就等於承擔將來既察覺不到系統已經停擺、也查不出原因的風險。

3. 盤點:記錄你把什麼交給了 MSMQ

3.1. 掌握功能與術語

在 MSMQ 中,傳送端把訊息寫進佇列,同一台機器或另一台機器上的應用程式再在方便的時機取出。需要讓遷移目標接手的性質,主要有下列 3 項。

傳送目標停了也能先累積:store-and-forward

即使傳送目標停止運作,訊息也會先累積在本機,等恢復之後再送達。這在不穩定的據點間線路上很有用,但它並不無條件保證跨重新啟動的持久性。

把佇列與資料庫更新一起確定:交易

佇列操作可以納入交易;搭配 MS DTC(分散式交易協調器)之後,可以把從佇列取出與資料庫更新放進一個分散式交易中一起確定。

不必導入額外中介軟體就能用:隨作業系統附帶

因為不需要額外導入中介軟體,2000 年代的訂單收發連動、報表的非同步處理、製造現場的工序間連動等場景都廣泛使用了它。這 3 項的組合,正是它至今仍然留存的原因。

用術語區分累積、持久化與交易

判斷表中用到的術語,也在這裡統一。9

術語 含義,以及需要確認它的理由
私人佇列/公用佇列 私人佇列只登錄在本機電腦上,不會發布到 Active Directory。以 .\private$\佇列名稱 這樣的形式指定。公用佇列會登錄到目錄服務中,可以在網域內搜尋到。中小規模的業務系統多半使用私人佇列
express 訊息 預設的傳送模式。傳遞中與傳遞後都放在記憶體裡,因此速度快,但所在的電腦或 MSMQ 服務一停止就會遺失
recoverable(可復原)訊息 在傳送端、中繼節點與目的佇列上都保存到磁碟,可以跨重新啟動保留下來。需要在傳送端明確指定
交易佇列 只處理交易訊息。這個屬性在建立佇列時就已決定,之後無法變更。交易訊息會持久化到磁碟
DTC 協調跨越 MSMQ 與資料庫等多個資源之交易的 Windows 服務。佇列與資料庫的一致性該如何取代,是遷移時的爭論焦點
日誌佇列/無法送達佇列 MSMQ 產生的系統佇列。前者保存已傳送、已取出訊息的副本,後者保存無法送達的訊息。要監控放著不管所造成的容量增長

「接收端停了也能累積」和「MSMQ 或機器重新啟動後訊息仍在」是兩回事。要分開調查:只用到了前者,還是也需要由 recoverable 訊息或交易訊息提供的後者。

3.2. 從程式碼、組態與維運中找出依賴

不要只看函式庫的參考就結束盤點

先找 System.Messaging 的參考與 MessageQueue 的建立位置。但光憑那裡沒有參考,還不能斷定「沒有使用 MSMQ」。要依呼叫路徑分別確認。

呼叫路徑 在程式碼與組態中要找的東西
.NET Framework 的函式庫 System.Messaging 的參考、MessageQueue 的建立位置
WCF 的組態 netMsmqBinding / msmqIntegrationBinding
原生 API 與 COM MQSendMessageMQ* 原生函式、透過 COM 的呼叫

逐一為每個佇列留下遷移判斷的依據

用下面的表,逐一記錄每個佇列的確認結果。目的不是確認完就結束,而是把它留存為遷移方針與簽核資料的依據。

觀察點 看哪裡 記錄什麼 對判斷的作用
(a)佇列路徑 傳給 MessageQueue 的路徑字串、組態檔中的連線目標、FormatName: 指定 本機還是遠端、私人還是公用、對方的機器名稱 若是遠端或跨據點,就據此判斷是否需要 store-and-forward(4.2 節的「離線耐受性」那一列)。若在本機就能完結,則落在 4.2 節的第 1 列
(b)交易與 recoverable 佇列建立位置(是不是交易佇列)、傳送時是否指定了 recoverable、是否使用了 DTC 是不是交易佇列、有沒有指定 recoverable、是否參與 DTC 有用 DTC,遷移目標幾乎就確定是資料表佇列。兩者都沒有時,要寫明現行系統是以「重新啟動就會遺失訊息」為前提在運作的
(c)格式器 XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter 的指定位置 使用的格式器,以及訊息本體的型別 屬於 BinaryFormatter 系列就必須換成 JSON(6.1 節)。這會成為遷移工時的主要來源8
(d)日誌佇列與無法送達佇列 佇列的屬性(是否啟用日誌佇列)、系統佇列裡的內容、維運手冊 是否使用日誌佇列、是否真的在看無法送達佇列 「失敗訊息怎麼處理」會成為遷移目標的設計需求(用資料表佇列的話就是轉存資料表)
(e)誰在建立與刪除 安裝程式、部署指令碼、應用程式啟動時的 Create 呼叫 建立的主體,以及由誰設定權限 遷移時「新佇列由誰準備」就是它的直接對應。選擇擱置不動時,要把它轉寫進裝機程序(第 2 章)

4. 遷移目標:不看後繼產品的名稱,而看需要的性質

4.1. 用 4 個問題整理需求

根據盤點結果,確認下列 4 點。

  1. 接收端做的是不是更新自家業務資料庫的處理。
  2. 交易佇列與資料庫更新,是不是用 DTC 一起確定的。
  3. 傳送端與接收端是否在不同機器、不同據點。是否真的用到了 store-and-forward。
  4. 訊息量有多大。若是多數業務系統那種一天數千到數萬筆的規模,光看候選方案的處理能力很難成為決定因素。

4.2. 依性質給出的首選

如果是更新同一系統業務資料庫的非同步作業,先考慮資料表佇列。只有在出現向多個系統傳遞、路由等資料表佇列滿足不了的需求時,才選擇訊息代理。

用到的性質 首選 理由與注意事項
同一系統內的非同步處理(接收端更新資料庫) 資料庫的資料表佇列 可以把「取出訊息」與「更新業務資料」放進同一個本機交易中確定,不再需要 DTC。既有的備份、監控與維運可以直接沿用。另外,傳送端也可以把「更新業務資料與新增訊息資料列」放進同一個交易,這就是所謂的 Outbox 模式
用 DTC 讓佇列與資料庫更新維持不可分割 資料庫的資料表佇列 把分散式交易換成本機交易才是這次遷移的本質。雲端佇列無法參與 DTC,只要還有這項需求,走訊息代理就是繞遠路
多系統、多語言之間鬆散耦合的連動 RabbitMQ(地端)/Azure Service Bus(可放在雲端) 傳遞的扇出與路由是訊息代理的強項。選 RabbitMQ 要把自行維運的負擔(備援、更新)估進去
據點間的離線耐受性(store-and-forward) Azure Service Bus 等+重送設計,或據點端資料表佇列+同步 能透明取代 MSMQ 那種「先存在傳送端本機、之後再送達」的機制並不多。要改成由應用程式明確承擔在傳送端暫存這項職責的設計
同一處理程序內的生產者/消費者 .NET 的 Channels 等記憶體內佇列 這類情況原本就不需要跨處理程序的佇列。在處理程序內完結即可,需要持久性再轉向資料表佇列
只當成 Windows 服務之間的處理程序間通訊來用 具名管道等 IPC 但能這樣取代的只有雙方始終同時運作的情況。接收端停止期間,MSMQ(即使是 express 訊息)仍會接受傳送並在之後送達,而管道會立即失敗。如果依賴停止期間的累積,就要在應用程式端實作重送與緩衝,或者保留佇列。選法請見「Windows 的 IPC 判斷表

為什麼先考慮資料表佇列

這裡說「先考慮資料表佇列」,並不是因為想在任何用途上都推薦資料庫。而是因為在 MSMQ 世代的業務系統中常見的更新同一個資料庫的非同步處理裡,用資料表佇列既能單純地保持一致性,又能活用既有的備份、監控與維運。

適合訊息代理的需求,以及要額外設計的部分

RabbitMQ 與 Azure Service Bus 適合多系統、多語言的鬆散耦合傳遞、扇出與路由。需要高吞吐量時也可以納入考慮。不過,由於佇列與業務資料庫成了不同的資源,不能把 MSMQ+DTC 的不可分割性原樣搬過去,而要在應用程式端設計冪等性與重送。新中介軟體的監控、備援、修補與人員教育也要算進估算裡。

就算遷移,也不代表停止期間的累積可以直接拿掉

請確認離線耐受性與接收端停止期間的累積,是不是可以拿掉的需求。選了雲端佇列,也不會自動附帶傳送端本機的累積;換成具名管道,停止期間的待接收也不會以同樣的形式保留下來。有需要的話,就在應用程式端加上重送與緩衝,或者把佇列保留下來。

5. 資料表佇列:把佇列操作與業務更新放在同一個資料庫中確定

本章先確認把兩者放進同一個資料庫的意義。在此基礎上,依序來看取出 SQL 的前提順序的保證範圍工作處理程序的交易實際維運中要補上的處理

5.1. 為什麼可以不再需要 DTC

在 MSMQ 中,佇列與資料庫是不同的資源。要把「已從佇列取出」與「已更新業務資料」一起確定,就需要由 DTC 支撐的分散式交易。

把佇列改成與業務資料同一個資料庫中的資料表之後,兩者都變成對該資料庫的更新。取出、業務更新與完成記錄可以用一個本機交易確定。把分散式交易換成本機交易,正是這次遷移的核心。

不只接收端,傳送端也能同時確定

在傳送端,也可以把業務資料的更新與訊息資料列的 INSERT 放進同一個交易。這就是 Outbox 模式,可以防止「只更新了業務資料,訊息卻沒有送出」這種不一致。

下面是 SQL Server 上的最小形式。C# 的部分是用來表示交易邊界的虛擬碼,不是可以直接部署的完整實作。

5.2. 儲存作業的資料表

CREATE TABLE dbo.JobQueue (
    Id          BIGINT IDENTITY(1,1) PRIMARY KEY,
    Payload     NVARCHAR(MAX) NOT NULL,                    -- 訊息內容。以 JSON 保存
    Status      TINYINT       NOT NULL DEFAULT 0,          -- 0:未處理 1:處理中 2:已完成
    EnqueuedAt  DATETIME2     NOT NULL DEFAULT SYSUTCDATETIME(),
    StartedAt   DATETIME2     NULL,                        -- 用來回收停在「處理中」的資料列
    RetryCount  INT           NOT NULL DEFAULT 0
);
CREATE INDEX IX_JobQueue_Status ON dbo.JobQueue (Status, Id);

Payload 中放 JSON 格式的訊息內容,用 Status 區分未處理、處理中與已完成。StartedAtRetryCount 用於回收與重試的維運。

5.3. 取出一筆,以及資料庫設定的確認

用一段 SQL 同時完成取出資料列的更新與訊息內容的取得

取出時,一邊把一筆更新為「處理中」,一邊用 OUTPUT 接收訊息內容。有了 READPAST,就能跳過被其他工作處理程序鎖住資料列的候選而不必等待,用多個處理程序平行處理。10

WITH next_job AS (
    SELECT TOP (1) *
    -- READCOMMITTEDLOCK 在 READ_COMMITTED_SNAPSHOT 為 ON 的資料庫上是必要的(後述)。
    -- 在 OFF 的資料庫上它與預設行為相同,所以一直帶著就能同時涵蓋兩種情況
    FROM   dbo.JobQueue WITH (READPAST, UPDLOCK, READCOMMITTEDLOCK)
    WHERE  Status = 0
    ORDER  BY Id          -- 依投入順序取出。這是它成為「佇列」的條件
)
UPDATE next_job
SET    Status = 1, StartedAt = SYSUTCDATETIME()
OUTPUT inserted.Id, inserted.Payload;

先確認快照設定與隔離等級

讀這段 SQL 時,要確認 READ_COMMITTED_SNAPSHOT 與工作階段的隔離等級。

官方文件指出,當資料庫的 READ_COMMITTED_SNAPSHOTON,且工作階段為 READ COMMITTED,或同時使用了 READCOMMITTED 提示時,就不能直接指定 READPAST。處理方式是:如果有 READCOMMITTED 提示就移除它,並加入 READCOMMITTEDLOCK10

預設的隔離等級就是 READ COMMITTED,因此不做任何處理就把它搬到啟用了快照的資料庫上,結果不是單純被封鎖,而是SQL 陳述式直接出錯。Azure SQL Database 預設為 ON。地端的資料庫也可能為了緩解讀取封鎖而啟用了它,所以要先查清楚。

SELECT is_read_committed_snapshot_on FROM sys.databases WHERE name = DB_NAME();

上面的取出 SQL 加了 READCOMMITTEDLOCK,不論快照設定為何都使用以鎖定為基礎的讀取。在 OFF 的資料庫上,它與預設行為相同。10

不一併寫上 ROWLOCK 的理由,以及 READPAST 的極限

另一方面,與常見的 WITH (READPAST, UPDLOCK, ROWLOCK) 不同,這裡沒有一併寫上 ROWLOCK。因為ROWLOCKREADCOMMITTEDLOCK 屬於同一組粒度提示,同一張資料表上不能同時指定兩者10

READPAST 能跳過的只有資料列鎖定,跳不過分頁鎖定。像本例這樣用 TOP (1) 的索引搜尋取一列的範圍內,鎖定擴大實際上不成問題;但如果想明確寫上 ROWLOCK,就要在確認 READ_COMMITTED_SNAPSHOTOFF 之後,改成把 READCOMMITTEDLOCK 拿掉。

5.4. 取出順序與寫入業務資料的順序是兩回事

要為取出的候選指定排序

省略 ORDER BY 寫成 UPDATE TOP (1),會選中哪一列是不確定的。UPDATETOP 不會為目標資料列排序,光是存在 (Status, Id) 索引也不構成順序保證。為了避免舊作業一直被往後排,範例中指定了 ORDER BY Id11

平行處理時先取出的未必先生效

不過,平行處理的完成順序是對不齊的。工作處理程序 A 在處理作業 1 期間,工作處理程序 B 會用 READPAST 跳過那一列,先把作業 2 提交。

能對齊的 對不齊的
哪一列先被取出ORDER BY Id 哪一列先寫入業務資料

如果像對同一筆訂單編號的更新那樣,寫入順序本身有意義,就要在下面兩種做法中選一種。

保證順序的方法 得到的性質與付出的代價
只留一個消費者 捨棄平行度換取順序。只要還能滿足所需的吞吐量,這是最單純的做法
依索引鍵拆分 依訂單編號等把工作處理程序固定下來,或加上「同一個鍵正在處理中就不取出」的條件。保證的是同一個鍵內部的順序,跨鍵的順序不保證

兩種都不選,就要在設計中寫明平行處理不保證整體的 FIFO。在 MSMQ 上平行接收也會出現同樣的問題。重要的是,不要帶著「既然是佇列就一定照順序」的理解去做遷移。

5.5. 工作處理程序的交易邊界

接收端要對齊的,是取出、業務資料更新與完成記錄這三者的交易。下面的虛擬碼把這三項處理交給同一個連線與交易,最後一起確定。

while (!stoppingToken.IsCancellationRequested)
{
    using var tx = connection.BeginTransaction();

    var job = DequeueOne(connection, tx);          // 上面的 UPDATE ... OUTPUT
    if (job is null)
    {
        tx.Commit();
        await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);  // 空的時候只等待一個輪詢間隔
        continue;
    }

    ApplyBusinessData(connection, tx, job);        // ← 更新業務資料(真正想做的工作)
    MarkDone(connection, tx, job.Id);              // Status = 2

    tx.Commit();   // 「取出」與「業務更新」在這一行同時確定
}

要點在於:DequeueOneApplyBusinessDataMarkDone 使用同一個連線與交易,最後的 Commit 把取出與業務更新一起確定。換成訊息代理時,同一個資料庫的這項性質就不存在了,必須另行設計一致性方案。

5.6. 實際維運中要補上的回收、重試與清理

在最小範例之外,還要設計下列維運處理。

維運項目 處理內容
回收停在「處理中」的資料列 Status = 1StartedAt 早於一定時間的資料列改回 Status = 0 的復原處理
重試上限與轉存 RetryCount 超過上限的資料列移到別的資料表,或改成 Status = 9 之類。相當於 MSMQ 無法送達佇列的存放處
清理已完成的資料列 定期刪除或封存 Status = 2 的資料列,防止資料表與索引膨脹

在中小規模的業務系統中,用資料表加輪詢、或加上通知的形式,往往就夠用了。在增加一套佇列產品之前,先確認這種實作能否滿足所需的性質與維運要求。

6. 切換程序:先把格式、行為與接收端對齊

切換時,先把訊息格式以及輸入與結果的測試對齊。之後再選擇是用橋接連通新舊,還是把舊佇列清空後切換

6.1. 決定新舊能夠並存的訊息格式

遷移後預設使用 JSON。不要帶過去的是 BinaryMessageFormatter 這類BinaryFormatter 系列的序列化。BinaryFormatter 的實作已在 .NET 9 從執行環境中移除,從安全性角度也不建議使用。8

另一方面,並不是所有二進位格式都不行。像 Protocol Buffers、MessagePack 這種規格由獨立一方維護的格式,直接把它帶進新系統本身沒有問題。先在 3.2 節的盤點中確認格式器與訊息內容的型別。

6.2. 改寫之前,先用測試把輸入與結果固定下來

佇列處理是容易出現時序相關缺陷的部分。先準備好「給這則輸入訊息,就會得到這個結果」的特性化測試,讓替換之後也能機械地確認結果一致。也請參考「用特性化測試固定行為之後再重構」。

6.3. 先從接收端做起,橋接要以會遺失、會重複為前提來設計

先讓接收端支援新佇列

先準備好新佇列,讓接收端支援它,再切換傳送端。

中間加一個把訊息從舊 MSMQ 搬到新佇列的小型橋接程式,就不必一次切換所有傳送端。橋接程式本身留在 .NET Framework 上也沒關係。

依有無交易區分交接的程序

不過,如果在從 MSMQ 取出之後、寫入新佇列之前停止運作,就會發生遺失或重複傳遞。要依來源佇列的種類,按下面的方式設計。

遷移來源 最低限度必要的交接方式
交易佇列 把 MSMQ 端改為交易接收,寫入失敗時可以退回。新佇列這端依訊息 ID 擋掉重複,做冪等處理
非交易佇列 Peek 讀取 → 冪等地寫入新佇列 → 確認寫入成功後再從舊佇列移除。移除之前停止運作所造成的重複,由新佇列這端吸收

佇列的交易屬性在建立時就已決定,之後無法變更。請注意,不能把交易接收的程序原樣用在非交易佇列上。

6.4. 橋接不划算,就清空再切換

如果為橋接所做的遺失與重複對策與系統規模不相稱,就不要勉強讓新舊並存,而是轉向以計畫停機把舊佇列清空之後再切換的做法。

留著訊息就切換,會造成重複處理與遺失。把佇列清空再切換,結果往往是最安全也最快的做法──這是本文基於實務給出的判斷。

7. 總結

在本文的基準時點 2026 年 7 月,MSMQ 尚未被官方列為淘汰。作為作業系統功能留存下來,和容易遷移到 .NET,是兩回事。System.Messaging 封閉在 .NET Framework 之內,要把應用程式升級到 .NET,原則上佇列也要一併重新檢視。123

如果短期內繼續使用,就要確認作業系統的支援期限,並備妥建置與復原程序、佇列監控、組態記錄以及每年一次的判斷檢視。擱置不動最大的風險不只在技術,還在於將來沒有人能碰它。

如果要遷移,先盤點依賴與訊息格式。對於更新同一個業務資料庫的非同步處理,資料表佇列是首選。它可以把原本由 MSMQ+DTC 承擔的一致性,換成同一個資料庫上的本機交易。出現向多個系統傳遞、路由等資料表佇列滿足不了的需求時,再考慮 RabbitMQ 或 Azure Service Bus。

在此基礎上,再確認 SQL 的隔離等級與提示、平行處理時的寫入順序,以及橋接的遺失與重複對策。重要的是,不要因為「聽說被廢除了」就著急,而要先掌握自己的系統究竟依賴什麼,再決定維持還是遷移

相關文章

相關諮詢領域

合同會社小村軟體承接包含 MSMQ 在內的遺留架構盤點與遷移計畫、從 .NET Framework 遷移到 .NET,以及包含佇列處理在內的業務系統改造。

參考連結

  1. Microsoft Learn, Deprecated features in the Windows client。Windows 用戶端中已停止積極開發(即已淘汰)功能的官方清單。關於截至 2026 年 7 月的清單中列有 NTLM、VBScript、WordPad 等項目,卻沒有列出 MSMQ(Microsoft Message Queuing);以及淘汰(deprecated)是指「並未積極開發,未來更新中可能會被移除」的階段,與移除(removed)有所區別這兩點。  2 3 4

  2. Microsoft Learn, Features Removed or No Longer Developed in Windows Server。Windows Server 中已移除功能與停止開發(已淘汰)功能的官方清單。關於包含 Windows Server 2025 分頁在內、截至 2026 年 7 月的清單中並未列出 MSMQ;以及已淘汰的元件仍會持續隨附於 Windows Server,依產品生命週期為正式環境提供支援,並持續獲得安全性更新與品質更新這兩點。  2 3 4

  3. Microsoft Learn, MessageQueue Class (System.Messaging)。關於 System.Messaging.MessageQueue 類別的參考文件所涵蓋的版本為 .NET Framework 1.1 到 4.8.1,並不存在 .NET(Core 以後)版本這一點。  2 3 4

  4. Microsoft Learn, .NET Framework official support policy。關於 .NET Framework 4.8 被定義為 Windows 作業系統的元件,依所安裝之母產品(作業系統)的生命週期政策提供支援這一點。  2

  5. Microsoft Learn(封存版), Message Queuing Functions 以及 MQSendMessageMQReceiveMessage。關於 MSMQ 的 Win32 原生 API(MQCreateQueue、MQSendMessage、MQReceiveMessage 等)已針對 C/C++ 應用程式提供文件,可以不經由受管理 API 就完成佇列的建立、傳送與接收這一點。  2

  6. Microsoft Learn, Use the Windows Compatibility Pack to port code to .NET。關於 Windows 相容性套件(Microsoft.Windows.Compatibility 套件)為了在遷移到 .NET 時補足對 .NET Framework 專用 API 的依賴,提供了約兩萬個 API;以及其技術領域清單(CodeDom、組態、Directory Services、Drawing、ODBC、ACL、WCF、登錄檔、WMI、效能計數器、Windows 服務、EventLog 等)並未包含訊息傳遞(System.Messaging)這兩點。  2

  7. CoreWCF project, CoreWCF.MSMQ (NuGet) 以及 CoreWCF 1.4.0 Preview release、Microsoft, CoreWCF Support Policy。關於 CoreWCF 是把 WCF 伺服器端移植到 .NET 的社群主導專案,Microsoft 為其提供官方支援政策;作為佇列類傳輸的一環,已發布支援 MSMQ 的套件(CoreWCF.MSMQ);以及該 MSMQ 實作依賴 .NET Framework 版 System.Messaging 函式庫的社群移植版本這幾點。  2 3

  8. Microsoft Learn, BinaryFormatter migration guide。關於 BinaryFormatter 基於安全性理由而逐步淘汰,自 .NET 9 起已從執行環境中移除實作、預設無法使用;以及官方建議改用 JSON(System.Text.Json)等安全的序列化格式作為遷移目標這兩點。  2 3

  9. Microsoft Learn(封存版), Express and Recoverable MessagingSystem-Generated QueuesMessage Queuing (MSMQ)。關於 express 訊息在傳遞中與傳遞後都保留在 RAM 上,一旦訊息所在的電腦停止或 MSMQ 服務停止就會遺失;recoverable 訊息會在傳送端與中繼的各台電腦上寫入磁碟,在目的佇列中也保留在磁碟上;公用佇列會登錄到目錄服務,而私人佇列只登錄在本機電腦上、不會公開到目錄服務;以及保存已從佇列取出的訊息與已傳送訊息副本的日誌佇列,與保存無法送達之訊息的無法送達(dead-letter)佇列,都是 MSMQ 產生的系統佇列這幾點。 

  10. Microsoft Learn, 資料表提示 (Transact-SQL)。關於 READPAST 是一種跳過而不讀取其他交易所鎖定之資料列的提示,可跳過的僅限資料列層級的鎖定,無法跳過分頁層級的鎖定,且只能在 READ COMMITTEDREPEATABLE READ 隔離等級下指定這一點。特別是以下這段敘述:「當 READ_COMMITTED_SNAPSHOT 資料庫選項設為 ON,且 (a) 工作階段的交易隔離等級為 READ COMMITTED,或 (b) 查詢中也指定了 READCOMMITTED 資料表提示,兩者之一成立時,就無法指定 READPAST 資料表提示。若要在這些情況下指定 READPAST 提示,須移除查詢中的 READCOMMITTED 資料表提示(如果有的話),並在查詢中加入 READCOMMITTEDLOCK 資料表提示」。另請參閱同一頁面中關於 READCOMMITTEDLOCK 是不論 READ_COMMITTED_SNAPSHOT 設定為何,都以鎖定方式執行 READ COMMITTED 的提示,以及對同一張資料表無法指定兩個以上粒度提示(PAGLOCK / NOLOCK / READCOMMITTEDLOCK / ROWLOCK / TABLOCK / TABLOCKX)的說明。資料庫端的設定可以透過 sys.databasesis_read_committed_snapshot_on 確認。  2 3 4

  11. Microsoft Learn, TOP (Transact-SQL)。關於在 INSERT、UPDATE、MERGE、DELETE 中使用 TOP 時,被參照的資料列並不會依任何順序排列;以及若要決定順序,應使用同時具有 TOP 與 ORDER BY 的子查詢這兩點。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

MSMQ 已經被廢除了嗎?
沒有。截至 2026 年 7 月,無論是 Windows 用戶端的已淘汰功能清單,還是 Windows Server 的「已移除功能與停止開發功能」清單,都沒有列出 MSMQ。它仍以 Windows 選用功能的形式,隨附於現行的作業系統中。「已經廢除」這種說法之所以流傳開來,是因為與 .NET 端的狀況混為一談。處理 MSMQ 的標準類別庫 System.Messaging 只存在於 .NET Framework,並未移植到 .NET(Core 以後)。也就是說,正確的狀態是「作為作業系統功能仍然存續,但沒有從現代 .NET 使用它的官方途徑」。
有沒有辦法從 .NET 8 或 .NET 10 使用 MSMQ?
沒有官方的受管理 API。System.Messaging 是到 .NET Framework 4.8.1 為止的 API,也不包含在 Windows 相容性套件(Microsoft.Windows.Compatibility)中。透過 P/Invoke 呼叫 MSMQ 的原生 Win32 API(如 MQSendMessage)在技術上是可行的,但這代表要自行撰寫並維護包含格式器與交易整合在內的包裝層。社群方面,CoreWCF 專案發布了 MSMQ 傳輸用的套件(CoreWCF.MSMQ),但這是用來在現代 .NET 上代管透過佇列呼叫的 WCF 服務(WCF 伺服器端的移植),既不是可以取代 System.Messaging 的通用佇列 API,也不能替代傳送端的用戶端。其實作本身也依賴 .NET Framework 版 System.Messaging 的社群移植版本。它可以作為驗證或暫時延續使用的選項。CoreWCF 本身有 Microsoft 官方的支援政策,但這並不代表這個 MSMQ 實作所依賴的 System.Messaging 社群移植版本也享有同樣的保證,因此若要把它當成業務系統的前提,應先確認所使用的版本是否在支援政策範圍內,以及依賴部分要如何處理,再做判斷。實務上,正途是在把應用程式升級到 .NET 的同時,一併遷移佇列。
遷移目標選 RabbitMQ 還是 Azure Service Bus 比較好?
在這兩個選項之前,請先考慮把資料庫的資料表當成佇列使用的方案。使用 MSMQ 的中小規模業務系統,多數情況下佇列的對象都是更新自家資料庫的處理。這種情況下,資料表佇列可以在與業務資料相同的本機交易中,同時確定「取出訊息」與「更新業務資料」,用更單純的機制取代原本由 MSMQ+分散式交易(DTC)所實現的一致性。若出現資料表佇列滿足不了的需求,例如需要在多個系統之間鬆散耦合地傳遞,或需要高吞吐量,再依序考慮:地端需求較強就選 RabbitMQ,可以放在雲端就選 Azure Service Bus,依這個順序推進比較不容易失敗。
當下先維持 .NET Framework、擱置不動,這樣的判斷可以嗎?
有條件地可以。.NET Framework 4.8 作為 Windows 的元件,會依照作業系統的生命週期受到支援,而 MSMQ 本身也尚未被淘汰,因此可以預期它「會持續運作」。不過,若選擇擱置不動,至少要做到以下四點:在裝機程序中明確記載啟用 MSMQ 功能的步驟、建立佇列長度與日誌佇列的監控機制、把訊息格式與連線組態文件化、為負責人異動預留復原程序。擱置不動真正的風險不在技術本身,而在於「變得沒有人能碰它」。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽