Share via


Menggunakan Antrean Surat Mati untuk Menangani Kegagalan Transfer Pesan

Pesan yang diantrekan dapat gagal dikirimkan. Pesan yang gagal ini direkam dalam antrean surat mati. Pengiriman yang gagal dapat disebabkan oleh alasan seperti kegagalan jaringan, antrian yang dihapus, antrian penuh, kegagalan autentikasi, atau kegagalan pengiriman tepat waktu.

Antrean pesan dapat tetap berada dalam antrean untuk waktu yang lama jika aplikasi penerima tidak membacanya dari antrean secara tepat waktu. Perilaku ini mungkin tidak sesuai untuk pesan sensitif waktu. Pesan sensitif waktu memiliki properti Time to Live (TTL) yang diatur dalam pengikatan antrean, yang menunjukkan berapa lama pesan dapat berada dalam antrean sebelum harus kedaluwarsa. Pesan yang kedaluwarsa dikirim ke antrean khusus yang disebut antrean surat mati. Pesan juga dapat dimasukkan ke dalam antrean surat mati karena alasan lain, seperti melebihi kuota antrean atau karena kegagalan autentikasi.

Umumnya, aplikasi menulis logika kompensasi untuk membaca pesan dari antrian surat mati dan alasan kegagalan. Logika kompensasi tergantung pada penyebab kegagalan. Misalnya, dalam kasus kegagalan autentikasi, Anda dapat memperbaiki sertifikat yang dilampirkan dengan pesan dan mengirim ulang pesan. Jika pengiriman gagal karena kuota antrean target tercapai, Anda dapat memasang ulang pengiriman dengan harapan masalah kuota diselesaikan.

Sebagian besar sistem antrean memiliki antrean surat mati di seluruh sistem di mana semua pesan yang gagal dari sistem tersebut disimpan. Message Queuing (MSMQ) menyediakan dua antrian surat mati seluruh sistem: antrian dead-letter seluruh sistem transaksional yang menyimpan pesan yang gagal dikirim ke antrean transaksional dan antrian surat mati seluruh sistem non-transaksi yang menyimpan pesan yang pengiriman gagal ke antrian non-transaksional. Jika dua klien mengirim pesan ke dua layanan yang berbeda, dan oleh karena itu antrean yang berbeda di WCF berbagi layanan MSMQ yang sama untuk dikirim, maka dimungkinkan untuk memiliki campuran pesan dalam antrean surat mati sistem. Ini tidak selalu optimal. Dalam beberapa kasus (keamanan, misalnya), Anda mungkin tidak ingin satu klien membaca pesan klien lain dari antrian surat mati. Antrean surat mati bersama juga mengharuskan klien untuk menelusuri antrean untuk menemukan pesan yang mereka kirim, yang dapat sangat mahal berdasarkan jumlah pesan dalam antrean surat mati. Oleh karena itu, di WCFNetMsmqBinding, MsmqIntegrationBinding, dan MSMQ pada Windows Vista menyediakan antrean surat mati khusus (kadang-kadang disebut sebagai antrean surat mati khusus aplikasi).

Antrean surat mati kustom menyediakan isolasi antara klien yang berbagi layanan MSMQ yang sama untuk mengirim pesan.

Pada Windows Server 2003 dan Windows XP, Windows Communication Foundation (WCF) menyediakan antrean surat mati di seluruh sistem untuk semua aplikasi klien yang diantrekan. Pada Windows Vista, WCF menyediakan antrean surat mati untuk setiap aplikasi klien yang diantrekan.

Menentukan Penggunaan Antrean Surat Mati

Antrean surat mati ada di manajer antrean aplikasi pengirim. Ini menyimpan pesan yang telah kedaluwarsa atau yang gagal ditransfer atau dikirimkan.

Pengikatan memiliki properti antrean surat mati berikut:

Membaca Pesan dari Antrean Surat Mati

Aplikasi yang membaca pesan dari antrean surat mati mirip dengan layanan WCF yang membaca dari antrean aplikasi, kecuali atas perbedaan kecil berikut:

  • Untuk membaca pesan dari antrean surat mati transaksional sistem, Pengidentifikasi Sumber Daya Seragam (URI) harus dalam bentuk: net.msmq://localhost/system$;DeadXact.

  • Untuk membaca pesan dari antrean surat mati nontransaksional sistem, URI harus dalam bentuk: net.msmq://localhost/system$;DeadLetter.

  • Untuk membaca pesan dari antrean surat mati khusus, URI harus dalam bentuk:net.msmq://localhost/private/<custom-dlq-name> dengan custom-dlq-name adalah nama antrean surat mati khusus.

