29、MSMQ 消息队列技术详解与应用实践

MSMQ 消息队列技术详解与应用实践

1. MSMQ 事务处理

MSMQ 支持事务,事务是一个原子工作单元,要么全部成功,要么全部失败。以银行系统为例,一个事务可能涉及通过一个消息队列从支票账户扣款,通过另一个队列向储蓄账户存款。如果在事务进行中系统发生故障,除非事务回滚,否则银行将面临被盗用资金的风险。系统重启后,事务可以再次执行。

以下是在 C# 和 VB.NET 中实现向队列添加两条消息的事务代码示例:

C# 代码

private void btnSend_Click(object sender, System.EventArgs e)
{
    int zero = 0;
    string queueName = ".\\private$\\test2";
    MessageQueueTransaction msgTx = new MessageQueueTransaction();
    MessageQueue mq;
    if (MessageQueue.Exists(queueName))
    {
        mq = new MessageQueue(queueName);
    }
    else
    {
        mq = MessageQueue.Create(queueName, true);
    }
    msgTx.Begin();
    try
    {
        mq.Send("Message 1", msgTx);
        zero = 5 / zero; // deliberate error
        mq.Send("Message 2", msgTx);
        msgTx.Commit();
    }
    catch
    {
        msgTx.Abort();
    }
    finally
    {
        mq.Close();
    }
}

VB.NET 代码

Private Sub btnSend_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim zero As Integer = 0
    Dim queueName As String = ".\private$\test2"
    Dim msgTx As MessageQueueTransaction = New MessageQueueTransaction()
    Dim mq As MessageQueue
    If MessageQueue.Exists(queueName) Then
        mq = New MessageQueue(queueName)
    Else
        mq = MessageQueue.Create(queueName, True)
    End If
    msgTx.Begin()
    Try
        mq.Send("Message 1", msgTx)
        zero = 5 / zero ' deliberate error
        mq.Send("Message 2", msgTx)
        msgTx.Commit()
    Catch
        msgTx.Abort()
    Finally
        mq.Close()
    End Try
End Sub

代码解释:
- Begin 方法启动一个事务,在调用 Commit 方法之前,对队列的任何更改都不会实际发生。
- 如果调用 Abort 方法或计算机崩溃, Begin 方法之后的所有语句将被忽略。
- 在这个例子中,第二条消息发送前出现错误,抛出异常,导致事务中止。

测试步骤:
1. 在 Visual Studio .NET 中运行应用程序,保留代码中的故意错误。
2. 点击发送按钮,然后打开计算机管理并查看消息队列。会发现创建了第二个队列,但没有消息被发布。
3. 从代码中移除故意错误,重新运行应用程序,点击发送按钮,会看到两条消息出现在队列消息列表中。

2. MSMQ 确认机制

MSMQ 的大部分工作是在后台完成的,对应用程序完全透明。如果 MSMQ 由于某种原因失败,应用程序和用户可能不知道当天的数据从未传输。确认机制为发送应用程序提供了一种验证接收应用程序是否已读取消息以及消息队列是否正常运行的方法。

以下是 C# 和 VB.NET 中实现确认机制的代码示例:

C# 代码

private void btnSend_Click(object sender, System.EventArgs e)
{
    string queueName = ".\\private$\\test";
    MessageQueue mq;
    if (MessageQueue.Exists(queueName))
    {
        mq = new MessageQueue(queueName);
    }
    else
    {
        mq = MessageQueue.Create(queueName);
    }
    System.Messaging.Message myMessage = new System.Messaging.Message();
    myMessage.Body = tbMessage.Text;
    myMessage.AdministrationQueue = new MessageQueue(".\\private$\\test");
    myMessage.AcknowledgeType = AcknowledgeTypes.FullReachQueue | AcknowledgeTypes.FullReceive;
    mq.Send(myMessage);
}

VB.NET 代码

Private Sub btnSend_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim queueName As String = ".\private$\test"
    Dim mq As MessageQueue
    If MessageQueue.Exists(queueName) Then
        mq = New MessageQueue(queueName)
    Else
        mq = MessageQueue.Create(queueName)
    End If
    Dim myMessage As System.Messaging.Message = New System.Messaging.Message()
    myMessage.Body = tbMessage.Text
    myMessage.AdministrationQueue = New MessageQueue(".\private$\test")
    myMessage.AcknowledgeType = AcknowledgeTypes.FullReachQueue Or AcknowledgeTypes.FullReceive
    mq.Send(myMessage)
