MSMQ 能用到什麼時候 ── 「連淘汰都稱不上」的遺留佇列遷移判斷

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

「這個系統用了 MSMQ,聽說已經廢除了,是不是要趕快遷移比較好?」──這是過去一年左右,在遺留系統遷移諮詢中反覆被問到的問題。

答案有點曲折。MSMQ(Microsoft Message Queuing)別說廢除了,連官方的淘汰(deprecated)都稱不上。截至 2026 年 7 月,Microsoft 的淘汰功能清單中並沒有 MSMQ 的名字,現行的 Windows 也仍以選用功能的形式隨附。儘管如此,現場之所以覺得它是「已經結束的技術」,是因為.NET 端的官方受管理 API 已經封閉。處理 MSMQ 的標準函式庫 System.Messaging 只存在於 .NET Framework,並未移植到 .NET(Core 以後)。

也就是說,MSMQ 並不是「明天就會停用」的技術,而是「一旦想把應用程式升級到 .NET,就會立刻撞牆」的技術

本文的目標讀者是負責維護、遷移既有 MSMQ 系統的開發者,以及被要求做出這項判斷的資訊系統部門人員。目標是協助各位確認「聽說已經廢除」這個傳聞的真偽,並依照自己所處環境的條件,判斷是否要進行遷移。

本文將把傳聞與事實區分開來,整理繼續使用或遷移的判斷,以及遷移目標的選擇方式。至於從 VB6 或 .NET Framework 遷移本身,已經在「VB6 遷移到 .NET 的實務做法」「.NET Framework → .NET 遷移前檢核表」中處理過,本文將聚焦在佇列的部分。

1. 先講結論

  • MSMQ 官方並未將其列為淘汰項目。無論是 Windows 用戶端的淘汰功能清單,還是 Windows Server 的移除・停止開發功能清單,都沒有列出它(截至 2026 年 7 月)。12
  • 但 .NET 沒有官方的受管理 API。System.Messaging 的適用範圍僅限於 .NET Framework 1.1〜4.8.1,3 也不包含在 .NET 遷移時的相容手段「Windows 相容套件」中。4 透過 P/Invoke 呼叫原生 Win32 API 的路仍然存在,但那只是延命手段的定位(第 3 章)。
  • 雖然有 CoreWCF 這條移植路徑,但適用對象僅限於經由佇列呼叫的 WCF 服務(接收端)遷移,是需要先確認條件才能使用的路徑。CoreWCF 本身有 Microsoft 的支援政策,但 MSMQ 傳輸(CoreWCF.MSMQ)的實作依賴 .NET Framework 版 System.Messaging 的社群移植版本。5
  • 判斷的軸心在於「是否要把應用程式升級到 .NET」。若要升級,原則上佇列也應一併遷移(雖然也有透過 P/Invoke 或 CoreWCF 延命的路徑,但那等於是選擇承擔維護負擔)。若不升級(擱置不動),只要 .NET Framework 4.8 還作為作業系統元件受到支援,就可以規劃讓系統持續運作下去。6
  • 遷移目標的首選,不是訊息代理服務,而是資料庫的資料表佇列。MSMQ+分散式交易(DTC)所維護的一致性,用能與業務資料庫在同一交易中處理的資料表佇列來取代,才是最直接的做法(第 5 章)。
  • 請優先盤點訊息格式。BinaryFormatter 系列的序列化已在 .NET 9 中從執行環境移除,7 若讓新舊系統以舊格式並存,會在遷移中卡關(第 6 章)。

2. 30 秒讀懂 MSMQ

MSMQ 是隨附於 Windows 的訊息佇列基礎架構。應用程式將訊息寫入佇列,另一個應用程式(不論在同一台機器上或不同機器上)可以在方便的時機取出。其特徵可以歸納為以下三點。

  • store-and-forward(儲存並轉發): 即使接收端當掉,訊息也會先儲存在本機端,等恢復後再送達,對於據點之間不穩定的線路很有韌性。不過這種持久性並非無條件成立,預設的高速(express)訊息可能只停留在記憶體上,一旦 MSMQ 服務或機器重新啟動就會遺失。只有傳送端指定為 Recoverable(可復原)的訊息,或交易訊息,才會被持久化到磁碟。
  • 交易: 可以把佇列操作納入交易,搭配 MS DTC(分散式交易協調器)使用時,可以將「從佇列取出」與「更新資料庫」放在同一個分散式交易中確定。
  • 隨作業系統附帶: 由於不需要額外導入中介軟體就能使用,因此在 2000 年代的業務系統中被廣泛採用,尤其是訂單資料的串接、報表的非同步處理、製造現場工序之間的串接等場景。