Untuk informasi selengkapnya tentang cara mengatasi antrean, lihat Titik Akhir Layanan dan Pengalamatan Antrean.

Tumpukan WCF pada penerima cocok dengan alamat yang didengarkan layanan dengan alamat pada pesan. Jika alamat cocok pesan akan dikirim; jika tidak cocok pesan tidak akan dikirim. Ini dapat menyebabkan masalah saat membaca dari antrean surat mati, karena pesan dalam antrean surat gagal biasanya ditujukan ke layanan dan bukan layanan antrean surat mati. Oleh karena itu, pembacaan layanan dari antrean surat mati harus menginstal filter ServiceBehavior alamat yang menginstruksikan tumpukan untuk mencocokkan semua pesan dalam antrean secara independen dari penerima alamat. Secara khusus, Anda harus menambahkan parameter ServiceBehavior dengan Any ke layanan membaca pesan dari antrean surat mati.

Penanganan Pesan Racun dari Antrean Surat Mati

Penanganan pesan racun tersedia pada antrean surat mati, dengan beberapa kondisi. Karena Anda tidak dapat membuat sub-antrean dari antrean sistem, saat membaca dari antrean surat mati sistem, ReceiveErrorHandling tidak dapat diatur ke Move. Perhatikan bahwa jika Anda membaca dari antrean surat mati kustom, Anda dapat memiliki sub-antrean dan, oleh karena itu, Move adalah disposisi yang valid untuk pesan racun.

Ketika ReceiveErrorHandling diatur ke Reject, ketika membaca dari antrean surat mati kustom, pesan racun dimasukkan ke dalam antrean surat mati sistem. Jika membaca dari antrean surat mati sistem, pesan akan dihilangkan (hapus menyeluruh). Penolakan dari antrean dead-letter sistem di MSMQ menghilangkan (hapus menyeluruh) pesan.

Contoh

Contoh berikut menunjukkan cara membuat antrean surat gagal dan cara menggunakannya untuk memproses pesan yang kedaluwarsa. Contohnya didasarkan pada contoh dalam Cara: Pertukaran Pesan Antrean dengan Titik Akhir WCF. Contoh berikut menunjukkan cara menulis kode klien ke layanan pemrosesan pesanan yang menggunakan antrean surat mati untuk setiap aplikasi. Contohnya juga menunjukkan cara memproses pesan dari antrean surat mati.

Berikut ini adalah kode untuk klien yang menentukan antrean surat mati untuk setiap aplikasi.

using System;
using System.ServiceModel.Channels;
using System.Configuration;
//using System.Messaging;
using System.ServiceModel;
using System.Transactions;

namespace Microsoft.ServiceModel.Samples
{
    
    //The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.

    //Client implementation code.
    class Client
    {
        static void Main()
        {
            // Get MSMQ queue name from appsettings in configuration.
            string deadLetterQueueName = ConfigurationManager.AppSettings["deadLetterQueueName"];

            // Create the transacted MSMQ queue for storing dead message if necessary.
            if (!System.Messaging.MessageQueue.Exists(deadLetterQueueName))
                System.Messaging.MessageQueue.Create(deadLetterQueueName, true);

            OrderProcessorClient client = new OrderProcessorClient("OrderProcessorEndpoint");
        try
            {	

                // Create the purchase order.
                PurchaseOrder po = new PurchaseOrder();
                po.CustomerId = "somecustomer.com";
                po.PONumber = Guid.NewGuid().ToString();

                PurchaseOrderLineItem lineItem1 = new PurchaseOrderLineItem();
                lineItem1.ProductId = "Blue Widget";
                lineItem1.Quantity = 54;
                lineItem1.UnitCost = 29.99F;

                PurchaseOrderLineItem lineItem2 = new PurchaseOrderLineItem();
                lineItem2.ProductId = "Red Widget";
                lineItem2.Quantity = 890;
                lineItem2.UnitCost = 45.89F;

                po.orderLineItems = new PurchaseOrderLineItem[2];
                po.orderLineItems[0] = lineItem1;
                po.orderLineItems[1] = lineItem2;

                //Create a transaction scope.
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
                {
                    // Make a queued call to submit the purchase order.
                    client.SubmitPurchaseOrder(po);
                    // Complete the transaction.
                    scope.Complete();
                }

                client.Close();
            }
            catch(TimeoutException timeout)
            {
        Console.WriteLine(timeout.Message);
                client.Abort();
        }
            catch(CommunicationException conexcp)
            {
        Console.WriteLine(conexcp.Message);
                client.Abort();
        }

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to terminate client.");
            Console.ReadLine();
        }
    }
}
Imports System.ServiceModel.Channels
Imports System.Configuration
'using System.Messaging;
Imports System.ServiceModel
Imports System.Transactions

