是否可以使用 Spring Boot 和 JmsTemplate 在 ActiveMQ 主题上强制执行消息顺序?
Is it possible to enforce message order on ActiveMQ topics using Spring Boot and JmsTemplate?
在使用 Spring Boot、ActiveMQ 和 JmsTemplate 时,我注意到消息顺序似乎并不总是保持不变。在阅读 ActiveMQ 时,"Message Groups" 作为一种潜在的解决方案提供,用于在发送到主题时保留消息顺序。有没有办法用 JmsTemplate 做到这一点?
补充说明:我开始认为 JmsTemplate 很适合 "getting launched",但问题太多。
下面发布了示例代码和控制台输出...
@RestController
public class EmptyControllerSB {
@Autowired
MsgSender msgSender;
@RequestMapping(method = RequestMethod.GET, value = { "/v1/msgqueue" })
public String getAccount() {
msgSender.sendJmsMessageA();
msgSender.sendJmsMessageB();
return "Do nothing...successfully!";
}
}
@Component
public class MsgSender {
@Autowired
JmsTemplate jmsTemplate;
void sendJmsMessageA() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message A");
}
void sendJmsMessageB() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message B");
}
}
@Component
public class MsgReceiver {
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
private final String consumerTwo = "Consumer.myConsumer2.VirtualTopic.TEST-TOPIC";
@JmsListener(destination = consumerOne )
public void receiveMessage1(String strMessage) {
System.out.println("Received on #1a -> " + strMessage);
}
@JmsListener(destination = consumerOne )
public void receiveMessage2(String strMessage) {
System.out.println("Received on #1b -> " + strMessage);
}
@JmsListener(destination = consumerTwo )
public void receiveMessage3(String strMessage) {
System.out.println("Received on #2 -> " + strMessage);
}
}
这是控制台输出(注意第一个输出的顺序)...
\Intel\Intel(R) Management Engine Components\DAL;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\gnupg\bin;C:\Users\LesR\AppData\Local\Microsoft\WindowsApps;c:\Gradle\gradle-5.0\bin;;C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\bin;;.]
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 672 ms
2019-04-03 09:23:08.705 INFO 13936 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-03 09:23:08.845 INFO 13936 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-03 09:23:08.877 INFO 13936 --- [ main] mil.navy.msgqueue.MsgqueueApplication : Started MsgqueueApplication in 1.391 seconds (JVM running for 1.857)
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-04-03 09:23:14.952 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
Received on #2 -> message A
Received on #1a -> message B
Received on #1b -> message A
Received on #2 -> message B
<HIT DO-NOTHING ENDPOINT AGAIN>
Received on #1b -> message A
Received on #2 -> message A
Received on #1a -> message B
Received on #2 -> message B
BLUF - 添加“?consumer.exclusive=true”到 JmsListener 注释的目标声明中。
似乎解决方案并没有那么复杂,尤其是如果放弃 ActiveMQ 的 "message groups" 支持或 "exclusive consumers"。 "message groups" 的缺点是发送方必须事先了解消息消费者的潜在分区。如果生产者有这方面的知识,那么 "message groups" 是一个很好的解决方案,因为该解决方案在某种程度上独立于消费者。
但是,可以从消费者端实施类似的解决方案,方法是让消费者在队列中声明 "exclusive consumer"。虽然我在 JmsTemplate 实现中没有看到直接支持这一点的任何内容,但似乎 Spring 的 JmsTemplate 实现将队列名称传递给 ActiveMQ,然后 ActiveMQ "does the right thing" 并强制执行独占消费者行为。
所以...
更改以下...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
到...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";?consumer.exclusive=true
执行此操作后,只调用了两个声明的接收方法之一,并且在我的所有测试运行中都保持了消息顺序。
在使用 Spring Boot、ActiveMQ 和 JmsTemplate 时,我注意到消息顺序似乎并不总是保持不变。在阅读 ActiveMQ 时,"Message Groups" 作为一种潜在的解决方案提供,用于在发送到主题时保留消息顺序。有没有办法用 JmsTemplate 做到这一点?
补充说明:我开始认为 JmsTemplate 很适合 "getting launched",但问题太多。
下面发布了示例代码和控制台输出...
@RestController
public class EmptyControllerSB {
@Autowired
MsgSender msgSender;
@RequestMapping(method = RequestMethod.GET, value = { "/v1/msgqueue" })
public String getAccount() {
msgSender.sendJmsMessageA();
msgSender.sendJmsMessageB();
return "Do nothing...successfully!";
}
}
@Component
public class MsgSender {
@Autowired
JmsTemplate jmsTemplate;
void sendJmsMessageA() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message A");
}
void sendJmsMessageB() {
jmsTemplate.convertAndSend(new ActiveMQTopic("VirtualTopic.TEST-TOPIC"), "message B");
}
}
@Component
public class MsgReceiver {
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
private final String consumerTwo = "Consumer.myConsumer2.VirtualTopic.TEST-TOPIC";
@JmsListener(destination = consumerOne )
public void receiveMessage1(String strMessage) {
System.out.println("Received on #1a -> " + strMessage);
}
@JmsListener(destination = consumerOne )
public void receiveMessage2(String strMessage) {
System.out.println("Received on #1b -> " + strMessage);
}
@JmsListener(destination = consumerTwo )
public void receiveMessage3(String strMessage) {
System.out.println("Received on #2 -> " + strMessage);
}
}
这是控制台输出(注意第一个输出的顺序)...
\Intel\Intel(R) Management Engine Components\DAL;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\gnupg\bin;C:\Users\LesR\AppData\Local\Microsoft\WindowsApps;c:\Gradle\gradle-5.0\bin;;C:\Program Files\JetBrains\IntelliJ IDEA 2018.3\bin;;.]
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-04-03 09:23:08.408 INFO 13936 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 672 ms
2019-04-03 09:23:08.705 INFO 13936 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-04-03 09:23:08.845 INFO 13936 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-04-03 09:23:08.877 INFO 13936 --- [ main] mil.navy.msgqueue.MsgqueueApplication : Started MsgqueueApplication in 1.391 seconds (JVM running for 1.857)
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-04-03 09:23:14.949 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-04-03 09:23:14.952 INFO 13936 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 3 ms
Received on #2 -> message A
Received on #1a -> message B
Received on #1b -> message A
Received on #2 -> message B
<HIT DO-NOTHING ENDPOINT AGAIN>
Received on #1b -> message A
Received on #2 -> message A
Received on #1a -> message B
Received on #2 -> message B
BLUF - 添加“?consumer.exclusive=true”到 JmsListener 注释的目标声明中。
似乎解决方案并没有那么复杂,尤其是如果放弃 ActiveMQ 的 "message groups" 支持或 "exclusive consumers"。 "message groups" 的缺点是发送方必须事先了解消息消费者的潜在分区。如果生产者有这方面的知识,那么 "message groups" 是一个很好的解决方案,因为该解决方案在某种程度上独立于消费者。
但是,可以从消费者端实施类似的解决方案,方法是让消费者在队列中声明 "exclusive consumer"。虽然我在 JmsTemplate 实现中没有看到直接支持这一点的任何内容,但似乎 Spring 的 JmsTemplate 实现将队列名称传递给 ActiveMQ,然后 ActiveMQ "does the right thing" 并强制执行独占消费者行为。
所以...
更改以下...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";
到...
private final String consumerOne = "Consumer.myConsumer1.VirtualTopic.TEST-TOPIC";?consumer.exclusive=true
执行此操作后,只调用了两个声明的接收方法之一,并且在我的所有测试运行中都保持了消息顺序。