正因為「隨作業系統附帶、支援交易、對離線環境有韌性」這樣的組合十分優秀,MSMQ 至今仍在第一線持續使用。在思考遷移目標時,實際使用到這三項特性中的哪一項,正是判斷的核心。

本文使用的最小詞彙集

第 5 章的判斷表與第 6 章的盤點,都以下列用語為前提。請在此一次掌握。8

  • 私人佇列(Private Queue) ── 不會發布到目錄服務(Active Directory),只登錄在該台電腦上的佇列。以 .\private$\佇列名稱 這樣的形式指定。相對的,公用佇列(Public Queue) 會登錄到目錄服務,可以在網域內搜尋。中小規模業務系統所使用的,幾乎都是私人佇列。
  • express 訊息 ── 預設的傳送模式。無論在配送中或配送後都放在記憶體上,因此速度快,但 MSMQ 服務或機器停止時就會遺失。
  • recoverable(可復原)訊息 ── 在傳送端與中繼的各台電腦上都會寫入磁碟,並在目的佇列中同樣保留在磁碟上的模式。可以跨越重新啟動而保留下來,由傳送端明確指定。
  • 交易佇列 ── 只處理交易訊息的佇列。在建立佇列時就已決定,之後無法變更(這一點會影響第 6 章的遷移步驟)。
  • DTC(分散式交易協調器) ── 用來協調跨越多個資源(如 MSMQ 佇列與資料庫)之交易的 Windows 服務。它是讓「從佇列取出」與「更新資料庫」一併確定的機制,也是第 5 章遷移判斷中最大的爭議點。
  • 日誌佇列 / 配送失敗(死信)佇列 ── MSMQ 自動產生的系統佇列。日誌佇列保存「已傳送/已取出訊息的副本」,配送失敗佇列則保存「無法送達的訊息」。放著不管會持續佔用容量,因此在擱置不動的運維方式中會成為監控對象(第 7 章)。

3. 事實關係 ── 「已經廢除」並不正確

「MSMQ 還能不能用」這個問題之所以會讓人混亂,是因為不同層級的答案本來就不一樣,卻被混在一起討論。先用一張表整理全貌。

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

以下依序確認此表格各列的根據。

第一,MSMQ 並未列在淘汰清單中。查看 Windows 用戶端的「Deprecated features」清單,可以看到 NTLM、VBScript、WordPad 都在其中,但沒有 MSMQ 的項目。1 在 Windows Server 端的「Features Removed or No Longer Developed」清單中,包含 Windows Server 2025 分頁在內,也同樣沒有列出 MSMQ。2 淘汰(deprecated)是「停止積極開發」這種官方宣告,而目前的現況是,連這個宣告都還沒有發布。

第二,System.Messaging 停留在 .NET Framework 上不再前進。參考文件所涵蓋的版本從 .NET Framework 1.1 到 4.8.1 為止,並不存在 .NET(Core 以後)的版本。3 作為 .NET Framework 專用 API 承接管道的 Windows 相容套件(Microsoft.Windows.Compatibility),雖然提供了登錄、WMI、Windows 服務、EventLog 等約兩萬個 API,但其技術領域清單中並不包含訊息傳遞(System.Messaging)。4 正確地說,被封閉的是受管理 API。MSMQ 的 Win32 原生 API(MQSendMessageMQReceiveMessage 等)目前仍然有文件記載,從 .NET 透過 P/Invoke 呼叫在技術上是可行的。不過,原本由 System.Messaging 吸收的格式器與交易整合,都必須自行撰寫成包裝層並持續維護,因此它的定位不是「值得長期依賴的正解」,而是「無論如何都要保留時的延命手段」。

第三,WCF 的 MSMQ 整合也處在同一道牆之內。.NET Framework 的 WCF 有以 MSMQ 為底層的繫結,但若想在現代 .NET 上重現這條路徑,最終會走到社群主導的 CoreWCF。CoreWCF 作為佇列系傳輸的一環,發布了支援 MSMQ 的套件(CoreWCF.MSMQ),但其實作明確標示依賴於 .NET Framework 版 System.Messaging 函式庫的社群移植版本。5 若只論能不能動,是能動的,而且 CoreWCF 本身備有 Microsoft 官方的支援政策,所以也不能說它「完全是野生的函式庫」。5 不過,MSMQ 傳輸所依賴的 System.Messaging 社群移植版本,是否享有同樣的待遇則是另一回事。若要採用,請先確認所使用的版本是否在支援政策的涵蓋範圍內,以及這個依賴部分的處理方式,再做判斷。

