Transaksi dan pengiriman ulang di JMS

Merancang dan mendesain aplikasi dengan Java Message Service (JMS) tidak hanya memerlukan pengetahuan tentang cara menggunakan JMS API, tetapi juga memiliki dasar yang kuat untuk konsepnya. Artikel ini berfokus pada dua konsep yang sangat kuat: transaksi dan pengiriman ulang . Dalam JMS, transaksi mengatur pesan atau grup pesan ke dalam unit pemrosesan atom; kegagalan untuk menyampaikan pesan dapat mengakibatkan pengiriman ulang pesan atau grup pesan tersebut.

Dalam artikel ini, saya membantu Anda mengembangkan pemahaman menyeluruh tentang opsi transaksi Anda dan menunjukkan bagaimana Anda dapat mengevaluasi dampaknya pada pengiriman ulang pesan. Saya berasumsi Anda sudah familiar dengan JMS API serta message-driven beans (MDBs).

Ringkasan opsi transaksi

Sebuah aplikasi memiliki banyak sekali pilihan transaksi yang tersedia, termasuk apakah ia ingin berpartisipasi dalam transaksi atau tidak. Jika aplikasi Anda tidak menggunakan transaksi, ia dapat menggunakan salah satu mode pengakuan berikut: otomatis, duplikat oke, dan klien. Anda menentukan mode pengakuan saat membuat sesi JMS. Jika aplikasi Anda menggunakan transaksi, ia dapat memilih dari opsi transaksi berikut: sesi yang ditransaksikan, MDB dengan demarkasi transaksi yang dikelola kontainer (CMTD), dan MDB dengan demarkasi transaksi yang dikelola kacang (BMTD). Daftar berikut menjelaskan secara singkat mode pengakuan dan opsi transaksi ini.

Opsi pengakuan:

  • Mode otomatis: Saat sesi menggunakan mode otomatis, pesan yang dikirim atau diterima dari sesi tersebut secara otomatis diakui. Ini adalah mode paling sederhana dan mengekspresikan kekuatan JMS dengan mengaktifkan jaminan pengiriman pesan hanya sekali.

  • Menduplikasi mode oke: Saat sesi menggunakan duplikat mode oke, pesan yang dikirim atau diterima dari sesi tersebut secara otomatis diakui seperti mode otomatis, meskipun dengan malas. Dalam situasi yang jarang terjadi, pesan mungkin dikirim lebih dari sekali. Mode ini mengaktifkan setidaknya satu jaminan pengiriman pesan.

  • Mode klien: Saat sesi menggunakan mode klien, pesan yang dikirim atau diterima dari sesi tersebut tidak diakui secara otomatis. Aplikasi harus mengakui tanda terima pesan. Mode ini memberi aplikasi (bukan penyedia JMS) kontrol penuh atas pengakuan pesan, dengan biaya kerumitan kode yang meningkat.

Jenis mode pengakuan lainnya dimungkinkan. Namun, mode pengakuan ini khusus untuk penyedia JMS, dan oleh karena itu, mengganggu portabilitas aplikasi JMS.

Opsi transaksi:

  • Sesi yang ditransaksikan : Aplikasi dapat berpartisipasi dalam transaksi dengan membuat sesi yang ditransaksikan (atau transaksi lokal). Aplikasi sepenuhnya mengontrol pengiriman pesan dengan melakukan atau memutar kembali sesi.

  • Kacang berbasis pesan dengan CMTD: MDB dapat berpartisipasi dalam transaksi kontainer dengan menentukan CMTD dalam deskriptor penyebaran XML. Transaksi dilakukan setelah pemrosesan pesan berhasil atau aplikasi dapat secara eksplisit memutarnya kembali.

  • Kacang berbasis pesan dengan BMTD: MDB dapat memilih untuk tidak berpartisipasi dalam transaksi kontainer dengan menentukan BMTD di deskriptor penyebaran XML. Programmer MDB harus merancang dan mengkodekan transaksi terprogram.

Gambar 1 menggambarkan pohon keputusan dari opsi transaksi yang disebutkan sebelumnya.

Sebelum mempelajari opsi transaksi secara detail, kita akan mempelajari proses pengiriman pesan.

Tahapan pengiriman pesan

