Javaで非同期メッセージキューを動的に作成する
-
10-07-2019 - |
質問
非同期メッセージキューをJavaで動的に作成する必要があります。私のユースケースは、複数のSMTPサーバーを介してメールを送信することです。同じSMTPサーバーへのメールは順番に処理されるように強制する必要がありますが、異なるSMTPサーバーへのメールは同時に処理される場合があります。過去にJMSを使用しましたが、見ることができる限り、コンパイル時にキューを作成することしかできませんが、実行時にキューを作成する必要があります(SMTPサーバーごとに1つのキュー)。
JMSに関する何かが足りないのですか、それとも他のツール/提案がありますか?
解決
アダムに同意します。ユースケースはJMSのように聞こえますがオーバーヘッドです。十分なJava組み込み機能:
package de.mhaller;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import org.junit.Assert;
import org.junit.Test;
public class Mailer {
@Test
public void testMailer() throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
ArrayList<Mail> log = new ArrayList<Mail>();
LinkedBlockingDeque<Mail> incoming = new LinkedBlockingDeque<Mail>();
// TODO: Put mails to be sent into the incoming queue
incoming.offer(new Mail("foo1@localhost", "localhost"));
incoming.offer(new Mail("foo2@otherhost", "otherhost"));
incoming.offer(new Mail("foo3@otherhost", "otherhost"));
incoming.offer(new Mail("foo4@localhost", "localhost"));
Map<Mailserver, Queue<Mail>> queues = new HashMap<Mailserver, Queue<Mail>>();
while (!incoming.isEmpty()) {
Mail mail = incoming.pollFirst();
Mailserver mailserver = findMailserver(mail);
if (!queues.containsKey(mailserver)) {
ArrayDeque<Mail> serverQueue = new ArrayDeque<Mail>();
queues.put(mailserver, serverQueue);
executor.execute(new SendMail(mailserver, serverQueue));
}
Queue<Mail> slot = queues.get(mailserver);
slot.offer(mail);
}
assertMailSentWithCorrectServer(log);
}
private void assertMailSentWithCorrectServer(ArrayList<Mail> log) {
for (Mail mail : log) {
if (!mail.server.equals(mail.sentBy.mailserver)) {
Assert.fail("Mail sent by wrong server: " + mail);
}
}
}
private Mailserver findMailserver(Mail mail) {
// TODO: Your lookup logic which server to use
return new Mailserver(mail.server);
}
private static class Mail {
String recipient;
String server;
SendMail sentBy;
public Mail(String recipient, String server) {
this.recipient = recipient;
this.server = server;
}
@Override
public String toString() {
return "mail for " + recipient;
}
}
public static class SendMail implements Runnable {
private final Deque<Mail> queue;
private final Mailserver mailserver;
public SendMail(Mailserver mailserver, Deque<Mail> queue) {
this.mailserver = mailserver;
this.queue = queue;
}
@Override
public void run() {
while (!queue.isEmpty()) {
Mail mail = queue.pollFirst();
// TODO: Use SMTP to send the mail via mailserver
System.out.println(this + " sent " + mail + " via " + mailserver);
mail.sentBy = this;
}
}
}
public static class Mailserver {
String hostname;
public Mailserver(String hostname) {
this.hostname = hostname;
}
@Override
public String toString() {
return hostname;
}
@Override
public int hashCode() {
return hostname.hashCode();
}
@Override
public boolean equals(Object obj) {
return hostname.equals(((Mailserver) obj).hostname);
}
}
}
他のヒント
JMS自体の仕様は、この問題についてはかなり静かです。ほとんどの実装では、JMS自体ではなく、独自のAPIを使用してこれを行うことができます。ただし、MDBのような形式的なものを動的キューに接続することはできません。むしろ、独自の接続とリスナーを管理する必要があります。
WebSphere環境でこれを最後に見たとき、キューを動的に作成することは驚くほど困難/不可能でした(一時キューは一時的すぎると思います)。キューを作成するためのAPIは存在していましたが、アクティブにするにはサーバーを再起動する必要がありました。次に、MDBの問題があります。
使用可能なプリンターのセットが比較的小さいことを前提とした、余分なレベルの間接化によってすべての問題を解決できるという格言に基づく汚い回避策はどうですか。
Printer01からPrinter99までのキューを作成します(またはそれよりも小さい数)。 「データベース」を持っている;キューを実際のプリンターにマップします。プリンターへの要求が来ると、マッピングテーブルに追加できます。 MDBが使用されないキューを調べるオーバーヘッドがあるかもしれませんが、潜在的なプリンターの数が膨大でない限り、多分それを買う余裕がありますか?
SMTPサーバーごとにキューを作成し、キューコンシューマー(MDBまたはメッセージリスナー)を1に制限します
activemqでこれを実行しました-同様の懸念があり(当時のJMSドキュメントではこれはサポートされていないと記載されていました)、サポートされていることが保証されていたため、この時点で実際に質問を投稿しました。