以上就是文首那張表格中各列的根據。並不是「MSMQ 已經廢除」,而是「從現代 .NET 通往 MSMQ 沒有官方的路徑」──掌握這個區別,公司內部的討論才會對得上焦點。

4. 「明明還在動,卻是問題」的真相

涉及 MSMQ 的系統諮詢,大多不是從故障開始,而是從遷移的估算開始。問題的真正核心並不是 MSMQ 本身,而是 MSMQ 成了把整個應用程式綁在 .NET Framework 上的錨

.NET Framework 4.8 被視為 Windows 的元件,依所安裝作業系統的生命週期提供支援。6 所以對於「還會不會繼續動」這個問題,可以回答「短期內會持續運作」。即便如此,以下幾點會隨著時間確實變得愈來愈沉重。

  • 招不到人、也交接不了。能夠解說 System.Messaging 與 DTC 的技術人員逐年減少。這正是「如何承接沒有原始碼、沒有文件的系統維護工作」一文中所描述情況的典型案例。
  • 享受不到執行環境與新函式庫的紅利。許多新版 C# 語法只要更新編譯器,在 .NET Framework 上也能使用,但執行環境端的效能改善與新的標準函式庫都用不到,而且愈來愈多近期的套件已將 .NET Framework 排除在支援對象之外,能選用的套件也逐漸變少。
  • 分散式交易會成為遷移中最大的難關。MSMQ+DTC 那種「讓佇列取出與資料庫更新具備原子性」的設計,在雲端佇列服務上無法重現。若放著這部分不管、只把周邊部分改成 .NET 化,最後會留下最難啃的核心。
  • 序列化格式的定時炸彈。舊系統的訊息內文有時採用二進位序列化,而 BinaryFormatter 已在 .NET 9 中從執行環境移除。7 遷移時必須連同訊息格式一併重新檢視。

也就是說,「MSMQ 能用到什麼時候」這個問題的實務答案是:「只要作業系統支援就能繼續運作,但遷移的難度會隨著拖延而確實升高」。即使決定把判斷往後延,至少也應該先透過下一章的判斷表掌握「難點在哪裡」。

5. 遷移目標判斷表

選擇遷移目標時,不是去問「MSMQ 的後繼產品是哪一個」,而是依實際使用到的特性來挑選。

5.1. 先用四個問題釐清需求

  1. 佇列的對象(接收端的處理)是不是更新自家資料庫的處理?
  2. 是否使用分散式交易(DTC)?(交易佇列 + 資料庫更新)
  3. 傳送端與接收端是不是不同機器、不同據點?離線韌性(store-and-forward)是不是真的有在使用?
  4. 訊息量實際上大約多少?(多數業務系統每天是數千到數萬筆,無論選哪個方案都綽綽有餘)

5.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 判斷表

5.3. 資料表佇列的最小實作

很多人聽到「資料表佇列」還是沒有具體概念,這裡展示最小的形式。首先是資料表定義(SQL Server)。

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);

取出時,一次只會替一筆資料標記為「處理中」,同時取得其內容。加上 READPAST 之後,可以跳過其他工作處理程序鎖定的資料列而不必等待(可以讓多個處理程序同時運作)。

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;

READ_COMMITTED_SNAPSHOTON 的資料庫中,READPAST 沒辦法直接使用。官方文件明確指出:「當 READ_COMMITTED_SNAPSHOTON,且工作階段的隔離等級為 READ COMMITTED(或查詢中同時使用了 READCOMMITTED 提示)時,無法指定 READPAST」,並提出對策是同時加上 READCOMMITTEDLOCK 提示10 由於預設的隔離等級就是 READ COMMITTED,在只是啟用了 READ_COMMITTED_SNAPSHOT 的資料庫上,這段取出查詢別說是「跳過其他工作處理程序的資料列」了,連陳述式本身都會出錯。Azure SQL Database 預設就是 ON,而在地端環境中,為了防止讀取阻塞而啟用它的環境也不少見。請先確認遷移目標的資料庫是哪一種情況。

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