End Sub

代码解释:
- 代码检查名为 \private$\test 的私有队列是否存在,如果不存在则创建。
- 创建一个消息并设置其内容,同时设置确认类型为 FullReachQueue FullReceive ,表示确认消息到达队列和到达最终接收者。
- 确认消息将出现在同一个测试队列中。

测试步骤:
1. 在 Visual Studio .NET 中运行代码,在提供的文本框中输入一些文本,然后点击发送按钮。
2. 打开计算机管理 -> 消息队列 -> 私有队列 -> 测试,会注意到确认消息穿插在列表中。确认消息的主体大小为 0,信封图标上有一个绿色圆圈。
3. 接收程序可以通过将消息的 MessageType 设置为 MessageType.Acknowledgment 来识别确认消息。

3. MSMQ 超时机制

“迟到的数据是坏数据” 这一说法特别适用于 MSMQ。例如,在使用 MSMQ 协调最后时刻的酒店预订时,如果在预订后 24 小时以上无法联系到酒店(客户端),则必须采取替代措施,如让操作员手动致电酒店确认预订。

超时机制为消息设置了有效期,如果消息未能及时到达目的地,可以将其删除或移动到死信队列,以便采取替代措施。

以下是 C# 和 VB.NET 中实现消息五秒超时的代码示例:

C# 代码

private void btnSend_Click(object sender, System.EventArgs e)
{
    string queueName = ".\\private$\\test";
    MessageQueue mq;
    if (MessageQueue.Exists(queueName))
    {
        mq = new MessageQueue(queueName);
    }
    else
    {
        mq = MessageQueue.Create(queueName);
    }
    System.Messaging.Message myMessage = new System.Messaging.Message();
    myMessage.Body = tbMessage.Text;
    myMessage.TimeToBeReceived = new TimeSpan(0, 0, 0, 5);
    myMessage.UseDeadLetterQueue = true;
    mq.Send(myMessage);
}

VB.NET 代码

Private Sub btnSend_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim queueName As String = ".\private$\test"
    Dim mq As MessageQueue
    If MessageQueue.Exists(queueName) Then
        mq = New MessageQueue(queueName)
    Else
        mq = MessageQueue.Create(queueName)
    End If
    Dim myMessage As System.Messaging.Message = New System.Messaging.Message()
    myMessage.Body = tbMessage.Text
    myMessage.TimeToBeReceived = New TimeSpan(0, 0, 0, 5)
    myMessage.UseDeadLetterQueue = True
    mq.Send(myMessage)
End Sub

代码解释:
- TimeToBeReceived 属性将消息的接收时间设置为五秒。
- UseDeadLetterQueue 属性设置为 true ,所有超过有效期的消息将被移动到死信队列进行管理。

测试步骤:
1. 在 Visual Studio .NET 中运行代码,在文本框中输入内容并点击发送按钮。
2. 快速打开计算机管理,点击测试队列(可能需要右键刷新),会看到列表中有一条新消息。
3. 五秒后刷新队列,消息将消失。点击系统队列 -> 死信消息,可查看过期消息。

4. MSMQ 日志记录

日志记录用于记录与远程机器之间的传入和传出消息。要指定消息应记录在日志中,使用 UseJournalQueue 方法。

以下是 C# 和 VB.NET 中实现日志记录的代码示例:

C# 代码

private void btnSend_Click(object sender, System.EventArgs e)
{
    string queueName = ".\\private$\\test";
    MessageQueue mq;
    if (MessageQueue.Exists(queueName))
    {
        mq = new MessageQueue(queueName);
    }
    else
    {
        mq = MessageQueue.Create(queueName);
    }
    System.Messaging.Message myMessage = new System.Messaging.Message();
    myMessage.Body = tbMessage.Text;
    myMessage.UseJournalQueue = true;
    mq.Send(myMessage);
}

VB.NET 代码

Private Sub btnSend_Click(ByVal sender As Object, ByVal e As System.EventArgs)
    Dim queueName As String = ".\private$\test"
    Dim mq As MessageQueue
    If MessageQueue.Exists(queueName) Then
        mq = New MessageQueue(queueName)
    Else
        mq = MessageQueue.Create(queueName)
    End If
    Dim myMessage As System.Messaging.Message = New System.Messaging.Message()
    myMessage.Body = tbMessage.Text
    myMessage.UseJournalQueue = True
    mq.Send(myMessage)
