Java Messaging Service (JMS) API: Temel Mantık, Kurulum, İlk Mesaj ve Optimizasyon

Emrullah YILDIRIM
11 min readJun 24, 2020

--

Java Message Service API, Java Platform Enterprise Edition tabanlı uygulama bileşenlerinin ileti oluşturmasına, göndermesine, almasına ve okumasına izin veren bir mesajlaşma standartıdır. Gevşek bağlı (loosely coupled), güvenilir ve asenkronize dağıtılmış iletişim sağlar.

1 Temel Mantık

1.1 Neden Mesajlaşma?

Günümüzde, herkesin ve herşeyin birbiri ile haberleştiği ortamda, yazılım uygulamaları da iletişimini sürdürmek için mesaj gönderme ve alma özelliğine sahip olmalıdır. Mesajlaşma güvenilirlik, ölçeklenebilirlik, eşzamanlılık, esneklik ve çeviklik gibi birçok mimari zorluğu çözmektedir.

1.2 Mesajlaşma Domainleri

JMS, iki tip mesajlaşma modeline destek vermektedir. Bunlar poin-to-point ve publish-and-subscribe dır. Kısaca p2p ve pub/sub. Basit bir örnek ile bu modelleri açıklamak gerekirse; pub/sub model one-to-many broadcasting, p2p ise one-to-one mesajlaşma modelini uygulamaktadır.

JMS Mesajlaşma Domainleri

JMS açısından bakacak olursak; mesajlaşma istemcisi JMS client olarak isimlendirilirken, mesajlaşma sistemi ise JMS sağlayıcıdır(provider). Bir JMS uygulaması, birçok JMS istemcisi ve genellikle bir JMS sağlayıcısından oluşan bir sistemdir.

1.2.1 Point-to-Point Domain

Point-to-point mesajlaşma modeli, JMS istemcisinin mesajlarını Queue aracılığı ile senkron yada asenkron olarak göndermesine yada almasına izin verir. P2P mesajlaşma modelinin karakteristiği olarak, bir mesaj sadece bir consumer tarafından okunabilir.

Point-to-point mesajlaşma modeli aynı zamanda fire and forget asenkron iletişimi de destekler. Point-to-point model daha çok coupled davranmaya yatkındır çünkü mesajı gönderirken mesajın nasıl kullanılacağını ve kimin alacağını genellikle bilir.

Point-to-point mesajlaşma modeli ile birden çok receiver, tek bir Queue’yu birlikte dinleyebilir, yani load balancing yapabilirsiniz.

1.2.2 Publish-and-Subscribe Domain

Pub/sub modelinde ise mesajlar Queue yerine Topic adı verilen bir ortama yayınlanır. Mesaj üreticileri publisher ve mesaj tükecileri subscriber olarak adlandırılır. P2P nin aksine pub/sub modelde, bir mesaj tüm tüketiciler tarafından consume edilir. Bu yönteme message broadcasting adı da verilmektedir.

Pub/sub model, p2p modele göre daha decoupled davranmaktadır. Çünkü publisher, ne kadar subscriber olduğundan yada herbir subscriber’ın mesajla ne yapacağından habersizdir.

1.3 JMS API

JMS aslında bir mesajlaşma servisi değildir. Tıpkı JDBC gibi soyutlama katmanıdır. JDBC nasıl RDBS ile bağlantı kurmak için bir soyutlama sağlıyor ise JMS de ileti sağlayıcılarına erişimi soyutlar. JMS API 3 dala ayrılabilir;

  • General API
  • P2P API
  • Pub/Sub API

APIs are syntactically and semantically are similar.

General API 7 farklı bileşenden meydana gelmektedir.

  • ConnectionFactory
  • Destination
  • Connection
  • Session
  • Message
  • MessageProducer
  • MessageConsumer
JMS API Interfaces

JMS, JDBC’nin aksine Session objesinde transactional yapıyı tutar, connection bilgisini değil. Buda demektir ki; uygulama tek bir connection nesnesine sahiptir fakat connection üzerinden bir Session pool oluşturabilir.

2 Mesaj Yapısı