之所以從常見寫法 WITH (READPAST, UPDLOCK, ROWLOCK) 中拿掉了 ROWLOCK,是因為ROWLOCKREADCOMMITTEDLOCK 屬於同一個「粒度提示」群組,一張資料表無法同時指定兩者。10 READPAST 能跳過的只有資料列鎖定,無法跳過分頁鎖定,所以本來會想明確指定 ROWLOCK,但對於透過 TOP (1) 索引搜尋所取得的這一筆資料列而言,實務上幾乎不會發生鎖定升級。若已確定 READ_COMMITTED_SNAPSHOTOFF,而想明確指定 ROWLOCK,請把 READCOMMITTEDLOCK 拿掉。

若省略 ORDER BY、只寫成 UPDATE TOP (1),會選中哪一筆資料列就是不確定的。官方明確指出 UPDATETOP 不會對目標資料列排序,即使有 (Status, Id) 索引,也不能保證取出的順序。11 在以「依投入順序處理」為前提的串接中,會出現舊工作被持續延後處理的問題。

不過,ORDER BY Id 所確保的只有取出的順序處理完成的順序並不會一致。READPAST 是用來「跳過其他工作處理程序所鎖定的資料列」的指定,因此當工作處理程序 A 取得工作 1 並花費較長時間處理時,工作處理程序 B 有可能取得工作 2 並先行提交。

會保持一致的 不會保持一致的
哪一筆資料列先被取出(ORDER BY Id) 哪一筆資料列先被反映到業務資料

像「對同一個訂單編號的更新順序若對調就會出問題」這種反映順序具有意義的串接,光靠這樣還不夠。可以採取以下兩種做法之一:

  • 把消費者限定為單一個。捨棄並行度,換取順序保證。若吞吐量足夠,這是最單純、也最不容易出錯的形式
  • 依鍵值分開處理。以訂單編號等鍵值固定對應的工作處理程序,或是在取出端加上「若相同鍵值的資料列正在處理中則不取出」的條件,只在同一個鍵值內保證順序。跨鍵值的順序則放棄保證

若兩者都不採用,就必須在遷移設計中明確寫下:一旦以並行方式運作,整體就沒有 FIFO 保證。從 MSMQ 遷移過來時,這一點特別容易被忽略。MSMQ 的佇列若以並行方式接收,同樣會發生一樣的情況,但若帶著「因為是佇列所以會按順序」這種理解遷移過來,就會看起來像是「一改成資料表佇列,順序問題就冒出來了」。

接收端的工作處理程序,只需要把這段邏輯放在與業務處理相同的本機交易中執行即可(C#,虛擬碼)。

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();   // 「取出」與「業務更新」在這一行同時確定
}

這就是取代 MSMQ+DTC 的關鍵所在。在 MSMQ 中,「從佇列取出」與「更新資料庫」是不同的資源,因此需要分散式交易(DTC)才能把兩者整合在一起。若佇列本身就是資料庫的資料表,兩者都是對同一個資料庫的更新,只需要普通的本機交易就足夠了。一旦導入 RabbitMQ 或 Azure Service Bus,這項特性就會消失(佇列與資料庫又變回不同的資源),冪等性與重送就必須由應用程式端自行實作。

在實際維運中,大致需要再補上以下三點。

  • 回收「處理中」狀態下當掉的資料列。Status = 1StartedAt 早於某個時間門檻的資料列,加入定期執行的復原處理,把它們的狀態改回 Status = 0
  • 重試上限與退避。RetryCount 超過上限的資料列,移到另一張資料表(或標記為 Status = 9)。這相當於 MSMQ 配送失敗佇列的存放位置。
  • 清理已完成的資料列。定期刪除或封存 Status = 2 的資料列。放著不管會讓資料表變得肥大,索引效率也會下降。

在傳送端,把業務資料的更新與訊息資料列的 INSERT 放進同一個交易中(Outbox 模式)。這樣一來,「業務資料已經更新,但訊息卻沒有送出」這種不一致的情況也會消失。

想強調的重點是,在中小規模的業務系統中,資料表佇列往往會成為首選。訊息佇列產品的比較文章常常會變成「RabbitMQ vs Kafka vs Service Bus」這種形式,但 MSMQ 世代的系統放在佇列上的,多半是「更新同一個資料庫的非同步工作」,而這用資料庫的資料表加上輪詢(或通知)就已足夠,而且能用更簡單的方式實現。多增加一套新的中介軟體所帶來的維運成本(監控、容錯備援、修補、人員教育),對中小型的組織體制來說格外沉重。

6. 遷移的實務步驟

