ActiveMQ入门(四)-消息的持久化订阅、应答模式(可靠性)

一、持久化订阅

前几篇博客中提到,消息的两种模式,点对点(queue)和发布订阅(topic),queue模式下,消息是会被持久化到磁盘,而topic模式下,消息会随着服务的停止而消失,但是某些场景下,我们想将topic消息也进行持久化,只需要进行如下改动。

生产者端:只需要将目的地创建成Topic模式即可,其他不用变    session.createTopic("xxxx");

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;

/**
 * topic模式下,消息的持久订阅-生产者
 */

public class TopicProducter {
	
	//用户名、密码、地址
	private static final String USERNAME = ActiveMQConnection.DEFAULT_USER;
	private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
	private static final String BROKER_URL = "tcp://localhost:61616";
	
	public static void main(String[] args) {
		Connection conn = null;
		try {
			ConnectionFactory factory = new ActiveMQConnectionFactory(USERNAME,PASSWORD,BROKER_URL);
			conn = factory.createConnection();
			conn.start();
			//不启用事务,自动应答
			Session session = conn.createSession(false,Session.AUTO_ACKNOWLEDGE);
			//创建目的地,这里如果想使用持久订阅,创建topic模式的目的地
			Destination destination = session.createTopic("persistent-topic");
			MessageProducer producer = session.createProducer(destination);


            //加上这个属性,即便创建持久订阅的Destination,也不会将消息持久化到磁盘
			//producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

			
			//发送消息
			for(int i=0;i<3;i++) {
				String msg = "序号:"+(i+1)+"  ,时间戳"+System.nanoTime();
				TextMessage message = session.createTextMessage(msg);
				producer.send(message);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(conn != null) {
				try {
					conn.close();
				} catch (JMSException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

消费者端:需要为连接指定一个ID,创建持久订阅的目的地和消费者

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
 * topic模式下,消息的持久订阅-消费者
 */
public class TopicConsumerOne {
	
	//用户名、密码、地址
	private static final String USERNAME = ActiveMQConnection.DEFAULT_USER;
	private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
	private static final String BROKER_URL = "tcp://localhost:61616";
	
	public static void main(String[] args) {
		
		try {
			ConnectionFactory factory = new ActiveMQConnectionFactory(USERNAME,PASSWORD,BROKER_URL);
			Connection conn = factory.createConnection();
			//需要一个ID
			conn.setClientID("TopicConsumerTwo");
			conn.start();
			//不启用事务,自动应答
			Session session = conn.createSession(false,Session.AUTO_ACKNOWLEDGE);
			//创建目的地,这里如果想使用持久订阅,创建topic模式的目的地
			Topic destination = session.createTopic("persistent-topic");
			//创建消费者-持久订阅的消费者
			TopicSubscriber topicSubscriber = session.createDurableSubscriber(destination, "any-name");
			topicSubscriber.setMessageListener(new MessageListener() {
				@Override
				public void onMessage(Message message) {
					try {
						String msg = ((TextMessage)message).getText();
						System.out.println("接收消息:"+msg);
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			});
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

注意:使用持久订阅模式,需要在启动生产者前,先启动一下消费者,这样,在ActiveMQ中先注册一个持久订阅的消费者,这样消息就会被持久化,即便服务停止,再次启动后,没有被消费的消息依然会被消费者消费。

springboot中的消息持久化订阅:

生产者端代码不用变,只发topic模式的消息,定义好目的地即可

消费者配置端需做如下配置:

 @Bean(name = "topicContainerFactory")
	    public DefaultJmsListenerContainerFactory topicClient(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer){
	        DefaultJmsListenerContainerFactory factory = defaultJmsListenerContainerFactoryTopic(connectionFactory,configurer);
	        factory.setClientId("10001");
	        return factory;
	    }

	   

	    public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactoryTopic(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
	        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
	        configurer.configure(factory,connectionFactory);
	        factory.setPubSubDomain(true);
	        factory.setSessionTransacted(true);
	        factory.setAutoStartup(true);
	        //开启持久化订阅
	        factory.setSubscriptionDurable(true);
	        return factory;
	    }

消费者端只需要和生产者定义的目的地保持一致,监听上面定义的topicContainerFactory即可

 @JmsListener(destination ="与生产者定义的保持一致",containerFactory="topicContainerFactory")
	    public void getMessage1(Message productMessage) throws JMSException {
	    	
	    	String msg = "接收消息:"+(((TextMessage)productMessage)).getText();
	    	System.out.println(msg);
	    	
	    }

二、消息的可靠性

消息由生产者创建,发送到消息队列,消费者去队列里取消息进行消费,假如在queue模式下,消费者处理消息时发生异常导致消息处理失败,那么这条消息没有被成功消费,其他消费者也不会去消费这条消息,那么这条消息就没有被成功消费,导致相对应的业务没有被处理,如果该业务极其重要务必要保证成功处理,那么消息可靠性就要提高,即生产者将消息发送到队列,消费者取消息进行消费,消费成功,这条消息被成功处理,消费失败则再次尝试消费,直到成功为止。这是我们想要的结果。

生产者发送queue模式:由于queue模式需要将消息持久化,生产者在发送的时候,需要等到消息被持久化后,才会确认消息发送成功。

生产者发送topic模式:由于topic不会将消息持久化(非持久化订阅模式)

以上两种都是在非事务模式下的讨论,现在为了保证消息可靠,需要启用事务模式。则将

Session session = conn.createSession(false,Session.AUTO_ACKNOWLEDGE);
改为
Session session = conn.createSession(true,Session.AUTO_ACKNOWLEDGE);

然后当消息发送完成,再commit()提交。所有消息都在事务内,直到执行提交代码,消息才算发送成功,这个过程是生产者到队列的可靠性,然后还有消费者去队列取消息并成功消费的可靠性。

消费者端保证可靠性需要创建session的第二个参数

Session session = conn.createSession(false,Session.AUTO_ACKNOWLEDGE);

第二个参数有四个选择:

Session session = conn.createSession(false,Session.AUTO_ACKNOWLEDGE);
Session session = conn.createSession(false,Session.CLIENT_ACKNOWLEDGE);
Session session = conn.createSession(false,Session.DUPS_OK_ACKNOWLEDGE);
Session session = conn.createSession(false,Session.SESSION_TRANSACTED);

Session.AUTO_ACKNOWLEDGE:自动应答,消费者收到消息并成功处理后,会通知生产者,
        如过在监听模式下出现异常,则不会通知生产者,这样生产者就认为消息没有被消费,就会重新发送(默认6次)。这是常用模式。

Session.CLIENT_ACKNOWLEDGE:客户端手动确认,在业务处理完成调用方法message.acknowledge();手动
        确认,这是会通知生产者消息成功消费。保证消费的可靠性上,常用的模式
        topicSubscriber.setMessageListener(new MessageListener() {
				@Override
				public void onMessage(Message message) {
					try {
						String msg = ((TextMessage)message).getText();
						System.out.println("接收消息:"+msg);
						//业务方法
						message.acknowledge();//手动确认
					} catch (JMSException e) {
						e.printStackTrace();
					}
				}
			});

Session.DUPS_OK_ACKNOWLEDGE:批量自动确认,类似第一种自动确认,但不是处理一条确认一条,内部采用
        某种方式,进行批量自动确认,效率高,但可能产生重复消费的问题(比如10条消息处理到第6条出现
        异常,则这10条消息会重新发送,前5条重复消费了。 )

Session.SESSION_TRANSACTED:事务模式,当创建session的第一个参数设置为true时,无论第二个参数写 
        的什么模式,都会被自动改成Session.SESSION_TRANSACTED这个模式 
  

总结:消息可靠性,如果想保证从生产者到消费者整个流程的可靠性,生产者要么使用事务消息,要么使用持久化消息,消费者这边可以使用手动确认,自动确认的话需要在监听模式下对业务代码用try()cache{}代码包裹,出现异常及时抛出,通知消费者,让消息重发

猜你喜欢

转载自blog.csdn.net/csdnbeyoung/article/details/91349544