End Sub

代码解释:
- 代码创建一个队列并将字符串作为消息发布到队列。
- 由于 UseJournalQueue 属性设置为 true ,消息在被接收后将被移动到系统日志队列。

测试步骤:
1. 在 Visual Studio .NET 中运行代码,在文本框中输入内容并点击发送按钮。
2. 打开计算机管理,查看测试队列,确认消息已在系统中。
3. 启动消息接收程序,点击监听。消息应出现在接收程序的文本框中,并从队列中移除。
4. 点击系统队列 -> 日志消息,会再次看到该消息。

5. MSMQ 排队组件

到目前为止,大多数 MSMQ 代码示例都非常关注消息发送和接收的底层实现。你可能希望编写能够抽象出底层 MSMQ 发送和接收机制的代码,更多地关注业务逻辑。

MSMQ 可以与 COM+ 组件服务协同工作,通过排队组件提供一种异步、排队调用对象方法的方式。

以下是创建排队组件的步骤和代码示例:

创建步骤:
1. 在 VS.NET 命令提示符下输入 sn –k CompPlusServer.snk 生成强名称密钥文件。
2. 在 Visual Studio .NET 中启动一个新的类库项目,输入以下代码:

C# 代码

[assembly: ApplicationName("ComPlusServer")]
[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: AssemblyKeyFile("..\\..\\ComPlusServer.snk")]
[assembly: ApplicationQueuing(Enabled = true, QueueListenerEnabled = true)]
namespace ComPlusService
{
    public interface IComPlusServer
    {
        void ExecSQLAsync(string SQL, string strDSN);
    }
    [InterfaceQueuing(Interface = "IComPlusServer")]
    public class ComPlusServer : ServicedComponent, IComPlusServer
    {
        public void ExecSQLAsync(string SQL, string strDSN)
        {
            OleDbConnection DSN = new OleDbConnection(strDSN);
            DSN.Open();
            OleDbCommand oSQL = new OleDbCommand("", DSN);
            oSQL.CommandText = SQL;
            oSQL.ExecuteNonQuery();
            DSN.Close();
        }
    }
}

VB.NET 代码

<assembly: ApplicationName("ComPlusServer")>
<assembly: ApplicationActivation(ActivationOption.Server)>
<assembly: AssemblyKeyFile("..\..\ComPlusServer.snk")>
<assembly: ApplicationQueuing(Enabled := True, QueueListenerEnabled := True)>
Public Interface IComPlusServer
    Sub ExecSQLAsync(ByVal SQL As String, ByVal strDSN As String)
End Interface
<InterfaceQueuing([Interface] := "IComPlusServer")>
Public Class ComPlusServer
    Inherits ServicedComponent
    Implements ServicedComponent, IComPlusServer
    Public Sub ExecSQLAsync(ByVal SQL As String, ByVal strDSN As String)
        Dim DSN As New OleDbConnection(strDSN)
        DSN.Open()
        Dim oSQL As New OleDbCommand("", DSN)
        oSQL.CommandText = SQL
        oSQL.ExecuteNonQuery()
        DSN.Close()
    End Sub
End Class

代码解释:
- 定义了一个接口 IComPlusServer ,包含 ExecSQLAsync 方法的原型。
- ComPlusServer 类实现了该接口, ExecSQLAsync 方法用于打开数据库连接,执行 SQL 语句并关闭连接。
- 排队组件的一个限制是不能有返回值。

使用 DLL 作为排队组件的进一步步骤:
1. 在命令提示符下输入 gacutil /I:ComPlusService.dll 将 DLL 导入全局程序集缓存(GAC)。
2. 在命令提示符下输入 regsvcs ComPlusService.DLL 将 DLL 导入组件服务。
3. 从管理工具中打开组件服务,展开计算机 -> 我的计算机 -> COM+ 应用程序。右键单击 ComPlusServer ,选择属性 -> 安全,取消选中 “对此应用程序强制执行访问检查”。
4. 右键单击 ComPlusServer ,点击启动。

编写客户端调用组件方法的代码示例:

C# 代码