JMS spesifikasyonunda en önemli unsur mesaj yapısıdır. JMS uygulamasında tüm eventler birbiri ile haberleşmek için mesajları kullanılır. Geri kalan JMS yapısı sadece mesajın iletimini kolaylaştırmak için vardır.

Messages are lifeblood of the system

Mesaj objecti, 3 temel parçadan oluşmaktadır; header, message properties ve son olarak taşınan mesajın kendisi (payload).

JMS Mesajının anatomisi

2.1 Header

Mesaj header bölümü, en basit düzeyde mesajın kim, ne tarafından, ne zaman ve time-to-live gibi metadata ve aynı zamanda routing ve acknowledgment bilgilerini saklar. Her JMS mesajının bir dizi standart header bilgisi vardır. Bu headerlar iki grup altında toplanır.

  • Automatically Assigned Headers
  • Developer Assigned Headers
JMS Headers

Çoğu JMS Header aslında otomatik olarak değeri JMS provider tarafından belirlenir. Burada birkaç JMS Header başlıklarını inceleyelim.

2.1.1 JMSDeliveryMode

İki farklı JMSDeliveryMode vardır bunlar; persistent ve nonpersistent. Persistent mesaj asla kaybolmaz. Yani, JMS provider down olsa bile server yeniden ayağa kalktığında mesaj tekrar işlenecektir. Nonpersistent mesaj ise, eğer JMS provider down olur ise kaybolur. setJMSDeliveryMode() metodu ile mesajı üretirken, belirlediğiniz producer tarafından tüm iletilen mesajlar persistent yada nonpersistent olarak üretilecektir.

TopicPublisher topicPublisher = topicSession.createPublisher(topic);
topicPublisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

2.1.2 JMSExpiration

Mesaj nesnenin, consumera iletildikten bir süre zarfı boyunca geçerli olmasını istiyorsanız bu özelliği kullabilirsiniz. Producer tarafında, mesajın geçerlilik süresini milisaniye cinsinden belirtmekteyiz.

topicPublisher.setTimeToLive(48000);

Varsayılan olarak TTL süresi sıfırdır. Yani mesajın bir geçerlilik süresi yoktur.

2.1.3 JMSPriority

Ürettiğimiz mesaj nesnesine öncelik sıralaması atayarak, eğer istersek Queue’da önceliklendirme yapabiliriz. İki tip önceliklendirme kategorisi mevcuttur. 0–4 normal priority ve 5–9 expedited priority.

TopicPublisher topicPublisher = topicSession.createPublisher(topic);
topicPublisher.setPriority(9);

Şuana kadar bahsettiklerimiz header bilgileri Automatically Assigned Headers kategorisine giriyordu. Şimdi son olarak Developer Assigned Headers kategorisindeki JMSReployTo’dan da kısaca bahsedelim.

2.1.4 JMSReplyTO

Bu header bilgisi request/reply senaryolarında kullanılmaktadır.

message.setJMSReplyTo(topic);

Topic topic = (Topic) message.getJMSReplyTo();

2.2 Properties

Properties bilgisi aslında ikincil bir opsiyonel header bilgisi gibidir. Geliştiricinin ek olarak custom mesaj bilgisi eklemesine izin vermektedir. Properties bilgisi 3 farklı ana başlık altında toplanabilir. Bunlar;

  • Appilication-spesific properties
  • JMS-defined properties
  • Provider-specific properties

Header bilgisinde olduğu gibi bazı opsiyonel ve JMS tarafından sağlanan properties bilgileri bulunmaktadır. Konuyu çok fazla uzatmamak için tek tek bunları incelemeyceğim fakat yinede bilgi edinmek isteyenler için kaynaklar kısmında bulunan JMS Message Service: Creating Distributed Enterprise Application kitabına ulaşarak 3.bölümü okuyabilirler.

2.3 Payload

Mesajlar, taşıdıkları payloadın ihtiyacına göre farklılaşırlar. JMS bazı durumlarda çok faydalı olabileceklerinden ötürü birtakım legacy mesaj tiplerini de desteklemektedir. Bunlardan başlıcaları; text, bytes ve stream mesaj tipleridir. Diğer durumlar için ise, ortaya çıkan ihtiyaçları kolaylaştırmak adına bazı mesaj türleri tanımlanmıştır; Örneğin,
ObjectMessage serileştirilebilir Java nesnelerini taşıyabilir.