進行方式依循遺留系統遷移的定石,也就是「先觀測、再動手」。

  1. 盤點依賴。從程式碼中找出所有對 System.Messaging 的參照,以及建立 MessageQueue 的地方。同時也請搜尋 WCF 組態檔中的 netMsmqBinding / msmqIntegrationBinding、原生 API(MQSendMessageMQ* 函式)以及透過 COM 的呼叫。即使沒有參照 System.Messaging,也有可能存在依賴 MSMQ 的路徑。要確認的觀點包括:(a) 佇列的路徑(本機或遠端、私人或公用)、(b) 是否為交易佇列、訊息是否設為 Recoverable(若兩者皆非,代表現行系統是以「重新啟動就會遺失訊息」為前提在運作)、(c) 格式器(XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter)、(d) 日誌佇列・配送失敗佇列的使用情況、(e) 由誰負責建立與刪除(安裝程式或應用程式本身)。

    盤點若只是確認過一遍,對遷移判斷並不會產生實際效果。請依照下列格式,每一條佇列填一列,直接作為遷移方針的依據(可以原封不動當作簽核文件的附件)。

    觀點 要看哪裡 記錄的內容 對判斷的影響
    (a) 佇列的路徑 傳給 MessageQueue 的路徑字串、組態檔中的連線目標、FormatName: 指定 本機/遠端之別、私人/公用之別、對方的機器名稱 若為遠端・跨據點,判斷是否需要 store-and-forward(對應 5.2 表格中「離線韌性」那一列)。若在本機內完結,則歸入 5.2 表格的第一列
    (b) 交易與 Recoverable 建立佇列的地方(是否為交易佇列)、傳送時是否指定 Recoverable、是否使用 DTC 是否為交易佇列、是否指定 Recoverable、是否參與 DTC 若有使用 DTC,遷移目標幾乎可以確定為資料表佇列。若兩者皆無,則須明確記載現行系統是以「重新啟動就會遺失訊息」為前提在運作
    (c) 格式器 XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter 的指定位置 使用中的格式器,以及訊息本體的型別 若為 BinaryFormatter 系列,必須替換為 JSON(步驟 2)。這是遷移工時的主要因素7
    (d) 日誌・配送失敗佇列 佇列的內容值(日誌是否啟用)、系統佇列的內容、運維手冊 是否使用日誌佇列、是否有實際查看配送失敗佇列 「如何處理失敗訊息」會成為遷移目標的設計需求(資料表佇列的話就是退避資料表)
    (e) 由誰建立・刪除 安裝程式、部署指令碼、應用程式啟動時呼叫的 Create 建立的主體,以及由誰設定權限 遷移時「由誰準備新佇列」會直接對應過去。若選擇擱置不動,則轉記到裝機程序中(第 7 章)
  2. 決定訊息格式。遷移後的格式以 JSON 為預設。不能帶進新系統的是像 BinaryMessageFormatter 這樣的BinaryFormatter 系列序列化。BinaryFormatter 已在 .NET 9 中移除,基於安全性考量也不建議使用。7 另一方面,若使用的是像 Protocol Buffers 或 MessagePack 這種規格獨立維護的二進位格式,把該格式原封不動搬到新系統本身並無問題。
  3. 從接收端開始遷移。先準備好新的佇列(例如資料表佇列),讓接收端的處理支援新佇列之後,再切換傳送端。在遷移期間,若在中間夾入一個把訊息從舊 MSMQ 搬到新佇列的小型橋接程式(這部分維持 .NET Framework 即可),就不必一次性切換所有傳送端。不過,若橋接程式在「從 MSMQ 取出」與「寫入新佇列」之間當掉,就會遺失訊息或造成重複流通。若遷移來源是交易佇列,最低條件是讓 MSMQ 端以交易方式接收,寫入失敗時可以復原,並讓新佇列端能以訊息 ID 排除重複(具備冪等性)的設計。若遷移來源是非交易佇列,則無法採用這個做法,因為佇列的交易屬性在建立時就已決定,之後無法變更。這種情況下,應依「用 Peek 讀取 → 以冪等方式寫入新佇列 → 確認寫入成功後才移除」的順序來組裝(即使在移除之前當掉,重複的部分也會由新佇列端的冪等性吸收)。若這兩種做法對於系統規模來說都不划算,不夾橋接程式、直接朝步驟 5 的「清空後切換」靠攏會更安全。
  4. 先固定行為,再進行改寫。佇列處理是容易出現時序相依錯誤的領域。若在遷移前先準備好「這個輸入訊息會得到這個結果」的特性化測試,替換之後的驗證就能機械化地進行(參見「以特性化測試固定行為後再進行重構」)。
  5. 在佇列內容清空的狀態下切換。在還有訊息殘留的狀態下切換,是重複處理與遺漏的溫床。透過計畫性停機把佇列處理到清空之後再切換,終究是最安全也最快的方法。

