在很多的業務場景下,如果有多個訂閱者,其不在線則會丟失生產者發出的消息,僅有在線狀態下方可接收到消息,那麼是否有方式可以實現離線訂閱消息的讀取呢,並保證可靠傳遞以及不重複消費呢,本章將通過持久訂閱者實現。
@Bean(name = { "jmsListenerContainerFactory4Topic" })
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory4Topic() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cachingConnectionFactory);
factory.setPubSubDomain(true);
if (this.transactionManager != null) {
factory.setTransactionManager(transactionManager);
} else {
factory.setSessionTransacted(Boolean.valueOf(true));
}
JmsProperties.Listener listener = jmsProperties.getListener();
factory.setAutoStartup(listener.isAutoStartup());
if (listener.getAcknowledgeMode() != null) {
factory.setSessionAcknowledgeMode(Integer.valueOf(listener.getAcknowledgeMode().getMode()));
}
String concurrency = listener.formatConcurrency();
if (concurrency != null)
factory.setConcurrency(concurrency);
// factory.setClientId("topicListener1");//無效,需要設置在ConnectionFactory
//實現持久訂閱
cachingConnectionFactory.setClientId("client_01");
factory.setSubscriptionDurable(true);
return factory;
}
2.2、添加訂閱者描述
@JmsListener(destination = "my.queue",containerFactory="jmsListenerContainerFactory4Queue",subscription="sub_01")//ActiveMQ.DLQ
public void receiveQueue(String text) throws Exception {
System.out.println("消費者:來源於生產者的消息:"+text);
this.jmsTopicMessagingTemplate.convertAndSend(this.topic, "生產者辛苦生產的發佈訂閱消息成果");
System.out.println("生產者2:辛苦生產發佈訂閱消息成果");
List<User> list=new ArrayList<User>(10);
list.add(new User("111","111"));
list.add(new User("12211","222"));
list.add(new User("333","333"));
for(User user:list){
userRepository.save(user);
}
// throw new Exception("出現異常啦");
}
3、啓動服務再次查看控制檯以及數據庫:
MQ控制檯
持久訂閱記錄表
4、此時我們啓動一個topic生產者,並不啓動消費者:
4.1、此時我們在消息記錄表中能夠看到生產了6個topic消息
4.2、上次監聽至503923
4.3、topic消息控制檯,已經被消費4個
5、此時我們啓動消費者監聽topic:
可以看到我們再次被消費了6個,累計被消費10個
本次消費最後一條記錄503929
總結:
a、通過實現可持久化訂閱者終於在數據庫表中存儲了兩種隊列信息,解開了之前爲何不存儲topic類型消息的原因;
b、如果存在多個持久化消費者,消息能夠可靠的被每一個消費者上線後消費,且不會重複消費,非常適合做廣播場景;
異常點:
1、此時在消息表中我們已經被消費的topic消息仍然存在,但過幾個小時後會自動刪除。
取消持久訂閱消費者
1、通過一段單元測試代碼實現:
@Test
public void testUnSub() throws JMSException{
jmsTopicTemplate.getConnectionFactory().createConnection().createSession(false, 1).unsubscribe("sub_01");
}
執行後沒有任何持久訂閱者
特別注意,sub_01爲之前@JmsListener(destination = "my.topic",containerFactory="jmsListenerContainerFactory4Topic",subscription="sub_01")屬性,我們需要取消監聽消費方可取消訂閱成功,否則將提示Durable
consumer is in use異常。
附錄
1、啓動服務遇到如下異常:
com.atomikos.icatch.HeurHazardException: Heuristic Exception
at com.atomikos.datasource.xa.XAResourceTransaction.commit(XAResourceTransaction.java:707)
at com.atomikos.icatch.imp.CommitMessage.send(CommitMessage.java:72)
at com.atomikos.icatch.imp.PropagationMessage.submit(PropagationMessage.java:83)
at com.atomikos.icatch.imp.Propagator$PropagatorThread.run(Propagator.java:79)
at com.atomikos.icatch.imp.Propagator.submitPropagationMessage(Propagator.java:58)
at com.atomikos.icatch.imp.HeurHazardStateHandler.onTimeout(HeurHazardStateHandler.java:131)
at com.atomikos.icatch.imp.CoordinatorImp.alarm(CoordinatorImp.java:933)
at com.atomikos.timing.PooledAlarmTimer.notifyListeners(PooledAlarmTimer.java:112)
at com.atomikos.timing.PooledAlarmTimer.run(PooledAlarmTimer.java:99)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
則刪除工程下tmlog.lck and tmlog6.log文件即可。