MSMQ 能用到什么时候 ── 「连弃用都算不上」的遗留队列迁移判断

· · Windows, .NET, C#, MSMQ, 消息队列, 遗留技术, 迁移, 信息系统

「这套系统用了 MSMQ,听说已经被废止了,是不是得赶紧迁移?」──在遗留系统迁移的咨询中,这大约是过去一年里被反复问到的问题。

答案有点拧巴。MSMQ(Microsoft Message Queuing)不仅没有被废止,官方甚至都没有把它列为弃用。截至 2026 年 7 月,微软的弃用功能一览中并没有 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 本身有微软的支持策略,但 MSMQ 传输(CoreWCF.MSMQ)的实现依赖于 .NET Framework 版 System.Messaging 的社区移植。5
  • 判断的主轴是「是否要把应用升级到 .NET」。如果要升级,原则上队列也应一并迁移(虽然也有用 P/Invoke 或 CoreWCF 续命的路,但那是要自行承担维护负担的选择)。如果不升级(搁置),只要 .NET Framework 4.8 作为 OS 组件继续获得支持,就可以制定持续运行下去的计划。6
  • 迁移目标的首选不是消息代理,而是数据库的表队列。MSMQ + 分布式事务(DTC)所守护的一致性,用能够与业务数据库在同一事务中处理的表队列来替代,更加直截了当(第 5 章)。
  • 请首先对消息格式做一次盘点。BinaryFormatter 系列的序列化已在 .NET 9 中从运行时移除,7 如果原样保留旧格式让新旧系统并存,迁移会卡住(第 6 章)。

2. 30 秒了解 MSMQ

MSMQ 是长期同捆在 Windows 中的消息队列基础设施。应用程序把消息写入队列,另一个应用程序(可以在同一台机器上,也可以在不同机器上)在自己方便的时机取出消息。它的特点可以归纳为以下 3 点。

  • store-and-forward(存储转发): 即使接收方宕机,消息也会先在本地累积,待恢复后再送达,对站点间不稳定的线路有很强的适应力。不过这种持久性并非无条件的:默认的高速(express)消息可能只停留在内存中,会因 MSMQ 服务或机器重启而丢失。真正会持久化到磁盘的,是发送方指定为 Recoverable(可恢复)的消息,或事务性消息。
  • 事务: 队列操作可以纳入事务,与 MS DTC(分布式事务协调器)组合使用时,可以把「从队列取出」与「数据库更新」放进同一个分布式事务中一并确定。
  • 随 OS 同捆: 无需额外引入中间件即可使用,因此在 2000 年代的业务系统中,特别是在订单数据联动、报表的异步处理、生产现场的工序间联动等场景中被广泛采用。

正因为「随 OS 同捆、事务生效、抗离线」这一组合足够优秀,MSMQ 才得以在今天依然现役。在考虑迁移目标时,判断的核心同样在于:这 3 个特点中,实际用到的是哪一个。

本文使用的最小术语集

第 5 章的判断表与第 6 章的盘点,都以下列术语为前提。请在这里一次性掌握。8

  • 私有队列(Private Queue) ── 不发布到目录服务(Active Directory),只注册在本机上的队列。以 .\private$\队列名 之类的形式指定。与之相对的公共队列(Public Queue)会注册到目录服务中,可以在域内被检索到。中小规模业务系统中使用的,绝大多数是私有队列。
  • express 消息 ── 默认的发送模式。无论在传输途中还是送达之后都保存在内存中,因此速度快,但一旦 MSMQ 服务或机器停止就会丢失。
  • recoverable(可恢复)消息 ── 在发送方以及中继过程中经过的每台计算机上都会写入磁盘,在目的队列中也保存在磁盘上的模式。可以跨越重启保留下来,需要由发送方显式指定。
  • 事务性队列(Transactional Queue) ── 只处理事务性消息的队列。该属性在创建队列时就已确定,之后无法更改(这会影响第 6 章的迁移步骤)。
  • DTC(分布式事务协调器) ── 用于调停跨多个资源(如 MSMQ 的队列与数据库)的事务的 Windows 服务。它是把「从队列取出」与「数据库更新」合并为一体加以确定的机制,也是第 5 章迁移判断中最大的争议点。
  • 日志队列(Journal Queue) / 无法送达(死信)队列 ── MSMQ 自动生成的系统队列。日志队列保留「已发送/已取出消息的副本」,无法送达队列则积累「未能送达的消息」。放任不管会持续占用容量,因此在搁置运行时需要将其纳入监控对象(第 7 章)。