private void btnExecSQL_Click(object sender, System.EventArgs e)
{
    ComPlusService.IComPlusServer ComPlusServer = null;
    ComPlusServer = (IComPlusServer)Marshal.BindToMoniker("queue:/new:ComPlusService.ComPlusServer");
    ComPlusServer.ExecSQLAsync(this.tbSQL.Text, this.tbDSN.Text);
    Marshal.ReleaseComObject(ComPlusServer);
}

VB.NET 代码

Private Sub btnExecSQL_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnExecSQL.Click
    Dim ComPlusServer As ComPlusService.IComPlusServer = Nothing
    ComPlusServer = CType(Marshal.BindToMoniker("queue:/new:ComPlusService.ComPlusServer"), IComPlusServer)
    ComPlusServer.ExecSQLAsync(Me.tbSQL.Text, Me.tbDSN.Text)
    Marshal.ReleaseComObject(ComPlusServer)
End Sub

代码解释:
- 代码不直接在 ComPlusService 组件上执行 ExecSQLAsync 方法,而是将指令写入 MSMQ 中的 ComPlusService 队列,然后由组件服务读取并在组件上执行该方法。

测试步骤:
1. 在 Visual Studio .NET 中运行客户端,输入有效的 DSN 和 SQL 语句,然后点击 “执行 SQL” 按钮。
2. 会看到数据库在几分钟内更新。
3. 如果从组件服务中临时停止组件并继续使用客户端,组件重新启动后,更改将立即应用。

MSMQ 消息队列技术详解与应用实践

6. MSMQ 安全机制

使用 MSMQ 会给攻击者提供另一个访问敏感信息的途径。如果没有加密和身份验证,MSMQ 永远不能用于处理信用卡信息或其他金融交易。

6.1 加密设置

要在 MSMQ 中加密消息,在发送消息之前将 UseEncryption 属性设置为 true 。这样可以防止消息在传输过程中被窃听,并且在接收端会自动解密。

加密算法可以使用 EncryptionAlgorithm 属性进行选择,可以设置为 RC2 RC4 RC4 是一种流密码,因此比 RC2 更快。

6.2 身份验证设置

要在 MSMQ 中使用身份验证,在发送消息之前将 UseAuthentication 属性设置为 true 。这将向接收者保证消息发送者是合法的,但不能防止数据包被窃听。

哈希算法可以使用 HashAlgorithm 属性进行选择,可以设置为 MAC MD2 MD4 MD5 SHA none 。默认算法是 MD5 ,尽管 MAC (密钥哈希)是最安全的。

经过身份验证的消息需要附带一个外部证书,该证书包含在 SenderCertificate 属性中。外部证书必须在 MSMQ 的目录服务中注册。外部证书包含有关证书颁发机构、证书用户、证书有效期、证书用户的公钥、证书颁发机构的签名等信息。

6.3 手动调整安全属性

在某些情况下,MSMQ 通常设置的消息属性可能不适合特定应用程序,可以手动调整 MSMQ 的底层安全方面。这包括 DestinationSymetricKey 属性,它只是一个字节数组,用于在发送和接收消息时对消息进行加密和解密。要访问此属性, ConnectorType 属性必须设置为一个真正唯一的标识符(GUID)。

ConnectorType 设置后,可以更改的底层身份验证属性包括 AuthenticationProviderName AuthenticationProviderType DigitalSignature 。这些方法分别指定应用于消息的身份验证的名称、类型和凭据,默认分别为 Microsoft Base Cryptographic Provider, Ver. 1.0 RSA_FULL 和一个零长度数组。

6.4 HTTP 下的安全措施

当 MSMQ 通过 HTTP 使用时,可以采用标准的 Web 安全系统,如 HTTPS。在这种情况下,MSMQ 服务器域名将以 https:// 为前缀。

以下是安全相关属性的总结表格:
| 属性 | 描述 | 可选值 | 默认值 |
| ---- | ---- | ---- | ---- |
| UseEncryption | 是否加密消息 | true false | 无 |
| EncryptionAlgorithm | 加密算法 | RC2 RC4 | 无 |
| UseAuthentication | 是否使用身份验证 | true false | 无 |
| HashAlgorithm | 哈希算法 | MAC MD2 MD4 MD5 SHA none | MD5 |
| SenderCertificate | 外部证书 | 包含证书信息的对象 | 无 |
| DestinationSymetricKey | 对称密钥 | 字节数组 | 无 |
| ConnectorType | 唯一标识符 | GUID | 无 |
| AuthenticationProviderName | 身份验证提供程序名称 | 字符串 | Microsoft Base Cryptographic Provider, Ver. 1.0 |
| AuthenticationProviderType | 身份验证提供程序类型 | 字符串 | RSA_FULL |
| DigitalSignature | 数字签名 | 字节数组 | 零长度数组 |