Gelin şimdi birlikte bazı çok kullanılan standart mesaj tiplerini inceliyelim.

2.3.1 Message

En basit mesaj türüdür. javax.jms.Message tipi base interface olarak diğer tüm mesaj çeşitleri tarafından implemente edilir. Payload taşımaz ve sadece notifikasyon için kullanılabilir.

// Create and deliver a Message 
Message message = session.createMessage( ); publisher.publish(message);
...
// Receive a message on the consumer
public void onMessage(Message message){
// No payload, process event notification
}

2.3.2 TextMessage

TextMessage ise payload olarak String ifade alır. String ve XML mesajlaşma için kullanışlı bir mesaj tipidir.

TextMessage textMessage = session.createTextMessage(); textMessage.setText("Hello!"); 
topicPublisher.publish(textMessage);
...
TextMessage textMessage = session.createTextMessage("Hello!"); queueSender.send(textMessage);

2.3.3 ObjectMessage

ObjectMessage, payload olarak serileştirilebilir bir Java nesnesi taşır. En çok kullanılan mesaj tiplerinden biridir. Payload sınıfının tanımı, hem JMS üreticisi hem de JMS tüketicisi içinde de olmalıdır. Örnek olarak producer Order tipinde bir nesneyi payload olarak gönderiyor ise consumerda da bu nesne tanımlı olmalıdır ki java.lang.ClassNotFoundException ile karşılaşmayalım.