Namespace Microsoft.ServiceModel.Samples

    'The service contract is defined in generatedProxy.cs, generated from the service by the svcutil tool.

    'Client implementation code.
    Friend Class Client
        Shared Sub Main()
            ' Get MSMQ queue name from appsettings in configuration.
            Dim deadLetterQueueName As String = ConfigurationManager.AppSettings("deadLetterQueueName")

            ' Create the transacted MSMQ queue for storing dead message if necessary.
            If (Not System.Messaging.MessageQueue.Exists(deadLetterQueueName)) Then
                System.Messaging.MessageQueue.Create(deadLetterQueueName, True)
            End If


            Dim client As New OrderProcessorClient("OrderProcessorEndpoint")
            Try


                ' Create the purchase order.
                Dim po As New PurchaseOrder()
                po.CustomerId = "somecustomer.com"
                po.PONumber = Guid.NewGuid().ToString()

                Dim lineItem1 As New PurchaseOrderLineItem()
                lineItem1.ProductId = "Blue Widget"
                lineItem1.Quantity = 54
                lineItem1.UnitCost = 29.99F

                Dim lineItem2 As New PurchaseOrderLineItem()
                lineItem2.ProductId = "Red Widget"
                lineItem2.Quantity = 890
                lineItem2.UnitCost = 45.89F

                po.orderLineItems = New PurchaseOrderLineItem(1) {}
                po.orderLineItems(0) = lineItem1
                po.orderLineItems(1) = lineItem2

                'Create a transaction scope.
                Using scope As New TransactionScope(TransactionScopeOption.Required)
                    ' Make a queued call to submit the purchase order.
                    client.SubmitPurchaseOrder(po)
                    ' Complete the transaction.
                    scope.Complete()
                End Using


                client.Close()
            Catch timeout As TimeoutException
                Console.WriteLine(timeout.Message)
                client.Abort()
            Catch conexcp As CommunicationException
                Console.WriteLine(conexcp.Message)
                client.Abort()
            End Try

            Console.WriteLine()
            Console.WriteLine("Press <ENTER> to terminate client.")
            Console.ReadLine()
        End Sub
    End Class
End Namespace

Berikut ini adalah kode untuk file konfigurasi klien.

Berikut ini adalah kode untuk pesan pemrosesan layanan dari antrean surat mati.

using System;
using System.ServiceModel.Description;
using System.Configuration;
using System.Messaging;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Transactions;

namespace Microsoft.ServiceModel.Samples
{
    // Define a service contract.
    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
    public interface IOrderProcessor
    {
        [OperationContract(IsOneWay = true)]
        void SubmitPurchaseOrder(PurchaseOrder po);
    }