3. 事实关系 ── 「已被废止」并不准确

「MSMQ 还能不能用」这个问题之所以会让人混乱,是因为不同层面的答案本就不同,却被混在一起谈论。先用一张表把全貌梳理出来。

层面 当前状态 实务意义
OS 功能(Windows 的可选功能) 存续。同捆在现行 Windows 客户端/Server 中,也没有发布弃用声明12 启用后现在依然能用。在 OS 的支持期内可以继续使用下去
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 承担的格式化器与事务协作都得自己写成包装层并持续维护,因此它的定位不是「继续使用的正解」,而是「无论如何都要保留 MSMQ 时的续命手段」。

第三,WCF 的 MSMQ 联动也处在同一堵墙内。.NET Framework 的 WCF 曾有以 MSMQ 为底层的绑定方式,但想在现代 .NET 上重现这条路径,最终会走到社区主导的 CoreWCF 上。CoreWCF 作为队列类传输的一环,发布了 MSMQ 对应的包(CoreWCF.MSMQ),但其实现明确声明依赖于 .NET Framework 版 System.Messaging 库的社区移植。5 就能不能用而言,是能用的,而且 CoreWCF 本身也备有微软官方的支持策略,所以也并非「完全是一个野生库」。5 不过,MSMQ 传输所依赖的 System.Messaging 社区移植是否享有同等待遇,则是另一回事。如果要采用,请先确认所使用的版本是否在支持策略范围内、以及这部分依赖该如何处理,再做判断。

以上就是本节开头那张一览表中每一行的依据。不是「MSMQ 已被废止」,而是「没有一条从现代 .NET 通往 MSMQ 的官方道路」──掌握这个区分,能让公司内部的讨论更容易对上焦点。

4. “明明还能用却有问题”的真相

涉及 MSMQ 的系统咨询,大多不是从故障开始,而是从迁移的工时估算开始的。问题的真相并不在 MSMQ 本身,而在于 MSMQ 成为了把整个应用锁死在 .NET Framework 上的锚

.NET Framework 4.8 被当作 Windows 的组件对待,会随所安装 OS 的生命周期获得支持。6 所以对「能不能继续运行」这个问题,可以回答「短期内会持续运行」。即便如此,下面这些方面会随着时间推移确实变得越来越沉重。

  • 招不到人、也交接不下去。能够讲清楚 System.Messaging 与 DTC 的技术人员逐年减少。这正是《没有源代码也没有文档的系统该如何维护》一文中所写的典型局面。
  • 享受不到运行时与新库带来的好处。很多新的 C# 语法只需更新编译器,在 .NET Framework 上也能用,但运行时一侧的性能改进与新的标准库都用不上,而且越来越多近期发布的包也逐渐把 .NET Framework 排除在支持范围之外,可选项会持续减少。
  • 分布式事务将成为迁移中最难啃的一关。MSMQ + DTC 那种「让队列取出与数据库更新保持原子性」的设计,在云端的队列服务中无法重现。如果放着这部分不管,只把周边先 .NET 化,最后剩下的会是那块最硬的核心。
  • 序列化格式这颗定时炸弹。旧系统的消息正文有时会是二进制序列化的,而 BinaryFormatter 已在 .NET 9 中从运行时移除。7 迁移时需要连同消息格式一起重新审视。

也就是说,「MSMQ 能用到什么时候」这个问题在实务上的答案是:「只要 OS 还在支持就能用,但迁移的难度会随着拖延而不断上升」。即便打算把判断往后推迟,也应该先通过下一章的判断表,把「难点在哪里」先摸清楚。

5. 迁移目标判断表

迁移目标不是靠「MSMQ 的后继产品是哪个」来选,而是要按实际用到的特性逐一挑选

5.1. 先把需求归结为 4 个问题

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

取出操作会把 1 行标记为「处理中」,同时取回其正文。加上 READPAST,就能跳过其他 worker 正锁定的行而无需等待(可以让多个进程同时运行)。

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 的数据库上,这条取出语句非但不会「跳过其他 worker 的行」,反而整条语句都会报错。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) 索引查找取到的这 1 行来说,实际上几乎不会发生锁升级。如果确定 READ_COMMITTED_SNAPSHOTOFF、想显式加上 ROWLOCK,那就把 READCOMMITTEDLOCK 去掉。

如果省略 ORDER BY 只写 UPDATE TOP (1)会选中哪一行是不确定的。官方明确说明,UPDATETOP 不会对目标行进行排序,即使存在 (Status, Id) 索引,也不能保证取出顺序。11 在以「按投入顺序处理」为前提的联动中,这会表现为旧任务被持续拖后处理的缺陷。

不过,ORDER BY Id 只能保证取出的顺序一致,处理完成的顺序并不一致。READPAST 是用来「跳过其他 worker 正锁定的行」的指定,所以当 worker A 拿到任务 1 并耗时较久时,worker B 完全可能拿到任务 2 并先一步提交。

能保证一致的 无法保证一致的
哪一行会被先取出(ORDER BY Id) 哪一行会先被反映到业务数据

对于像「同一订单编号的更新如果顺序颠倒就会出问题」这类反映顺序有意义的联动场景,这样是不够的。可以采取的做法有以下两种之一。

  • 把消费者收敛为单一线路。放弃并行度以换取顺序保证。只要吞吐量够用,这是最简单、也最不容易出问题的形式
  • 按键(key)划分。用订单编号等键把 worker 固定下来,或者在取出侧加上「若同一键的行正在处理中则不取出」的条件,只在键内部保证顺序。跨键的顺序则放弃保证

如果两者都不采用,请在迁移设计中明确写清楚:一旦并行运行,整体的 FIFO 就不复存在。从 MSMQ 迁移过来时,这一点尤其容易被忽略。MSMQ 的队列如果也并行接收,同样会出现这种情况,但如果带着「因为是队列所以顺序有保证」这种理解迁移过来,就会显得好像一改成表队列就冒出了顺序问题一样。

