保证消息中间件的可靠性方式为:

  • 消息的持久性
  • 消息的事务
  • 消息的签收


1、消息的持久性


非持久化:当服务器宕机时,消息不存在

持久化:当服务器宕机,消息依然存在

队列 queue 模式下,只需要为消息 producer 设置持久化模式为持久化即可:

// 设置消息为持久化
MessageProducer messageProducer = session.createProducer(queue);
messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

当然,队列默认是开启持久化的,所以不进行设置也是可以的。

但主题的持久化就比较复杂一点,下面是主题模式的 Producer 代码示例:

public class JmsProducerTopicPersist {

    public static final String ACTIVEMQ_URL = "tcp://localhost:61616";
    public static final String TOPIC_NAME = "my-topic";

    @Test
    public void producer() throws JMSException {
        // 1、创建连接工厂,按照指定的URL地址,采用默认用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);

        // 2、连接连接工厂获得 connect 连接池并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();

        // 3、创建会话 session,第一个参数叫事务,第二个叫签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4、创建主题 topic 目的地
        Topic topic = session.createTopic(TOPIC_NAME);

        // 5、创建持久化的消息生产者
        MessageProducer messageProducer = session.createProducer(topic);
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

        // 设置完持久化后才可以开启
        connection.start();

        // 6、通过生产者生产消息到 MQ 的队列里面
        for (int i = 0; i < 6; i++) {
            // 7、创建指定格式的消息
            TextMessage textMessage = session.createTextMessage("message ====> " + i);
            // 8、通过 messageProducer 发送消息给 mq
            messageProducer.send(textMessage);
        }

        // 9、关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("======== 消息发布到 MQ 完成 ==========");
    }
}

从上面的代码可以看出,除了要设置为持久化模式外,还需要把 connection.start() 移到设置持久化后面。

下面是消费者示例代码:

public class JmsConsumerTopicPersist {

    public static final String ACTIVEMQ_URL = "tcp://localhost:61616";
    public static final String TOPIC_NAME = "my-topic";

    @Test
    public void consumer() throws JMSException, IOException {

        System.out.println(">>> 我是订阅者");

        // 创建连接工厂,按照指定的URL地址,采用默认用户名和密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);

        // 连接连接工厂获得 connect 连接池并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        // 设置客户ID
        connection.setClientID("wang");

        // 创建会话 session,第一个参数叫事务,第二个叫签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 创建主题 topic 目的地
        Topic topic = session.createTopic(TOPIC_NAME);

        // 创建订阅者
        TopicSubscriber subscriber = session.createDurableSubscriber(topic, "remark...");

        // 设置完持久化后才可以开启
        connection.start();

        // 接收消息
        Message message = subscriber.receive();
        while (null != message) {
            TextMessage textMessage = (TextMessage) message;
            System.out.println("收到持久化消息:" + textMessage.getText());
            message = subscriber.receive(1000L);
        }

        // 关闭资源
        session.close();
        connection.close();
    }
}

相比较生产者,消费者还需要为 connection 设置客户ID,同时还需要为会话 session 创建订持久化阅者。



2、事务


事务的好处相信大家都知道了,说白了就是我们经常说的 ACID。要开启事务,需要在创建 session 时把第一个参数设置为 true:

// 创建会话 session,设置第一个参数为 true 则开启事务
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

一旦开启事务,那么生产者发送消息到队列或者消费者从队列中消费消息后都需要进行事务提交,否则不生效(生产者不提交则无法发送成功,消费者不提交则会出现重复消费的问题):

// 开启事务后,发送消息和消费消息都需要提交事务
session.commit();



3、签收


签收模式为创建 session 的第二个参数,签收分为自动签收(Session.AUTO_ACKNOWLEDGE)和手动签收(Session.CLIENT_ACKNOWLEDGE),一旦设置为手动签收,则消费者在每消费一条消息后都需要消息的签收函数:

while (true){
    TextMessage textMessage = (TextMessage) messageConsumer.receive(3000L);
    if (textMessage != null) {
        System.out.println("消费者接收到的消息 ====> " + textMessage.getText());
        // 签收消息
        textMessage.acknowledge();
    } else {
        break;
    }
}

如果不签收的话,也会出现重复消费消息的现象。



事务和签收覆盖问题


如果同时设置事务和签收,那么,事务会覆盖签收的范围,也就是说,开启了事务后,如果不提交,哪怕对消息进行签收也会失效。所以一旦开启了事务,那么不管是否开启签收,都要进行事务提交。