首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 訊息佇列應用程式:建立高可用 Windows 服務處理 MSMQ

     編輯:關於C#

前言:企業級整合的骨幹——訊息佇列

在微服務與事件驅動架構普及前,Microsoft Message Queuing (MSMQ) 早已提供可靠的非同步訊息傳遞、負載平衡與錯誤重試機制。本文展示如何使用 C# 與 .NET Framework 建立一個生產級的 Windows 服務,支援多佇列並行處理、三種路由策略 (RoundRobin/AppSpecific/Assembly)、效能計數器監控與標準化安裝部署。

系統架構總覽

┌─────────────────────────────────────────────────────────────┐
│                    MSDNMessageService (Windows Service)     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ CWorker      │  │ CWorker      │  │ CWorker      │  ...  │
│  │ (Queue: test │  │ (Queue: test │  │ (Queue: test │       │
│  │  _load1)     │  │  _load2)     │  │  _load3)     │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │                │
│  ┌──────▼───────┐  ┌──────▼───────┐  ┌──────▼───────┐       │
│  │ CWorkerThread│  │ CWorkerThread│  │ CWorkerThread│       │
│  │ (Thread 1)   │  │ (Thread 1)   │  │ (Thread 1)   │       │
│  │ (Thread 2)   │  │              │  │ (Thread 2)   │       │
│  └──────────────┘  └──────────────┘  └──────────────┘       │
└─────────────────────────────────────────────────────────────┘
         ▲                    ▲                    ▲
    RoundRobin           AppSpecific           Assembly (IWebMessage)

核心類別職責鏈

類別繼承/實作核心職責
ServiceControlServiceBase服務進入點,解析 XML 設定,建立/管理 CWorker 集合
CWorker-對應單一輸入佇列,建立 N 個 CWorkerThread,轉發 Start/Stop/Pause/Continue
CWorkerThread (abstract)-抽象基類,封裝訊息接收迴圈、錯誤處理、佇列開關、資源釋放鉤子
CWorkerThreadRoundRobinCWorkerThread輪詢模式:依序將訊息轉發至多個輸出佇列
CWorkerThreadAppSpecificCWorkerThread依訊息 AppSpecific 屬性值決定目標輸出佇列
CWorkerThreadAssemblyCWorkerThread動態載入組件,呼叫實作 IWebMessage 的 Process() 方法

關鍵介面定義

public enum WebMessageReturn { ReturnGood, ReturnBad, ReturnAbort }

public interface IWebMessage
{
    WebMessageReturn Process(string label, string body, int appSpecific);
    void Release();  // 釋放昂貴資源 (DB連線、檔案控制代碼)
}

XML 設定檔驅動架構 (MSDNMessageService.cfg)

<?xml version="1.0"?>
<configuration>
  <ProcessList>
    <!-- RoundRobin: 2 threads, 輸出輪詢 2 queues -->
    <ProcessDefinition
      ProcessName="Worker1"
      ProcessType="RoundRobin"
      ProcessThreads="2"
      InputQueue=".\private$\test_load1"
      ErrorQueue=".\private$\test_error">
      <OutputList>
        <OutputDefinition OutputName=".\private$\test_out11" />
        <OutputDefinition OutputName=".\private$\test_out12" />
      </OutputList>
    </ProcessDefinition>

    <!-- Assembly: 1 thread, 呼叫自訂組件 -->
    <ProcessDefinition
      ProcessName="Worker2"
      ProcessType="Assembly"
      ProcessThreads="1"
      InputQueue=".\private$\test_load2"
      ErrorQueue=".\private$\test_error">
      <OutputList>
        <OutputDefinition OutputName="C:\MSDNMessageService\MessageExample.dll" />
        <OutputDefinition OutputName="MSDNMessageService.MessageSample.ExampleClass" />
      </OutputList>
    </ProcessDefinition>
  </ProcessList>
</configuration>

核心處理迴圈:CWorkerThread.ProcessMessages

protected void ProcessMessages()
{
    while (bRun)
    {
        if (bPause) { Thread.Sleep(500); continue; }

        try
        {
            // 1. 接收訊息 (1秒逾時)
            Message mInput = mqInput.Receive(new TimeSpan(0,0,1));
            
            // 2. 處理訊息 (衍生類實作)
            ProcessMessage(mInput);
            
            // 3. 更新效能計數器
            pcMsgTotWorker.Increment();
            pcMsgSecWorker.Increment();
        }
        catch (MessageQueueException mqe) when (mqe.ErrorCode == -1072824293) 
        {
            // 逾時忽略,繼續迴圈
        }
        catch (CWorkerThreadException ex)
        {
            ProcessError(mInput, ex.Terminate);  // 送錯誤佇列,決定是否終止執行緒
        }
        catch (Exception)
        {
            ProcessError(mInput, true);  // 未知錯誤 → 強制終止
        }
    }
}