接收端的 worker 只需把这些操作放在与业务处理相同的本地事务中运行即可(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,这个特性就会消失(队列与数据库重新变回不同资源),就需要在应用一侧自行实现幂等性与重发。

在实际运维中,大致还需要补上以下 3 点。

  • 回收停留在「处理中」状态却已崩溃的行。把定期执行任务加入进去,将 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. 先固定行为,再改写代码。队列处理是很容易出现时序相关 bug 的领域。在迁移前准备好「输入这样的消息就应得到这样的结果」这类特性测试,替换之后的验证工作就能变得机械化(参见《用特性测试固定行为后再重构》)。
  5. 在队列内容清空的状态下切换。在还有消息残留的状态下切换,是重复处理与遗漏的温床。通过计划性停机把队列消耗殆尽之后再切换,归根结底才是最安全、也最快的方法。

7. 选择搁置时的最低条件

「没有把应用升级到 .NET 的计划,从成本效益上看也暂不迁移」这一判断,在满足条件的前提下是合理的。下面以可以直接转载到审批・评审资料中的检查清单形式,列出这种情况下的最低条件。请这样理解:只有 4 项全部打勾,「搁置」才能真正成为一个选项

确认项 最低条件 具体要做的事 不满足会发生什么
在装机・恢复步骤中明确写出 MSMQ MSMQ 是 Windows 的可选功能。把「启用 Windows 功能」或通过 DISM/PowerShell 启用的步骤,写进环境搭建操作手册 更换 PC・服务器时忘记启用,导致上线当天原因不明地无法运行
监控队列长度 建立滞留件数的阈值监控与通知。把无法送达队列与日志队列也纳入监控对象 接收端停止时不会报错,只会持续累积,导致业务在无人察觉的情况下停摆
把配置写成文档 记录队列的路径、权限、是否使用事务、格式化器、日志设置(可以直接沿用第 6 章的盘点表) 将来评估迁移工时时要从调查重新开始,精度与工时都会恶化
每年重新审视一次判断 每年确认一次「是否已被列入弃用清单」「OS 更新是否改变了行为」 等到弃用公告发布后才慌忙行动

第 4 项特别容易被轻视,但正因为有这道年度确认,即使公告发布之后也不至于慌乱。反过来说,如果这 4 项都无法满足的环境里选择搁置,就等同于「在看不见的地方等着它某天悄悄停摆」。

8. 总结

  • 截至 2026 年 7 月,MSMQ 尚未被官方列为弃用,作为 OS 功能仍然存续。「已经废止」这种认知并不准确。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 的社区主导项目、微软为其提供官方支持策略;作为队列类传输的一环,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 操作系统的组件,会按照所安装的父产品(OS)的生命周期政策获得支持这一点。  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 消息会在发送方以及中继过程中经过的每台计算机上写入磁盘,在目的队列中也保存在磁盘上;公共队列会注册到目录服务中,而私有队列只注册在本地计算机上、不会公开到目录服务;以及保留从队列中取出的消息或已发送消息副本的日志队列,和保留未能送达的消息的无法送达(死信)队列,都是 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 的提示;并且对同一张表不能同时指定 2 个以上的粒度提示(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 月,MSMQ 既没有出现在 Windows 客户端的弃用功能一览中,也没有出现在 Windows Server 的「已删除功能・停止开发功能」一览中。它仍作为 Windows 的可选功能同捆在现行操作系统中。之所以「已被废止」的说法广为流传,是因为与 .NET 一侧的现状混淆了。处理 MSMQ 的标准类库 System.Messaging 只存在于 .NET Framework,并未移植到 .NET(Core 及之后版本)。也就是说,准确地说,现状是「作为 OS 功能仍然存续,但没有面向现代 .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 本身拥有微软官方的支持策略,但这个保证并不延伸到该 MSMQ 实现所依赖的 System.Messaging 社区移植上,因此如果要将其作为业务系统的前提,应先确认所使用的版本是否在支持策略范围内、以及依赖部分该如何处理,再做判断。从实务角度看,正道是在把应用升级到 .NET 的同时一并迁移队列。
迁移目标该选 RabbitMQ 还是 Azure Service Bus?
在这二选一之前,请先考虑把数据库的表当作队列使用的方案。使用 MSMQ 的中小规模业务系统里,队列对端的处理多数是更新自家数据库。在这种情况下,表队列可以在与业务数据相同的本地事务中同时确定「取出消息」与「更新业务数据」,用更简单的机制替代原本由 MSMQ + 分布式事务(DTC)实现的一致性。只有在表队列无法满足的需求上,例如需要多系统间的松耦合分发或高吞吐量,才去考虑:本地部署需求较强就选 RabbitMQ,能放在云端就选 Azure Service Bus。按这个顺序推进不容易踩坑。
暂时维持 .NET Framework 不动(搁置)的判断可行吗?
有条件地可行。.NET Framework 4.8 作为 Windows 的组件,会随操作系统的生命周期获得支持,MSMQ 本身也未被弃用,因此可以预期它会「继续运行」。不过,如果选择搁置,至少要做到以下 4 点:在装机(kitting)流程中明确写出 MSMQ 功能的启用步骤、加入队列长度与日志(journal)的监控、把消息格式与连接配置写成文档、为负责人调动做好准备并留下恢复步骤。搁置真正的风险不在技术,而在于「变得没有人能碰它」。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表