    // Service class that implements the service contract.
    // Added code to write output to the console window
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single, AddressFilterMode = AddressFilterMode.Any)]
    public class PurchaseOrderDLQService : IOrderProcessor
    {
        OrderProcessorClient orderProcessorService;
        public PurchaseOrderDLQService()
        {
            orderProcessorService = new OrderProcessorClient("OrderProcessorEndpoint");
        }

        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SimpleSubmitPurchaseOrder(PurchaseOrder po)
        {
            Console.WriteLine("Submitting purchase order did not succeed ", po);
            MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;

            Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
            Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
            Console.WriteLine();
        }

        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
        public void SubmitPurchaseOrder(PurchaseOrder po)
        {
            Console.WriteLine("Submitting purchase order did not succeed ", po);
            MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;

            Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus);
            Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure);
            Console.WriteLine();

            // Resend the message if timed out.
            if (mqProp.DeliveryFailure == DeliveryFailure.ReachQueueTimeout ||
                mqProp.DeliveryFailure == DeliveryFailure.ReceiveTimeout)
            {
                // Re-send.
                Console.WriteLine("Purchase order Time To Live expired");
                Console.WriteLine("Trying to resend the message");

                // Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
                orderProcessorService.SubmitPurchaseOrder(po);
                Console.WriteLine("Purchase order resent");
            }
        }

        // Host the service within this EXE console application.
        public static void Main()
        {
            // Create a ServiceHost for the PurchaseOrderDLQService type.
            using (ServiceHost serviceHost = new ServiceHost(typeof(PurchaseOrderDLQService)))
            {
                // Open the ServiceHostBase to create listeners and start listening for messages.
                serviceHost.Open();

                // The service can now be accessed.
                Console.WriteLine("The dead letter service is ready.");
                Console.WriteLine("Press <ENTER> to terminate service.");
                Console.WriteLine();
                Console.ReadLine();

                // Close the ServiceHostBase to shutdown the service.
                serviceHost.Close();
            }
        }
    }
}

Imports System.ServiceModel.Description
Imports System.Configuration
Imports System.Messaging
Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.Transactions

Namespace Microsoft.ServiceModel.Samples
    ' Define a service contract. 
    <ServiceContract(Namespace:="http://Microsoft.ServiceModel.Samples")> _
    Public Interface IOrderProcessor
        <OperationContract(IsOneWay:=True)> _
        Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder)
    End Interface

    ' Service class that implements the service contract.
    ' Added code to write output to the console window
    <ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single, ConcurrencyMode:=ConcurrencyMode.Single, AddressFilterMode:=AddressFilterMode.Any)> _
    Public Class PurchaseOrderDLQService
        Implements IOrderProcessor
        Private orderProcessorService As OrderProcessorClient
        Public Sub New()
            orderProcessorService = New OrderProcessorClient("OrderProcessorEndpoint")
        End Sub

        <OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
        Public Sub SimpleSubmitPurchaseOrder(ByVal po As PurchaseOrder)
            Console.WriteLine("Submitting purchase order did not succeed ", po)
            Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)

            Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
            Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
            Console.WriteLine()
        End Sub

        <OperationBehavior(TransactionScopeRequired:=True, TransactionAutoComplete:=True)> _
        Public Sub SubmitPurchaseOrder(ByVal po As PurchaseOrder) Implements IOrderProcessor.SubmitPurchaseOrder
            Console.WriteLine("Submitting purchase order did not succeed ", po)
            Dim mqProp As MsmqMessageProperty = TryCast(OperationContext.Current.IncomingMessageProperties(MsmqMessageProperty.Name), MsmqMessageProperty)

            Console.WriteLine("Message Delivery Status: {0} ", mqProp.DeliveryStatus)
            Console.WriteLine("Message Delivery Failure: {0}", mqProp.DeliveryFailure)
            Console.WriteLine()

            ' Resend the message if timed out.
            If mqProp.DeliveryFailure = DeliveryFailure.ReachQueueTimeout OrElse mqProp.DeliveryFailure = DeliveryFailure.ReceiveTimeout Then
                ' Re-send.
                Console.WriteLine("Purchase order Time To Live expired")
                Console.WriteLine("Trying to resend the message")

                ' Reuse the same transaction used to read the message from dlq to enqueue the message to the application queue.
                orderProcessorService.SubmitPurchaseOrder(po)
                Console.WriteLine("Purchase order resent")
            End If
        End Sub

        ' Host the service within this EXE console application.
        Public Shared Sub Main()
            ' Create a ServiceHost for the PurchaseOrderDLQService type.
            Using serviceHost As New ServiceHost(GetType(PurchaseOrderDLQService))
                ' Open the ServiceHostBase to create listeners and start listening for messages.
                serviceHost.Open()

                ' The service can now be accessed.
                Console.WriteLine("The dead letter service is ready.")
                Console.WriteLine("Press <ENTER> to terminate service.")
                Console.WriteLine()
                Console.ReadLine()

                ' Close the ServiceHostBase to shutdown the service.
                serviceHost.Close()
            End Using
        End Sub
    End Class
End Namespace

Berikut ini adalah kode untuk file konfigurasi layanan antrean surat mati.

Lihat juga