7. 選擇擱置不動時的最低條件

「沒有把應用程式升級到 .NET 的計畫,從成本效益來看短期內也不遷移」這種判斷,在有條件的前提下是合理的。以下用可以直接轉記到簽核・審查資料中的檢查清單形式,列出這種情況下的最低條件。請如此理解:唯有四項全部打勾,「擱置不動」才會成為一個可行選項。

確認 最低條件 具體要做的事 若不滿足會發生什麼
在裝機・復原程序中明確記載 MSMQ MSMQ 是 Windows 的選用功能。請在環境建置手冊中寫下「啟用 Windows 功能」或透過 DISM/PowerShell 啟用的步驟 更換 PC・伺服器時忘記啟用,導致遷移當天原因不明地無法運作
監控佇列長度 建立滯留筆數的門檻監控與通知機制,並將配送失敗佇列與日誌佇列也納入監控對象 即使接收端停止也不會報錯,只會持續累積,導致沒有人察覺,業務就這樣停擺
將組態文件化 記錄佇列的路徑、權限、是否有交易、格式器、日誌設定(可以直接沿用第 6 章的盤點表) 未來估算遷移時必須從調查重新開始,精確度與工時都會變差
每年檢視一次判斷 每年確認「是否已列入淘汰清單」「作業系統更新是否改變了行為」 等到淘汰公告發布後才慌忙行動

第四項特別容易被輕忽,但正因為有這個年度確認,即使公告發布之後也不至於慌張。反過來說,若在無法滿足這四項的環境中選擇擱置不動,就等同於「在看不見的狀態下,靜待某天突然停擺」。

8. 總結

  • 截至 2026 年 7 月,MSMQ 並未被官方列為淘汰項目,作為作業系統功能仍然存續。「已經廢除」這種認知並不正確。12
  • 另一方面,System.Messaging 仍停留在僅限 .NET Framework 使用的階段,不包含在相容套件中,也沒有通往 .NET(Core 以後)的官方路徑。雖然 CoreWCF 本身有支援政策,但其 MSMQ 傳輸依賴 System.Messaging 的社群移植版本。345
  • 判斷的軸心在於「是否要把應用程式升級到 .NET」。若要升級,原則上佇列也應一併遷移(以 P/Invoke 等方式延命,等於是用維護負擔去交換);若選擇擱置不動,則以監控與文件化作為成立的條件。6
  • 遷移目標的首選是資料庫的資料表佇列。MSMQ+DTC 的一致性,本質上應以本機交易來取代,訊息代理產品的導入則放在其後再考慮。
  • BinaryFormatter 系列的序列化,由於在 .NET 9 中已被移除,無法帶入新系統。遷移時應以 JSON 為預設,但若是像 protobuf 這種獨立維護的格式,直接沿用也無妨。7
  • 依「接收端 → 橋接程式 → 傳送端」的順序切換,並先用特性化測試固定行為之後再改寫,是不會出事故的進行方式。

相關文章

相關諮詢領域

合同會社小村軟體承接包含 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, 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 3 4

  5. 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 4 5

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

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

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

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

  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

  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)所實現的一致性。若資料表佇列無法滿足需求,例如需要在多個系統之間進行鬆散耦合的傳遞,或需要高吞吐量,再依序考慮:若地端(on-premises)需求較強就選 RabbitMQ,若可以放在雲端就選 Azure Service Bus,依這個順序判斷較不容易失敗。
當下先維持 .NET Framework、擱置不動,這樣的判斷可以嗎?
有條件地可以。.NET Framework 4.8 作為 Windows 的元件,會依照作業系統的生命週期受到支援,而 MSMQ 本身也尚未淘汰,因此可以預期「會持續運作」。不過,若選擇擱置不動,至少要做到以下四點:在裝機程序中明確記載啟用 MSMQ 功能的步驟、建立佇列長度與日誌(journal)的監控機制、將訊息格式與連線組態文件化、為負責人異動預留回復程序。擱置不動真正的風險不在技術本身,而在於「變得沒有人能夠處理」。

作者檔案

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

Go Komura

小村軟體有限公司 代表

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

回到部落格一覽