以酒店预订中心为例,假设预订中心通过 MSMQ 向酒店转发信用卡信息。预订中心需要确保拨打 MSMQ 服务器的人确实是酒店而不是黑客。此外,如果酒店的技术人员能够从酒店网络中窃听信用卡信息,那将是一场灾难。

首先,酒店需要从证书颁发机构(如 Verisign 或 Thawte)获取一个 X.509 证书。包含私钥的证书将留在酒店,而公钥证书将发送到预订中心。

当预订中心接到电话订单时,会将一条消息放入队列,该消息将使用证书中的公钥进行加密。此时,黑客仍然可以接收消息,但无法读取。然而,仍然存在一个问题,即酒店不知道是否有新消息或消息是否被盗。

为了避免这种情况,预订中心需要酒店确认已收到预订。确认消息将只是一个用证书中的私钥加密的确认参考号码。攻击者无法生成此消息,因此消息可以重新发布,等待正确的接收者拾取。

7. MSMQ 可扩展性

当计算机系统规模扩大时,它们之间传输的数据量也会增加。MSMQ 需要能够处理更大的数据量和更大的网络。

7.1 队列配额设置

MSMQ 可能会消耗大量磁盘空间,因此可能需要确保某些队列不会增长到填满硬盘并阻止其他队列运行的大小。要设置队列配额,可以通过以下步骤操作:
1. 打开计算机管理,点击消息队列。
2. 选择相关队列(例如,私有队列 -> 测试 2)。
3. 右键单击队列并选择属性。
4. 在 “Limit message storage to (KB):” 框中设置队列配额。计算机配额也可以以相同的方式设置。

7.2 MQIS 数据库

另一个对 MSMQ 正常运行至关重要且占用空间的项目是 MQIS 数据库,这是一个包含队列信息和网络拓扑的内部数据库。这是一个分布式数据库,因此多个 MSMQ 服务器可以保存该数据。

7.3 多服务器部署

在网络中多个网段通过临时连接相互连接的情况下,可以在每个网段部署多个 MSMQ 服务器。例如,一个国际连锁商店将其销售点数据集中在地区办公室进行每日结算处理,并每周将数据发送到总部进行审计。

在 MSMQ 术语中,整个连锁店称为一个企业,每个地区办公室是一个站点,每个商店是一个客户端。位于总部的 MSMQ 服务器称为主企业控制器(PEC),地区办公室的服务器称为主站点控制器(PSCs)。

还有其他三种类型的 MSMQ 服务器:备用站点控制器(BSCs)、路由服务器和连接器服务器。

  • 备用站点控制器(BSCs) :需要一个 PEC 和一个 PSC,并存储 PSC 数据库的只读备份。这确保如果 PSC 离线,客户端仍然可以从 BSC 读取数据。
  • 路由服务器 :提供一种机制,如果网络连接中断,可以通过替代路由转发消息。例如,假设有两个站点(纽约和多伦多)和一个位于达拉斯的总部。如果纽约和多伦多之间的直接连接中断,路由服务器可以将消息转发到其他可用路径。

下面是 MSMQ 服务器类型的关系 mermaid 流程图:

graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;

    A(企业):::process --> B(主企业控制器 - PEC):::process
    A --> C(站点):::process
    C --> D(主站点控制器 - PSC):::process
    C --> E(备用站点控制器 - BSC):::process
    D --> E
    F(客户端 - 商店):::process --> C
    G(路由服务器):::process --> C
    H(连接器服务器):::process --> C

综上所述,MSMQ 提供了丰富的功能和机制来满足不同场景下的消息队列需求。通过合理运用事务处理、确认机制、超时机制、日志记录、排队组件、安全机制和可扩展性设置,可以构建出高效、安全、可靠的消息队列系统。在实际应用中,需要根据具体的业务需求和环境特点,灵活选择和配置这些功能,以达到最佳的性能和效果。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值