Menjelang akhir pengiriman, pesan secara konseptual melewati tahapan berikut: pesan dengan penyedia JMS dan pesan dalam pemrosesan aplikasi.

Pesan dengan penyedia JMS

Dalam tahap ini, pesan tetap berada di penyedia JMS tepat sebelum penyedia mengirimkannya ke aplikasi. Pertimbangkan situasi bencana di mana penyedia JMS gagal. Apa yang terjadi pada pesan yang belum dikirim oleh penyedia ke klien? Akankah pesan-pesan itu hilang?

Nasib pesan tidak bergantung pada opsi transaksi yang diuraikan sebelumnya, melainkan pada mode pengiriman. Ada dua mode pengiriman: nonpersistent dan persistent . Pesan dengan mode pengiriman tidak persisten berpotensi hilang jika penyedia JMS gagal. Pesan dengan mode pengiriman persisten dicatat dan disimpan ke penyimpanan yang stabil. Penyedia JMS menyimpan pesan ini ke penyimpanan yang stabil, seperti database atau sistem file, dan akhirnya mengirimkannya ke aplikasi untuk diproses.

Pesan dalam pemrosesan aplikasi

Pada tahap ini, aplikasi menerima pesan dari penyedia JMS dan memprosesnya. Pertimbangkan kegagalan yang terjadi selama pemrosesan pesan. Apa yang terjadi dengan pesan tersebut? Apakah pesan akan hilang atau dikirim ulang untuk pemrosesan yang sukses nanti? Jawaban atas pertanyaan-pertanyaan ini bergantung pada opsi transaksi yang Anda pilih.

Gambar 2 menggambarkan dua tahap pemrosesan. Diagram menunjukkan bahwa pesan berpindah dari penyedia JMS ke pemrosesan aplikasi.

Sepanjang sisa artikel, saya menggunakan legenda aksi yang ditunjukkan pada Gambar 3 untuk mengilustrasikan opsi transaksi yang berbeda. Seperti yang ditunjukkan Gambar 3, panah yang terisi menggambarkan tindakan yang dilakukan penyedia JMS, sedangkan panah yang diuraikan menggambarkan tindakan yang dilakukan oleh aplikasi.

Pengaturan

To demonstrate the impact of various transaction options as well as redelivery, I will use one sender. The sender sends simple integers as object messages to a queue. Each transaction option has a different receiver. Each receiver demonstrates the impact of choosing a particular transaction option as well as highlights the impact on message redelivery. The sender and receivers utilize common administered objects: connection factory and queue. The connection factory is available using the Java Naming and Directory Interface (JNDI) name jms/QueueConnectionFactory, whereas the queue is available using the jms/Queue JNDI name.

Listing 1 shows the code for the sender:

Listing 1. Sender

package com.malani.examples.jms.transactions; import javax.naming.InitialContext; import javax.jms.*; public class Sender { public static void main(String[] args) { System.out.println("Starting..."); QueueConnectionFactory aQCF = null; QueueConnection aQC = null; QueueSession aQS = null; QueueSender aSender = null; try { InitialContext aIC = new InitialContext(Resource.getResources()); aQCF = (QueueConnectionFactory) aIC.lookup( iConstants.FACTORY_NAME ); aQC = aQCF.createQueueConnection(); aQS = aQC.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); Queue aQueue = (Queue) aIC.lookup(iConstants.QUEUE_NAME); aSender = aQS.createSender(aQueue); aQC.start(); for (int i = 0; i < 10; i++) { aSender.send(aQS.createObjectMessage(new Integer(i))); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (aSender != null) { aSender.close(); } if (aQS != null) { aQS.close(); } if (aQC != null) { aQC.stop(); aQC.close(); } } catch (JMSException e) { e.printStackTrace(); } } System.out.println("Ending..."); } } 

The following sections describe each acknowledgement mode in detail. A receiver demonstrates each acknowledgement mode. Each case uses the sender above to demonstrate the impact and implications of implementing a specific transaction option.

Auto acknowledgement