Assembly 模式深度解析:動態載入與介面呼叫

public class CWorkerThreadAssembly : CWorkerThread
{
    private IWebMessage _messageProcessor;
    private string _assemblyPath, _typeName;

    protected override void OnStart()
    {
        _assemblyPath = wfThread.OutputName[0];  // DLL 路徑
        _typeName = wfThread.OutputName[1];      // 完整類別名

        Assembly asm = Assembly.LoadFrom(_assemblyPath);
        Type type = asm.GetType(_typeName);
        object instance = Activator.CreateInstance(type);
        _messageProcessor = (IWebMessage)instance;
    }

    protected override void ProcessMessage(Message msg)
    {
        WebMessageReturn ret = _messageProcessor.Process(
            msg.Label, (string)msg.Body, msg.AppSpecific);

        switch (ret)
        {
            case WebMessageReturn.ReturnBad:
                throw new CWorkerThreadException("Message marked bad", false);
            case WebMessageReturn.ReturnAbort:
                throw new CWorkerThreadException("Process terminating", true);
        }
    }

    protected override void OnPause()  => _messageProcessor?.Release();
    protected override void OnContinue() => OnStart();  // 重新載入
    protected override void OnStop()   => _messageProcessor?.Release();
}

監控與診斷:效能計數器與事件日誌

效能計數器類別建立 (服務啟動時)

CounterCreationData[] counters = new CounterCreationData[2];
counters[0] = new CounterCreationData("Messages/Total", "Total Messages", PerformanceCounterType.NumberOfItems64);
counters[1] = new CounterCreationData("Messages/Second", "Messages Per Sec", PerformanceCounterType.RateOfChangePerSecond32);
PerformanceCounterCategory.Create("MSDN Message Service", "Counters", counters);

每執行緒實例計數器

pcMsgTotWorker = new PerformanceCounter("MSDN Message Service", "Messages/Total", processName, false);
pcMsgSecWorker = new PerformanceCounter("MSDN Message Service", "Messages/Second", processName, false);
pcMsgTotWorker.RawValue = 0; pcMsgSecWorker.RawValue = 0;

// 處理迴圈中
pcMsgTotWorker.Increment(); pcMsgSecWorker.Increment();

事件日誌寫入

if (!EventLog.SourceExists("MSDNMessageService"))
    EventLog.CreateEventSource("MSDNMessageService", "Application");

EventLog.WriteEntry("Service started", EventLogEntryType.Information);

安裝部署:Installer 類別與 installutil.exe

[RunInstaller(true)]
public class ServiceRegister : Installer
{
    public ServiceRegister()
    {
        ServiceInstaller svc = new ServiceInstaller
        {
            ServiceName = "MSDNMessageService",
            DisplayName = "MSDN Message Queue Processor",
            StartType = ServiceStartMode.Manual
        };
        ServiceProcessInstaller proc = new ServiceProcessInstaller
        {
            Account = ServiceAccount.LocalSystem  // 需 MSMQ 權限
        };
        Installers.AddRange(new Installer[] { svc, proc });
    }
}

安裝指令:installutil.exe MSDNMessageService.exe
解除安裝:installutil.exe /u MSDNMessageService.exe

現代化遷移建議

原 MSMQ 架構現代替代方案遷移重點
MSMQ 佇列RabbitMQ / Azure Service Bus / Kafka協定轉換、訊息格式標準化 (JSON/Protobuf)
Windows ServiceK8s Deployment / Azure Container Apps / BackgroundService容器化、健康檢查、水平擴展
XML 設定檔appsettings.json / Consul / etcd動態配置、環境隔離
效能計數器Prometheus + Grafana / Application Insights標準化指標、分散式追蹤
ActiveXMessageFormatterSystem.Text.Json / Protobuf.NET跨平台序列化、版本容忍

總結

本文展示的訊息佇列處理服務,雖基於 .NET Framework 1.1/2.0 時代技術,但其「設定驅動、多執行緒佇列消費、策略模式路由、介面導向擴展、完整觀測性」的架構設計原則,放在今天的雲原生環境同樣適用。理解這套經典實作,能幫助開發者在遷移至現代訊息中介軟體時,保留核心業務邏輯並正確映射至新技術棧。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved