複数のメッセージリスナーがトピックからの連続するメッセージを同時に処理するようにしたいことに注意してください。さらに、特定のメッセージリスナーで処理が失敗すると、そのリスナーのメッセージがトピックに残るように、各メッセージリスナーをトランザクションで操作する必要があります。
Spring DefaultMessageListenerContainerは、JMSキューの同時実行のみをサポートしているようです。
複数のDefaultMessageListenerContainerをインスタンス化する必要がありますか?
時間が縦軸に沿って流れる場合:
ListenerA reads msg 1 ListenerB reads msg 2 ListenerC reads msg 3
ListenerA reads msg 4 ListenerB reads msg 5 ListenerC reads msg 6
ListenerA reads msg 7 ListenerB reads msg 8 ListenerC reads msg 9
ListenerA reads msg 10 ListenerB reads msg 11 ListenerC reads msg 12
...
更新:
@ T.Robと@skaffmanのフィードバックに感謝します。
私がやったことは、concurrency=1
で複数のDefaultMessageListenerContainers
を作成し、メッセージリスナーにロジックを配置して、1つのスレッドだけが特定のメッセージIDを処理するようにすることです。
複数のDefaultMessageListenerContainer
インスタンスは必要ありませんが、 DefaultMessageListenerContainer
プロパティ を使用してconcurrentConsumers
を同時に構成する必要があります。
作成する同時コンシューマーの数を指定します。デフォルトは1です。
この設定に高い値を指定すると、実行時にスケジュールされた同時コンシューマーの標準レベルが増加します。これは、事実上、任意の時点でスケジュールされる同時コンシューマーの最小数です。これは静的な設定です。動的スケーリングの場合は、代わりに「maxConcurrentConsumers」設定を指定することを検討してください。
キューから着信するメッセージの消費量を増やすために、同時コンシューマーの数を増やすことをお勧めします。ただし、複数の消費者が登録されると、注文保証は失われることに注意してください。一般に、少量のキューの場合は1つのコンシューマーを使用します。
ただし、下部に大きな警告があります。
トピックの同時コンシューマーの数を上げないでください。これにより、同じメッセージが同時に消費されることになりますが、これはほとんど望ましくありません。
これは興味深いことであり、考えてみると理にかなっています。複数のDefaultMessageListenerContainer
インスタンスがある場合も同じことが起こります。
私が何を提案するかはわかりませんが、おそらくあなたはあなたのデザインを再考する必要があると思います。 pub/subメッセージを同時に消費することは完全に合理的なことのように思えますが、同じメッセージがすべてのコンシューマーに同時に配信されないようにするにはどうすればよいでしょうか。
少なくともActiveMQでは、必要なものが完全にサポートされています。彼の名前は VirtualTopic です。
コンセプトは次のとおりです。
VirtualTopic.
を使用してトピックを作成するだけです)。 VirtualTopic.Color
Consumer.<clientName>.VirtualTopic.<topicName>
に一致するこのVirtualTopicにサブスクライブするコンシューマーを作成します。 Consumer.client1.VirtualTopic.Color
、それを行うと、Activemqはその名前でキューを作成し、そのキューはVirtualTopic.Color
にサブスクライブし、すべてのメッセージが公開されますこの仮想トピックはclient1キューに配信されますが、rabbitmq交換のように機能することに注意してください。VirtualTopic.Color
に公開されたメッセージのコピーを受け取りますここにコードがあります
@Component
public class ColorReceiver {
private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class);
@Autowired
private JmsTemplate jmsTemplate;
// simply generating data to the topic
long id=0;
@Scheduled(fixedDelay = 500)
public void postMail() throws JMSException, IOException {
final Color colorName = new Color[]{Color.BLUE, Color.RED, Color.WHITE}[new Random().nextInt(3)];
final Color color = new Color(++id, colorName.getName());
final ActiveMQObjectMessage message = new ActiveMQObjectMessage();
message.setObject(color);
message.setProperty("color", color.getName());
LOGGER.info("status=color-post, color={}", color);
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.color"), message);
}
/**
* Listen all colors messages
*/
@JmsListener(
destination = "Consumer.client1.VirtualTopic.color", containerFactory = "colorContainer"
selector = "color <> 'RED'"
)
public void genericReceiveMessage(Color color) throws InterruptedException {
LOGGER.info("status=GEN-color-receiver, color={}", color);
}
/**
* Listen only red colors messages
*
* the destination ClientId have not necessary exists (it means that his name can be a fancy name), the unique requirement is that
* the containers clientId need to be different between each other
*/
@JmsListener(
// destination = "Consumer.redColorContainer.VirtualTopic.color",
destination = "Consumer.client1.VirtualTopic.color",
containerFactory = "redColorContainer", selector = "color='RED'"
)
public void receiveMessage(ObjectMessage message) throws InterruptedException, JMSException {
LOGGER.info("status=RED-color-receiver, color={}", message.getObject());
}
/**
* Listen all colors messages
*/
@JmsListener(
destination = "Consumer.client2.VirtualTopic.color", containerFactory = "colorContainer"
)
public void genericReceiveMessage2(Color color) throws InterruptedException {
LOGGER.info("status=GEN-color-receiver-2, color={}", color);
}
}
@SpringBootApplication
@EnableJms
@EnableScheduling
@Configuration
public class Config {
/**
* Each @JmsListener declaration need a different containerFactory because ActiveMQ requires different
* clientIds per consumer pool (as two @JmsListener above, or two application instances)
*
*/
@Bean
public JmsListenerContainerFactory<?> colorContainer(ActiveMQConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("1-5");
configurer.configure(factory, connectionFactory);
// container.setClientId("aId..."); lets spring generate a random ID
return factory;
}
@Bean
public JmsListenerContainerFactory<?> redColorContainer(ActiveMQConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
// necessary when post serializable objects (you can set it at application.properties)
connectionFactory.setTrustedPackages(Arrays.asList(Color.class.getPackage().getName()));
final DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setConcurrency("1-2");
configurer.configure(factory, connectionFactory);
return factory;
}
}
public class Color implements Serializable {
public static final Color WHITE = new Color("WHITE");
public static final Color BLUE = new Color("BLUE");
public static final Color RED = new Color("RED");
private String name;
private long id;
// CONSTRUCTORS, GETTERS AND SETTERS
}
可能性は次のとおりです。
1)受信メッセージを処理するためのBeanとメソッドで構成されたDMLCを1つだけ作成します。並行性を1に設定します。
2)必要な同時実行性に等しい#threadsを使用してタスクエグゼキュータを構成します。実際にメッセージを処理することになっているオブジェクトのオブジェクトプールを作成します。 #1で構成したBeanへのタスクエグゼキューターとオブジェクトプールの参照を提供します。オブジェクトプールは、実際のメッセージ処理Beanがスレッドセーフでない場合に役立ちます。
3)着信メッセージの場合、DMLCのBeanはカスタムRunnableを作成し、それをメッセージとオブジェクトプールにポイントして、タスクエグゼキュータに渡します。
4)Runnableのrunメソッドは、オブジェクトプールからBeanを取得し、指定されたメッセージを使用してその「process」メソッドを呼び出します。
#4は、プロキシとオブジェクトプールを使用して管理しやすくすることができます。
私はまだこの解決策を試していませんが、それは法案に合うようです。このソリューションはEJBMDBほど堅牢ではないことに注意してください。春(例: RuntimeExceptionをスローした場合、プールからオブジェクトを破棄しません。
これは、JMSの抽象化によってトランスポートプロバイダーの違いが膨らむ機会の1つです。 JMSは、トピックの各サブスクライバーにメッセージのコピーを提供したいと考えています。しかし、必要な動作は実際にはキューの動作です。説明されていないpub/subソリューションにこれを推進する他の要件があると思います。たとえば、他のものは、アプリとは関係なく同じトピックにサブスクライブする必要があります。
WebSphere MQでこれを行う場合の解決策は、管理サブスクリプションを作成することです。これにより、特定のトピックに関する各メッセージの単一のコピーがキューに配置されます。次に、複数のサブスクライバーがそのキューのメッセージをめぐって競合する可能性があります。このようにして、アプリにメッセージが配信される複数のスレッドを含めることができ、同時に、このアプリケーションから独立している他のサブスクライバーが同じトピックに動的に(非)サブスクライブできます。
残念ながら、これを行うための一般的なJMSポータブルな方法はありません。あなたは、トランスポートプロバイダーの実装に大きく依存しています。私が話すことができるのはWebSphereMQだけですが、創造性があれば、他のトランスポートが何らかの方法でさまざまな程度でこれをサポートしていると確信しています。
JMS 2.0では同じトピックサブスクリプションで複数のコンシューマーが許可されましたが、JMS1.1ではそうではありませんでした。参照してください: https://www.Oracle.com/technetwork/articles/Java/jms2messaging-1954190.html
server.xml構成:
したがって、maxSessionsでは、必要なセッションの数を特定できます。
私は同じ問題に遭遇しました。私は現在、RabbitMQを調査しています。これは、「ワークキュー」と呼ばれるデザインパターンで完璧なソリューションを提供しているようです。詳細はこちら: http://www.rabbitmq.com/tutorials/tutorial-two-Java.html
JMSに完全に縛られていない場合は、これを調べることができます。 JMSからAMQPへのブリッジもあるかもしれませんが、それはハッキーに見え始めるかもしれません。
MacにRabbitMQをインストールして実行するのは楽しいですが(読み:難しさ)、ほぼ機能していると思います。これを解決できたら、投稿します。
カスタムタスクエグゼキュータを作成すると、重複処理なしで問題が解決したようです。
@Configuration
class BeanConfig {
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskExecutor topicExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setAllowCoreThreadTimeOut(true);
executor.setKeepAliveSeconds(300);
executor.setCorePoolSize(4);
executor.setQueueCapacity(0);
executor.setThreadNamePrefix("TOPIC-");
return executor;
}
@Bean
JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer, @Qualifier("topicExecutor") Executor topicExecutor) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
factory.setSessionTransacted(false);
factory.setSubscriptionDurable(false);
factory.setTaskExecutor(topicExecutor);
return factory;
}
}
class MyBean {
@JmsListener(destination = "MYTOPIC", containerFactory = "topicListenerFactory", concurrency = "1")
public void receiveTopicMessage(SomeTopicMessage message) {}
}