To implement the auto acknowledgement mode, when you create the receiver's session, specify false as the first argument and Session.AUTO_ACKNOWLEDGE as the second argument of the createSession() factory method. Specifying false creates a nontransacted session. The second parameter creates a session that automatically acknowledges messages. A message is automatically acknowledged when it successfully returns from the receive() method. If the receiver uses the MessageListener interface, the message is automatically acknowledged when it successfully returns from the onMessage() method. If a failure occurs while executing the receive() method or the onMessage() method, the message is automatically redelivered. The JMS provider carefully manages message redelivery and guarantees once-only delivery semantics.

Listing 2 describes the Receiver class. The Receiver is the AutoReceiver class's superclass. The Receiver superclass does most of the heavy lifting. It receives the object messages sent by the Sender class. In the processMessage() method, the receiver prints the message:

Listing 2. Receiver

package com.malani.examples.jms.transactions; import javax.jms.*; import javax.naming.InitialContext; import java.io.InputStreamReader; public abstract class Receiver { protected void doAll() { QueueConnectionFactory aQCF = null; QueueConnection aQC = null; QueueSession aQS = null; QueueReceiver aQR = null; try { InitialContext aIC = new InitialContext(Resource.getResources()); aQCF = (QueueConnectionFactory) aIC.lookup( iConstants.FACTORY_NAME ); aQC = aQCF.createQueueConnection(); aQS = createQueueSession(aQC); final QueueSession aQS1 = aQS; Queue aQueue = (Queue) aIC.lookup(iConstants.QUEUE_NAME); aQR = aQS.createReceiver(aQueue); MessageListener aML = new MessageListener() { public void onMessage(Message aMessage) { try { processMessage(aMessage, aQS1); } catch (JMSException e) { e.printStackTrace(); } } }; aQR.setMessageListener(aML); aQC.start(); InputStreamReader aISR = new InputStreamReader(System.in); char aAnswer = ' '; do { aAnswer = (char) aISR.read(); if ((aAnswer == 'r') || (aAnswer == 'R')) { aQS.recover(); } } while ((aAnswer != 'q') && (aAnswer != 'Q')); } catch (Exception e) { e.printStackTrace(); } finally { try { if (aQR != null) { aQR.close(); } if (aQS != null) { aQS.close(); } if (aQC != null) { aQC.stop(); aQC.close(); } } catch (JMSException e) { e.printStackTrace(); } } } protected void processMessage(Message aMessage, QueueSession aQS) throws JMSException { if (aMessage instanceof ObjectMessage) { ObjectMessage aOM = (ObjectMessage) aMessage; System.out.print(aOM.getObject() + " "); } } protected abstract QueueSession createQueueSession( QueueConnection aQC ) throws JMSException; } 

Listing 3 describes the AutoReceiver class. As shown, the AutoReceiver creates a nontransacted session that automatically acknowledges messages in the createQueueSession() method:

Listing 3. AutoReceiver

package com.malani.examples.jms.transactions; import javax.naming.InitialContext; import javax.jms.*; import java.io.InputStreamReader; public class AutoReceiver extends Receiver { public static void main(String[] args) { System.out.println("Starting..."); new AutoReceiver().doAll(); System.out.println("Ending..."); } protected QueueSession createQueueSession( QueueConnection aQC ) throws JMSException { return aQC.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); } } 

Executing Listing 3 produces the following output; type character q and press Return to end the program:

Starting... Java (TM) Message Service 1.0.2 Reference Implementation (build b14) 0 1 2 3 4 5 6 7 8 9 q Ending... 

In Figure 4, a message is automatically acknowledged after the application successfully processes it, which is after the message returns from the onMessage() method.

Duplicates okay acknowledgement

The duplicates okay acknowledgement mode closely resembles the auto acknowledgement mode. However, rather than pass Session.AUTO_ACKNOWLEDGE, you specify Session.DUPS_OK_ACKNOWLEDGE as the acknowledgement mode of createSession()'s second argument. With less overhead than auto mode, in duplicates okay mode, the JMS provider guarantees at-least-once message delivery. During failure recovery, certain messages are probably delivered more than once.

Kode 4 menjelaskan DuplicatesOkayReceiverkelas, yang memperluas Receiversuperclass tersebut. Seperti yang ditunjukkan, DuplicatesOkayReceivermembuat sesi nontransacted dengan duplikat mode pengakuan oke dalam createQueueSession()metode:

Kode 4. DuplicatesOkayReceiver