public void onMessage(Message message) {  
try {
ObjectMessage objectMessage = (ObjectMessage)message;
Order order = (Order)objectMessage.getObject( );
...
catch (JMSException jmse){
...
}

Daha birçok farklı durum için birçok farklı mesaj tipleri bulunmaktadır. BytesMessage, StreamMessage, MapMessage ve Read-Only Message vb. Farklı enterprise ihtiyaçlarınız için birçok farklı mesaj tipleri JMS tarafından desteklenmektedir.

3 Guaranteed Messaging ve Acknowledgments

3.1 Guaranteed Messaging

Guaranteed messaging, bağlantısı kesilen tüketicileri yönetmek için oluşturulan bir mekanizmadan daha fazlasıdır. Mesajlaşma paradigmasının önemli bir parçasıdır ve dağıtılmış bir mesajlaşma sisteminin tasarımını anlamanın anahtarıdır.

JMS provider hatası, uygulama kodunun etki alanının dışındaki herhangi bir hata durumunu ifade eder. Bu, sağlayıcıya bir iletinin işlenmesi anında, beklenmeyen bir istisna, yazılım hatası, ağ hataları nedeniyle bir işlemde oluşan anomali veya bir donanım hatası anlamına gelebilir. Guaranteed messaging üç ana bölüme ayrılabilir: message autonomy, store-and-forward ve acknowledgment semantics.

Bir mesaj persistent olarak işaretlenmiş ise bu mesajı saklamak ve daha sonra kullanmak JMS provider sorumluluğu altındadır(store-and-forward). Depolama mekanizması, bir sağlayıcı hatası veya tüketen istemcinin hatası durumunda mesajın kurtarılabilmesini sağlamak için diske mesajların kalıcı olarak gönderilmesi için kullanılır. Depolama mekanizmasının uygulanması JMS sağlayıcısına bağlıdır. Mesajlar, merkezi olarak, her göndericide veya alan istemcide yerel olarak saklanabilir. Yönlendirme mekanizması(forwarding mechanism), mesajların depodan alınmasından ve ardından yönlendirilmesinden ve iletilmesinden sorumludur.

3.2 Message Acknowledgments

Message Acknowledgments garantili mesajlaşmanın anahtarıdır. Bu bölümde, onay protokolünün nasıl çalıştığı ve garantili mesajlaşmadaki rolü hakkında ayrıntılı bir açıklama yapacağız.

3.2.1 AUTO_ACKNOWLEDGE

AUTO_ACKNOWLEDGE modunu 2 farklı bakış açısı ile değerlendireceğiz.

— Producer Perspektif

Mesajımızı topic yada queue’ya gönderirken ki işlemler senkron olarak gerçekleşmektedir. TopicPublisher.publish( ) ve QueueSender.send( ) metotları mesajı gönderir ve ACK bilgisi gelene kadar işlemi bloklar. Mesaj ACK bilgisi geldiğinde thread geri kalan execution işlemlerine devam eder.

— Server Perspektif

Aşağıdaki akıştanda anlaşılacağı üzere eğer mesajınız persistent ise; producer tarafından mesaj iletildekten sonra ilk olarak diske mesaj yazılır ve daha sonra ACK onaylandı bilgisi gönderilir. Böylelikle mesaj kaybolmaz.

Persistent Mesaj 1. Adım

JMS server mesajı tüm subscriberlara teslim ettikten ve herbirinden onay aldıktan sonra, diskten kaldırır.

Persistent Mesaj 2. Adım

Eğer mesajınız nonpersistent olsaydı, 2. adımda mesaj diske yazılmak yerine direk ACK bilgisi gönderilecekti. Execution anında hata meydana geldiğinde veya JMS server down olduğunda ise mesajınız kaybolacaktı.

3.2.2 DUPS_OK_ACKNOWLEDGE

DUPS_OK_ACKNOWLEDGE dağıtım modu, yalnızca bir kez mesaj gönderimini sağlamak için gereken işlemlerin fazladan ek yüke neden olduğu ve provider düzeyinde mesajların performansını ve verimini engellediği varsayımına dayanmaktadır. Tekrarlı mesajlar almaya toleranslı bir uygulama, bu ek yükün oluşmasını önlemek için DUPS_OK_ACKNOWLEDGE modunu kullanabilir.

Pratikte, DUPS_OK_ACKNOWLEDGE’den kazandığınız performans artışı, JMS providera bağlı olarak önemsiz veya olmayabilir. Hatta bir JMS sağlayıcısının AUTO_ACKNOWLDEGE modunda daha iyi performans gösterebileceği düşünülebilir, çünkü ACK onaylarını daha sonra değil daha önce alacaktır.

3.2.3 Client Acknowledge

int count = 1000;  
try {
// Perform some business logic with the message
message.acknowledge( );
// Perform more business logic with the message ...
}
catch (javax.jms.JMSException jmse){
// Catch the exception thrown and undo the results
// of partial processing ...
}
}

Acknowledge() metodu, iletinin consumer tarafından başarıyla alındığını JMS sağlayıcısına bildirir. Bu yöntem, onaylama işlemi sırasında bir provider hatası oluşursa istemciye bir istisna atar. Sağlayıcı hatası şu şekilde sonuçlanır:
iletinin yeniden teslim edilmek üzere JMS sunucusu tarafından alıkonur. Bu nedenle, iletiyi yeniden almaya hazırlanırken kısmen işlenen herhangi bir iş mantığının sonuçlarını geri almalı veya yeniden iletilen iletinin yok sayılabilmesi için iletiyi işlenmiş olarak günlüğe kaydetmelidir. Acknowlege() metodu yalnızca CLIENT_ACKNOWLEDGE moduyla kullanılmalıdır; AUTO_ACKNOWLEDGE veya DUPS_OK_ACKNOWLEDGE modunda kullanılırsa, çağrı JMS sağlayıcısı tarafından yok sayılır.

4 Enterprise Java Bean ve Message Driven Bean

EJB, message driven bean için konteyner altyapısı sağlamanın yanı sıra, bir başka önemli avantaj daha sağlar: eşzamanlı işleme. Bir message driven bean bir JMS consumer olarak görev alır. Çalışma zamanında, EJB konteyneri beanlerin birçok örneğini başlatır ve bu örnekleri havuzda tutar. Message driven bean bir ileti aldığında, bu beanin bir örneği iletiyi işlemek için bir havuzdan seçilir. Aynı anda birden fazla mesaj gönderilirse, container her mesajı işlemek için farklı bir bean instance seçebilir; mesajlar aynı anda işlenebilir. Message driven bean, sağlam bir sunucu ortamında aynı anda iletileri tüketebildiğinden, çoğu geleneksel JMS istemcisinden çok daha yüksek verim ve daha iyi ölçeklenebilirlik kapasitesine sahiptir.

5 Kurulum ve İlk Mesaj

Bu kadar teorik bilgiden sonra gelgelelim öğrendiklerimizi uygulamaya. Bir demo ile öğrendiklerimizi uygulayacağız. Uygulamamızı Weblogic Server üzerine deploy edeceğiz. Daha önceki yazılarımda WLS kurulumunu ve artifactların deploymentini göstermiştim. Merak ederseniz şöyle bakabilirsiniz. Şimdi WLS üzerine JMS Queue ve Connection Factory kurulumunu adım adım anlatacağım.

WebLogic Server’da JMS Queue bir dizi ek kaynak ile ilişkilendirilir.

— JMS Module: Bir JMS Module queue ve topic gibi JMS kaynaklarını içeren bir tanımdır.

— Subdeployment: JMS modülleri bir veya daha fazla WebLogic instance’ında ya da cluster’da hedeflenebilir. JMS modülü içerisindeki queue ve topic gibi kaynakları da JMS Server ya da WebLogic instance’larında hedeflenebilir. Bir subdeployment hedeflerinin gruplanmasıdır. Advanced targeting olarak da bilinir.

— Connection Factory: JMS hedeflerine bağlantı oluşturmak için JMS clientlarına izin veren kaynak olarak adlandırılır.

  1. JMS Server Oluşturma Services > Messaging > JMS Servers
  2. New
  3. Name: TestJMSServer, Persistent Store: (none)
  4. Target : DefaultServer
  1. JMS Modül Oluşturma Services > Messaging > JMS Modules
  2. New
  3. Name : TestJMSModule
  4. Subdeployment Oluşturma Services > Messaging > JMS Modules
  5. TestJMSModule seç.
  6. Açılan ekrandan SubDeployments tabına tıkla ve daha sonra new butonuna tıkla.
  7. Subdeployment Name: TestSubdeployment
  8. Next butonuna tıkla.
  9. Target olarak TestJMSServer’ı seç.
  10. Finish butonuna tıkla.
  11. Connection Factory Oluşturma Services > Messaging > JMS Modules
  12. TestJMSModule seç and New butonuna tıkla
  13. Connection Factory’i seç and Next butonuna tıkla
  14. Name: TestConnectionFactory JNDI Name: jms/DefaultConnectionFactory Diğer değerleri default olarak bırak.
  15. Advanced Targeting butonuna tıkla ve TestSubdeployment seç
  16. JMS Queue Oluşturma Services > Messaging > JMS Modules
  17. TestJMSModule seç ve New butonuna tıkla
  18. Queue seç and Next’e tıkla
  19. Name: TestJMSQueueJNDI Name: jms/DefaultQueueName Template: None Next’e tıkla
  20. Subdeployments: TestSubdeployment

İşlemlerimiz bu kadar. Artık JNDI isimlerine verdiğimiz tanımlarımız ile kodtan bu yapılara erişebileceğiz.

5.1 İlk Mesaj

Serializable java nesnesini parametre alarak, tanımladığımız JMS Queue’ya yazan producer.

Ve kuyruğa her bir yeni kayıt geldiğinde yeni uyandıralacak bir consumer bean.

Aslında mesajlarımızı en basit hali ile gönderip almak bu kadar basit. Geri kalan tüm mesele ihtiyacı çözümleyip, buna en uygun mimariyi implemente etmekten ibaret.

Github: github.com/Sangaibisi/j2ee-jms-ejb-demo

7 Optimizasyon

Son bölümde, JMS sunucusunun en önemli özelliği olan performansının nasıl optimize edilebileceğini inceleyeceğiz. Aslında optimizasyon denilince akla bir sürü yapı gelmektedir. Bunlardan başlıcaları; bellekte saklanabilecek ileti sayısını sınırlamak, JMS sunucusu ileti kotalarını sınırlandırmak, disk belleği oluşturma, JMS sunucusunun yük altında ileti üretim hızını azaltma vb. gibi birçok parametre ile optimize bir JMS server elde edilebilir. Optimizasyon konusuna Weblogic müşterilerine ayrıca destek vermektedir. Çünkü bu derinlemesine uzmanlık gerektiren bir konudur.

Biz bu bölümde üç farklı optimizasyon metotlarına değineceğiz.

  • Flow Control
  • Concurent Messaging with Session Pools
  • Message-Driven Beans Tuning

7.1 Flow Control

Genel olarak üreticiler, mesajların tüketicilerin üstesinden gelebileceğinden çok daha hızlı gönderirler ve bazı durumlarda aşırı aktif üreticiler tüketicileri fail edebilir. Buda JMS sunucularının performansını düşürebilir ve durumu daha da kötüleştirebilir. WebLogic, iletilerin üretilme hızını azaltarak JMS sunucusunun veya hedefinin üreticileri kısıtlamasını sağlayan bir denetim özelliği sağlar. Kontrol mekanizması, JMS sunucusunun hedefi belirtilen bayt veya mesaj üst eşiğini aştığında devreye girer.

Peki Flow Control nasıl uygulanır?

Flow control denetiminin ayarlarına, connection factory üzerinden ulaşılabilir. Flow Mode Enabled seçeneği işaretlenerek, diğer min/max değerleriniz ile bu ayarı optimize edebilirsiniz.

7.2 Concurent Messaging with Session Pools

Session pool ile oluşturduğunuz sessionlarınızı JMS sunucusuyla ilişkilendirebilirsiniz; burada her session pool concurent bir işlemi ifade eder. weblogic.jms.extensions paketinin altındaki sınıfları ve arabirimleri içeren WebLogic JMS tarafından sağlanan genel API’yı kullanarak işlemleri programatik olarak gerçekleştirebilirsiniz.

Yukarıda verilen kod bloğu ile Queue’yu dinleyen 8 threadli bir pool oluşturmuş olduk.

7.3 Message-Driven Beans

MDB’ler, bir hedefe gelen iletileri eşzamanlı işlemek için etkin bir yol sağlar. Artifactın deploy anında WebLogic, MDB’nin EJB instancelarından bir bean havuzu oluşturur. Bir mesaj geldiğinde bir bean uyandırılır ve operasyonu devralır.

weblogic-ejb-jar.xml dosyası, EJB’lerin eşzamanlılığını, önbelleğe alınmasını, kümelenmesini, WebLogic Server kaynaklarını, güvenlik rolü adlarını, JDBC havuzlarını, JMS connection factory vb. diğer ayarları yönetebileceğiniz konfigürasyon dosyanızdır.

weblogic-ejb-jar.xml path

Bu dosya üzerinden konfigüre edebileceğiniz tüm xml tagları ve kaynaklar Oracle’ın şu resmi dökümanında listelenmiş.

Bir weblogic-ejb-jar.xml örneği

7.3.1 MDB Thread Management

MDB’lerde eşzamanlık, aynı anda etkin olabilecek MDB instanceları ile sağlanır. Çoğu durumda, mesaj varış hızı yüksek veya gecikme süresi gerekiyorsa, iş parçacıklarının MDB’ler için ayrılması mantıklıdır. İş parçacığı, minimum iş parçacığı kısıtlaması olan bir Work Manager belirterek ayarlanabilir.

max-threads-constraint değeri default olarak 16'dır.

Bu değeri arttırmak için yapmanız gereken Custom Work Manager tanımlamalısınız.

WLS Console üzerinden custom work manager tanımlama

Ve mutlu son. Sabrınız için teşekkür ederim, umarım faydalı bir yazı olmuştur. Geri bildirimleriniz benim kendimi geliştirmem içinde çok yararlı olacaktır, lütfen yorum yapmaktan çekinmeyin. Bir sonraki buluşmaya kadar esenlikle kalın. 👏🏻

Kaynaklar

Optimizasyon konusu için olan kaynakları ayrıca ayırmak istiyorum. Tek tek tüm oracle dökümanları ve bu konu hakkında yazılmış popüler yazılar ile ihtiyacınız olan veriyi elde edebileceğiniz birkaç link;

--

--

Emrullah YILDIRIM

Kendine Blogger. Yazarak öğrenen, yazdıklarini paylaşan Java Software Engineer.