尚硅谷 ActiveMQ消息队列

系列文章目录



前言


1、ActiveMQ

1.1、什么是消息中间件

消息中间件适用于需要可靠的数据传送的分布式环境。采用消息中间件机制的系统中,不同的对象之间通过传递消息来激活对方的事件,完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在若干队列中,在合适的时候再将消息转发给接收者。消息中间件能在不同平台之间通信,它常被用来屏蔽掉各种平台及协议之间的特性,实现应用程序之间的协同,其优点在于能够在客户和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储转发,这也是它比远程过程调用RPC更进一步的原因。

1.2、为什么使用?

  • 业务解耦,代码解耦:例如,订单业务需要发送短信、推送app通知信息、扣除会员积分、发送email等,如果把这些全部写在订单业务代码中,订单代码将会变得十分臃肿,不利于修改维护,事物管理十分麻烦,使用中间件就不会有上述问题。
  • 同步变异步,加快业务响应时间,相对于RPC来说,异步通信使得生产者和消费者得以充分执行自己的逻辑而无需等待。
  • 流量消峰,消息存储堆积也是消息中间件的核心,可堆积大量的消息,当上游系统的吞吐能力远高于下游系统,在流量洪峰时可能会冲垮下游系统,消息中间件可以在峰值时堆积消息,而在峰值过去后下游系统慢慢消费消息解决流量洪峰的问题,典型的场景就是秒杀系统的设计。
  • 消息的顺序性,消息中间件采用的是队列技术,消息队列可以保证消息的先进先出,能保证消息顺序执行。
  • 消息的可靠性,消息中间件有消费确认机制(ACK),在收到成功被消费的确认消息之后才会把消息从队列中删除,并且消息中间件有本地刷盘存储功能。
    解决分布式事物复杂性。

1.3、安装使用(Linux)

1、下载:http://activemq.apache.org/
在这里插入图片描述

2、下载完成后解压到自定义文件夹中

	[root@ROOT opt]# tar -zxvf apache-activemq-5.15.13-bin.tar.gz

在这里插入图片描述
3、启动activemq
进入bin目录,启动activemq命令,其后端ip为:9247

[root@ROOT bin]# ./activemq start

在这里插入图片描述
进程查询:
ps -ef|grep activemq|grep -v grep
在这里插入图片描述

4、访问前台管理页面
地址:http://ip:8161/admin/
用户名:admin / admin
进入conf目录查看配置文件
注意:关闭windows、linux系统防火墙

在这里插入图片描述

2、HelloWorld

2.1、queue

队列模式,消费者跟生产者为一对一,生产一条消息,消费者就会消费一条消息。不存在消息丢失。
在这里插入图片描述
2.1.1、消息生产

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * mq启动demo
 * */
public class JmsProducer {
    //activemq 地址
    public static final String DEFAULT_BROKER_URL = "tcp://192.168.198.222:61616";

    //队列名称
    public static final String QUEUE_NAME = "QUEUE1";

    public static void main(String[] args) throws JMSException {
        //1、获取工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
        //2、建立连接
        Connection connection = factory.createConnection();
        //启动连接
        connection.start();
        //3、创建会话 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        //4、创建目的地(这里有两种:队列、主题,这里用主题)
        Queue queue = session.createQueue(QUEUE_NAME);

        //5、创建消息的生产者
        MessageProducer producer = session.createProducer(queue);

        //6、通过MessageProducer生产3条消息
        for (int i = 0; i < 3 ; i++) {
            //7、创建消息
            TextMessage message = session.createTextMessage("message--" + i);
            //8、通过MessageProducer发布消息
            producer.send(message);
        }

        //9、关闭资源
        producer.close();
        session.close();
        connection.close();
        System.out.println("*** 消息发送完成 ***");
    }
}

2.1.2、消息消费

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**

  • mq启动demo

  • */
    public class JmsConsumer {

    //activemq 地址
    public static final String DEFAULT_BROKER_URL = “tcp://192.168.198.222:61616”;

    //队列名称
    public static final String QUEUE_NAME = “QUEUE1”;

    public static void main(String[] args) throws JMSException, IOException {

     //1、获取工厂
     ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
     //2、建立连接
     Connection connection = factory.createConnection();
     //启动连接
     connection.start();
     //3、创建会话 两个参数,第一个事务, 第二个签收
     Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    
     //4、创建目的地(这里有两种:队列、主题,这里用主题)
     Queue queue = session.createQueue(QUEUE_NAME);
    
     //5、创建消息的消费者
     MessageConsumer consumer = session.createConsumer(queue);
    
     //6、消费消息
     //   (1) 使用receive方法获取消息
    /*
     while (true)
     {
         //receive在没有接受到消息的时候会一致阻塞:同步阻塞
         TextMessage message = (TextMessage) consumer.receive();
         if (message != null)
         {
             System.out.println("获取到消息:" + message.getText());
         }
         else
         {
             break;
         }
     }*/
     //   (2) 通过异步非阻塞的方式来监听消息
     //       订阅者通过注册一个消息监听器,当消息到底之后,系统自动调用MessageListener.onMessage方法
     consumer.setMessageListener(new MessageListener() {
         @Override
         public void onMessage(Message message) {
             if (null != message || message instanceof TextMessage)
             {
                 TextMessage textMessage = (TextMessage) message;
                 try {
                     System.out.println("MessageListener:" + textMessage.getText());
                 } catch (JMSException e) {
                     e.printStackTrace();
                 }
             }
         }
     });
    
     System.in.read();
    
     //9、关闭资源
     consumer.close();
     session.close();
     connection.close();
    

    }
    }

2.2、topic
该模式为发布\订阅,生产者发布消息,消费者订阅消息,订阅者可为多个。消费者无法接收在订阅生产者之前发布的消息,因此消息会丢失。
在这里插入图片描述
2.2.1、消息消费

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**
 * mq启动demo
 * */
public class JmsConsumer_topic {

    //activemq 地址
    public static final String DEFAULT_BROKER_URL = "tcp://192.168.198.222:61616";

    //队列名称
    public static final String TOPIC_NAME = "TOPIC1";

    public static void main(String[] args) throws JMSException, IOException {

        //1、获取工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
        //2、建立连接
        Connection connection = factory.createConnection();
        //启动连接
        connection.start();
        //3、创建会话 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        //4、创建目的地(这里有两种:队列、主题,这里用主题)
        Topic topic = session.createTopic(TOPIC_NAME);

        //5、创建消息的消费者
        MessageConsumer consumer = session.createConsumer(topic);

        //6、消费消息
        //   (1) 使用receive方法获取消息
       /*
        while (true)
        {
            //receive在没有接受到消息的时候会一致阻塞:同步阻塞
            TextMessage message = (TextMessage) consumer.receive();
            if (message != null)
            {
                System.out.println("获取到消息:" + message.getText());
            }
            else
            {
                break;
            }
        }*/
        //   (2) 通过异步非阻塞的方式来监听消息
        //       订阅者通过注册一个消息监听器,当消息到底之后,系统自动调用MessageListener.onMessage方法
        consumer.setMessageListener((message)-> {
            if (null != message || message instanceof TextMessage)
            {
                TextMessage textMessage = (TextMessage) message;
                try {
                    System.out.println("MessageListener:" + textMessage.getText());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });

        System.in.read();

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

    }
}

2.2.2、消息生产

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * mq启动demo
 * */
public class JmsProducer_topic {
    //activemq 地址
    public static final String DEFAULT_BROKER_URL = "tcp://192.168.198.222:61616";

    //队列名称
    public static final String TOPIC_NAME = "TOPIC1";


    public static void main(String[] args) throws JMSException {
        //1、获取工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
        //2、建立连接
        Connection connection = factory.createConnection();
        //启动连接
        connection.start();
        //3、创建会话 两个参数,第一个事务, 第二个签收
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        //4、创建目的地(这里有两种:队列、主题,这里用主题)
        Topic topic = session.createTopic(TOPIC_NAME);

        //5、创建消息的生产者
        MessageProducer producer = session.createProducer(topic);

        //6、通过MessageProducer生产3条消息
        for (int i = 0; i < 3 ; i++) {
            //7、创建消息
            TextMessage message = session.createTextMessage("message--" + i);
            //8、通过MessageProducer发布消息
            producer.send(message);
        }

        //9、关闭资源
        producer.close();
        session.close();
        connection.close();
        System.out.println("*** 消息发送完成 ***");
    }
}

2.3、总结

  1. 开发步骤
    在这里插入图片描述
    在这里插入图片描述
    2.两种消费方式
    同步阻塞方式:订阅者调用MessageConsumer的receive()方法来接收消息,
    receive方法能在接收到消息之前(或超时之前)将一直阻塞。
while (true)
        {
            //receive在没有接受到消息的时候会一致阻塞:同步阻塞
            TextMessage message = (TextMessage) consumer.receive();
            if (message != null)
            {
                System.out.println("获取到消息:" + message.getText());
            }
            else
            {
                break;
            }
        }
        

异步非阻塞方式:订阅者通过调用MessageConsumer的SetMessageListener (MessageListener listener)注册一个监听器,当消息到达之后,系统将自动调用监听器MessageListener的onMessage方法。

  consumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if (null != message || message instanceof TextMessage)
                {
                    TextMessage textMessage = (TextMessage) message;
                    try {
                        System.out.println("MessageListener:" + textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

3、两种模式的对比
在这里插入图片描述

3、Jms(Java Message Server)

3.1、什么是jms

在这里插入图片描述

3.2、消息中间件产品对比mq
在这里插入图片描述

3.3、jms组成结构和消息特点
3.3.1、组成结构
jms provider:实现jms接口和规范的消息中间件,也就是MQ服务器
jms producer:消息生产者,创建和发送jms消息的客户端
jms consumer:消息消费者,接受和消费jms消息的客户端
jms message:jms 消息,由消息头,消息属性和消息体组成
3.3.2、Message
3.3.2.1 消息头
JMSDestination:消息目的地
JMSDeliveryModle:消息投递模式,是持久还是非持久
JMSExpiration:消息过期时间,默认为0,永不过期
JMSPriority:消息优先级(0-9),默认为4,4以上为加急消息,4以下为普通消息
JMSMessageID:消息唯一标识
3.3.2.2、消息体

  • 封装消息的具体数据
  • 5种消息体格式
    在这里插入图片描述
  • 发送和接受消息体的类型必须一致

3.3.2.3、消息属性
如果需要除消息头以外的值,那么就可以使用消息属性。通常用作识别、去重、重点标识作用
在这里插入图片描述

3.4、消息的可靠性

3.4.1 消息的持久性(persistent)
在这里插入图片描述

  • 持久的Queue: 默认是持久化
  • 持久的topic

3.4.1 .1 topic 持久化代码
消费者

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;
import java.io.IOException;

/**
 * topic 消费者持久化topicSubscriber
 * */
public class JmsConsumer_topic_persistent {

    //activemq 地址
    public static final String DEFAULT_BROKER_URL = "tcp://192.168.198.222:61616";

    //队列名称
    public static final String TOPIC_NAME = "topicSubscriber";

    public static void main(String[] args) throws JMSException, IOException {

        //1、获取工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
        //2、建立连接
        Connection connection = factory.createConnection();
        connection.setClientID("z1");

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

        //4、创建目的地(这里有两种:队列、主题,这里用主题)
        Topic topic = session.createTopic(TOPIC_NAME);
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic, "remark...");

        //5、启动
        connection.start();

        //6、接受消息
        topicSubscriber.setMessageListener(message ->
        {
            TextMessage textMessage = (TextMessage) message;
            try {
                System.out.println("接收到持久化消息。。" + textMessage.getText());
            } catch (JMSException e) {
                e.printStackTrace();
            }
        });
        System.in.read();
        topicSubscriber.close();
        session.close();
        connection.close();
    }
}

生产者

package com.moon.study;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * mq启动demo
 * */
public class JmsProducer_topic_persistent {
    //activemq 地址
    public static final String DEFAULT_BROKER_URL = "tcp://192.168.198.222:61616";

    //队列名称
    public static final String TOPIC_NAME = "topicSubscriber";


    public static void main(String[] args) throws JMSException {
        //1、获取工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(DEFAULT_BROKER_URL);
        //2、建立连接
        Connection connection = factory.createConnection();

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

        //4、创建目的地(这里有两种:队列、主题,这里用主题)
        Topic topic = session.createTopic(TOPIC_NAME);

        //5、创建消息的生产者
        MessageProducer producer = session.createProducer(topic);
        producer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //启动连接
        connection.start();
        //6、通过MessageProducer生产3条消息
        for (int i = 0; i < 3 ; i++) {
            //7、创建消息
            TextMessage message = session.createTextMessage("topicSubscriberMessage--" + i);
            //8、通过MessageProducer发布消息
            producer.send(message);
        }

        //9、关闭资源
        producer.close();
        session.close();
        connection.close();
        System.out.println("*** 消息发送完成 ***");
    }
}

在这里插入图片描述

3.4.2、消息的事务
createSession的第一个参数为true 为开启事务,开启事务之后必须在将消息提交,才可以在队列中看到消息

Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

提交:
session.commit();

事务开启的意义在于,如果对于多条必须同批次传输的消息,可以使用事务,如果一条传输失败,可以将事务回滚,再次传输,保证数据的完整性。
对于消息消费者来说,开启事务的话,可以避免消息被多次消费,以及后台和服务器数据的不一致性。举个栗子:
如果消息消费的 createSession 设置为 ture ,但是没有 commit ,此时就会造成非常严重的后果,那就是在后台看来消息已经被消费,但是对于服务器来说并没有接收到消息被消费,此时就有可能被多次消费。

3.4.3、消息签收Acknowledge
非事务
Session.AUTO_ACKNOWLEDGE 自动签收,默认
Session.CLIENT_ACKNOWLEDGE 手动签收
手动签收需要acknowledge
textMessage.acknowledge();

而对于开启事务时,设置手动签收和自动签收没有多大的意义,都默认自动签收,也就是说事务的优先级更高一些。

	Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
	//Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE);   //  也是自动签收
	session.commit();  

但是开启事务没有commit 任就会重复消费

4、broker
broker 就是实现了用代码形式启动 ActiveMQ 将 MQ 内嵌到 Java 代码中,可以随时启动,节省资源,提高了可靠性。
就是将 MQ 服务器作为了 Java 对象

使用多个配置文件启动 activemq

cp activemq.xml  activemq02.xml 
// 以active02 启动mq 服务器
./activemq start xbean:file:/myactivemq/apache-activemq-5.15.9/conf/activemq02.xml 

把小型 activemq 服务器嵌入到 java 代码: 不在使用linux 的服务器
需要的包:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>

代码实现:

public class Embebroker {
    public static void main(String[] args) throws Exception {
        // broker 服务
        BrokerService brokerService = new BrokerService();
        // 把小型 activemq 服务器嵌入到 java 代码
        brokerService.setUseJmx(true);
        // 原本的是 192.……  是linux 上的服务器,而这里是本地windows 的小型mq 服务器
        brokerService.addConnector("tcp://localhost:61616");
        brokerService.start();
    }
}

5、Spring整合activeMQ

5.1、队列(Queue)

maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.moon.study</groupId>
  <artifactId>mq-spring</artifactId>
  <version>1.0-SNAPSHOT</version>
  <name>mq-spring</name>

  <properties>
    <spring.version>4.3.7.RELEASE</spring.version>
  </properties>

  <dependencies>
    <!--  activeMQ  jms 的支持  -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>4.3.23.RELEASE</version>
    </dependency>

    <!--  pool 池化包相关的支持  -->
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-pool</artifactId>
      <version>5.15.9</version>
    </dependency>

    <!-- Spring依赖 -->
    <!-- 1.Spring核心依赖 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- spring 上下文-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring.version}</version>
    </dependency>

    <!-- 日志相关依赖 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.16</version>
    </dependency>
  </dependencies>
</project>

spring application.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/mvc">

    <context:component-scan base-package="com.moon.study"/>

    <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory"  destroy-method="stop">
        <property name="connectionFactory">
            <bean class="org.apache.activemq.ActiveMQConnectionFactory">
                <property name="brokerURL" value="tcp://192.168.198.222:61616"></property>
            </bean>
        </property>
        <property name="maxConnections" value="100"></property>
    </bean>

    <!-- 队列目的地 -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg index="0" value="spring-active-queue"></constructor-arg>
    </bean>


    <!--  jms 的工具类 -->
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="defaultDestination" ref="destinationQueue"/>
        <property name="messageConverter">
            <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
        </property>
    </bean>
</beans>

5.1.1、生产者

package com.moon.study;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

@Service
public class SpringMQ_Producer {

    @Autowired
    JmsTemplate jmsTemplate;

    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        SpringMQ_Producer springMQ_producer = applicationContext.getBean(SpringMQ_Producer.class);
        springMQ_producer.producerMessage("SpringMQ_Producer:发送消息。。。。");
        System.out.println("发送成功。。");
    }

    private void producerMessage (String messageText)
    {
        jmsTemplate.send((message) -> {
            return message.createTextMessage(messageText);
        });
    }
}

5.1.2、消费者

package com.moon.study;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

@Service
public class SpringMQ_Consumer {

    @Autowired
    JmsTemplate jmsTemplate;

    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        SpringMQ_Consumer springMQ_consumer = applicationContext.getBean(SpringMQ_Consumer.class);
        String message = springMQ_consumer.consumerMessage();
        System.out.println("接受消息:" + message);
    }

    private String consumerMessage ()
    {
        return (String) jmsTemplate.receiveAndConvert();
    }
}
  • 异步非阻塞监听
    配置监听程序: DefaultMessageListenerContainer可实异步非阻塞监听,当启动生产者生产消息时,程序自动消费消息。
<!--配置监听程序-->
    <bean id="defaultMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="jmsFactory"/>
        <property name="destination" ref="destinationTopic"/>
        <!--消息监听实体-->
        <property name="messageListener" ref="topicMessageListener"/>
    </bean>

bean topicMessageListener:获取消息进行操作

package com.moon.study;

import org.springframework.stereotype.Component;

import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@Component
public class TopicMessageListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        if (null != message && message instanceof TextMessage)
        {
            TextMessage textMessage = (TextMessage) message;
            System.out.println(textMessage);
        }
    }
}

5.2、主题

topic与queue的代码差距极小,只需将队列目的地由queue换成topic

<!-- 队列目的地 -->
    <bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQTempTopic">
        <constructor-arg index="0" value="spring-active-topic"></constructor-arg>
    </bean>

6、SpringBoot整合activeMQ

6.1、队列
maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.moon.study</groupId>
    <artifactId>mq-boot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mq-boot</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-activemq</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

server:
  port: 8080

## activemq配置
spring:
  activemq:
    broker-url: tcp://192.168.198.222:61616
    user: admin
    password: admin
  jms:
    pub-sub-domain: false # 是否topic, false:queue/true:topic

#自己定义队列名称
myqueue: boot-activemq-queue

6.1.1、生产者
配置类

package com.moon.study.mqboot.config;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.stereotype.Component;

@Component
@EnableJms
public class ActiveMQConfig {

    //队列名称
    @Value("${myqueue}")
    private String myQueue;

    @Bean
    public ActiveMQQueue getActiveMQQueue()
    {
        return new ActiveMQQueue(myQueue);
    }
}

生产消息服务类

package com.moon.study.mqboot.active;

import org.apache.activemq.command.ActiveMQQueue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
public class BootProducer {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Autowired
    private ActiveMQQueue activeMQQueue;

    public void sendMessage ()
    {
        jmsMessagingTemplate.convertAndSend(activeMQQueue,  "-----boot-activemq-producer-message----");
    }
}

6.1.2、定时推送消息
定时任务

 /**
     * 定时生产消息
     * */
    @Scheduled(fixedDelay = 3000l)
    public void sendMessageSchedule()
    {
        jmsMessagingTemplate.convertAndSend(activeMQQueue,  "-----boot-activemq-producer-message----");
        System.out.println("sendMessageSchedule----已推送");
    }

启动类添加@EnableScheduling开关注解

@SpringBootApplication
@EnableScheduling
public class MqBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MqBootApplication.class, args);
    }
}

6.1.2、消费者

  • maven依赖与yml配置与生产者相同,注意避免与生产者server.port重复
  • 消费代码
package com.moon.study.mqbootcomsumer.active;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.TextMessage;

@Service
public class QueueConsumer {

    @JmsListener(destination = "${myqueue}")
    public void receiveMessage (TextMessage textMessage) throws JMSException
    {
        System.out.println("------接受消息:" + textMessage.getText());
    }

}

6.2、主题

6.2.1、生产者
代码与队列基本相同,主要区别是目的地改为队列,yml配置 jms:pub-sub-domain改为true

package com.moon.study.mqbootproducer.config;

import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.jms.Queue;
import javax.jms.Topic;

@Configuration
public class ActiveMQConfig {

    @Value("${mytopic}")
    private String mytopic;

    @Bean
    public Topic getTopic()
    {
        return new ActiveMQTopic(mytopic);
    }
}

6.2.2、消费者
代码与队列基本相同,主要区别是目的地改为主题,yml配置 jms:pub-sub-domain改为true

package com.moon.study.mqbootcomsumer.active;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;

import javax.jms.JMSException;
import javax.jms.TextMessage;

@Service
public class TopicConsumer {

    @JmsListener(destination = "${mytopic}")
    public void receiveMessage (TextMessage textMessage) throws JMSException
    {
        System.out.println("------mytopic接受消息:" + textMessage.getText());
    }

}

7、协议

7.1、 ActiveMQ支持的协议

支持的协议有 TCP 、 UDP、NIO、SSL、HTTP(S) 、VM 这是activemq 的activemq.xml 中配置文件设置协议的地方

 <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon    nections=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect    ions=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn    ections=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect    ions=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection    s=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>

默认是使用 openwire 也就是 tcp 连接 默认的Broker 配置,TCP 的Client 监听端口 61616 ,在网络上传输数据,必须序列化数据,消息是通过一个 write protocol 来序列化为字节流。默认情况 ActiveMQ 会把 wire protocol 叫做 Open Wire ,它的目的是促使网络上的效率和数据快速交互 。

使用tcp 的一些优化方案:tcp://hostname:port?key=value
它的参数详情参考:http://activemq.apache.org/tcp-transport-reference

7.2、协议

  • NIO 协议为ActiveMQ 提供更好的性能
  • 适合NIO 使用的场景:
    nio://hostname:port?key=value

各种协议对比 : http://activemq.apache.org/configuring-version-5-transports.html

修改 activemq.xml 使之支持 NIO 协议:

 <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumCon    nections=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnect    ions=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConn    ections=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnect    ions=1000&wireFormat.maxFrameSize=104857600"/>
         <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnection    s=1000&wireFormat.maxFrameSize=104857600"/>
 <transportConnector name="nio" uri="nio://0.0.0.0:61618?trace=true"/>   <!-- 这是添加的 -->
 </transportConnectors>

在这里插入图片描述
而使用 NIO 协议,代码修改量极小,只需同时将消息生产者和消费者的 URL 修改即可:

//public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61618";

修改之后即可正确运行

7.3、NIO 增强
URI 格式以 nio 开头,表示这个端口使用 tcp 协议为基础的NIO 网络 IO 模型,但这样设置让它只支持 tcp 、 nio 的连接协议。如何让它支持多种协议?
在这里插入图片描述

	Starting with version 5.13.0, ActiveMQ supports wire format protocol
	detection. OpenWire, STOMP, AMQP, and MQTT can be automatically
	detected. This allows one transport to be shared for all 4 types of
	clients.

使用 : auto+nio+ssl
官网介绍 : http://activemq.apache.org/auto
使用 auto 的方式就相当于四合一协议 : STOMP AMQP MQTT TCP NIO

<transportConnector name="auto+nio" uri="auto+nio://localhost:5671"/>

auto 就像是一个网络协议的适配器,可以自动检测协议的类型,并作出匹配

<transportConnector name="auto" uri="auto://localhost:5671?auto.protocols=default,stomp"/>

配置文件修改:

       …… 

<transportConnector name="auto+nio" uri="auto+nio://0.0.0.0:61608?maximumConnections=1000
&amp;wireFormat.maxFrameSize=104857600&amp;org.apache.activemq.transport.nio.SelectorManager.corelPoolSize=20
&amp;org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50"/>

连接:
在这里插入图片描述
消息发送成功
在这里插入图片描述
同样代码只需修改 URI

public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61608";

对于 NIO 和 tcp 的代码相同,但不代表使用其他协议代码相同,因为底层配置不同,其他协议如果使用需要去修改代码

8、持久化

8.1、ActiveMQ 的可持久化

  • 将MQ 收到的消息存储到文件、硬盘、数据库 等、 则叫MQ 的持久化,这样即使服务器宕机,消息在本地还是有,仍就可以访问到。
    官网 : http://activemq.apache.org/persistence
    ActiveMQ 支持的消息持久化机制: 带复制功能的 LeavelDB 、 KahaDB 、 AMQ 、 JDBC
  • 持久化就是高可用的机制,即使服务器宕机了,消息也不会丢失

8.2、持久化存储方式

  1. AMQ: 是文件存储形式,写入快、易恢复 默认 32M 在 ActiveMQ 5.3 之后不再适用
  2. KahaDB : 5.4 之后基于日志文件的持久化插件,默认持久化插件,提高了性能和恢复能力
    KahaDB 的属性配置 : http://activemq.apache.org/kahadb
    它使用一个事务日志和 索引文件来存储所有的地址

在这里插入图片描述

db-<数字>.log: 存储数据,一个存满会再次创建 db-2 db-3 …… ,当不会有引用到数据文件的内容时,文件会被删除或归档
db.data: 是一个BTree 索引,索引了消息数据记录的消息,是消息索引文件,它作为索引指向了 db-.log 里的消息

一点题外话:就像mysql 数据库,新建一张表,就有这个表对应的 .MYD 文件,作为它的数据文件,就有一个 .MYI 作为索引文件。

db.free : 存储空闲页 ID 有时会被清除
db.redo :当 KahaDB 消息存储在强制退出后启动,用于恢复 BTree 索引
lock : 顾名思义就是锁

四类文件+一把锁 ==》 KahaDB

  1. LeavelDB : 希望作为以后的存储引擎,5.8 以后引进,也是基于文件的本地数据存储形式,但是比 KahaDB 更快
    它比KahaDB 更快的原因是她不使用BTree 索引,而是使用本身自带的 LeavelDB 索引
    题外话:为什么LeavelDB 更快,并且5.8 以后就支持,为什么还是默认 KahaDB 引擎,因为 activemq 官网本身没有定论,LeavelDB 之后又出了可复制的LeavelDB 比LeavelDB 更性能更优越,但需要基于 Zookeeper 所以这些官方还没有定论,任就使用 KahaDB
  2. JDBC持久化。

8.3、JDBC持久化配置mysql

使用JDBC 的持久化

  1. 修改配置文件,默认 kahaDB
    修改之前:
<persistenceAdapter>
       <kahaDB directory="${activemq.data}/kahadb"/>  
 </persistenceAdapter>

修改之后:

<persistenceAdapter>
      <jdbcPersistenceAdapter dataSource="#mysql-ds"/>
 </persistenceAdapter>
  1. 在activemq 的lib 目录下添加 jdbc 的jar 包 (connector.jar 我使用5.1.49 版本)
  2. 修改配置文件 : activemq.xml 使其连接自己windows 上的数据库,并在本地创建名为activemq 的数据库
<bean id="mysql-ds" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://192.168.198.1:3306/activemq?relaxAutoCommit=true"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="poolPreparedStatements" value="true"/>
  </bean> 
  1. 让linux 上activemq 可以访问到 mysql ,之后产生消息。
    ActiveMQ 启动后会自动在 mysql 的activemq 数据库下创建三张表:activemq_msgs 、activemq_acks、activemq_lock
    activemq_acks:用于存储订阅关系。如果是持久化Topic,订阅者和服务器的订阅关系在这个表保存
    activemq_lock:在集群环境中才有用,只有一个Broker可以获得消息,称为Master Broker
    activemq_msgs:用于存储消息,Queue和Topic都存储在这个表中

点对点会在数据库的数据表 ACTIVEMQ_MSGS 中加入消息的数据,且在点对点时,消息被消费就会从数据库中删除
但是对于主题,订阅方式接受到的消息,会在 ACTIVEMQ_MSGS 存储消息,即使MQ 服务器下线,并在 ACTIVEMQ_ACKS 中存储消费者信息 。 并且存储以 activemq 为主,当activemq 中的消息被删除后,数据库中的也会自动被删除。

8.4、修改配置坑

无法正常启动是可以先在data/activemq.log问价查看启动日志,分析失败原因

  • 失败1:没有远程访问的权限,需要你给指定用户设置访问权限才能远程访问该数据库,报错如下图,
    解决方法:sql赋权限,root用户名,192.168.198.222虚拟机ip,123密码
GRANT ALL PRIVILEGES ON *.* TO 'root'@'192.168.198.222' IDENTIFIED BY '123456' WITH GRANT OPTION;

在这里插入图片描述

  • 失败二:虚拟机无法访问3306端口号
    使用wget ip:port 测试是否能访问端口
    如果失败,添加端口入站规则。

8.5、jdbc改进:加入高速缓存机制Journal

该机制相当与在mysql与mq之间加了个Journal缓存,当同时产生10000条消息时,先把消息存在缓存中,如果缓存中的消息被消费90%,剩下的没来得及消费10%则会被存储在mysql中。
高速缓存在 activemq.xml 中的配置:
在这里插入图片描述

8.6、持久化总结

8.6、持久化总结
9、多节集群
如何保证高可用 ==》 搭建集群
ZooKeeper + Replicated LevelDB Store ==》
集群 http://activemq.apache.org/replicated-leveldb-store

在这里插入图片描述
这幅图的意思就是 当 Master 宕机后,zookeper 监测到没有心跳信号, 则认为 master 宕机了,然后选举机制会从剩下的 Slave 中选出一个作为 新的 Master

搭建大概流程:
在这里插入图片描述
9.1、搭建zookeper 集群,搭建 activemq 集群
集群搭建:

  1. 新建 /mq_cluster 将原始的解压文件复制三个,修改端口 (jetty.xml)
    在这里插入图片描述

  2. 增加IP 到域名的映射(/etc/hosts 文件)

在这里插入图片描述

  1. 修改 为相同的borkername
    4
  2. 改为 replicated LevelDB (3个都配,这里列举一个)
<persistenceAdapter>
    <replicatedLevelDB
      directory="{activemq.data}/leveldb"
      replicas="3"
      bind="tcp://0.0.0.0:63631"
      zkAddress="localhost:2191,localhost:2192,localhost:2193"
      zkPassword="123456"
	  sync="local_disk"
      zkPath="/activemq/leveldb-stores"
      hostname="wh-mq-server"
      />
  </persistenceAdapter>
  1. 修改activemq 端口 02 节点 =》 61617 03 节点 =》 61618
    在这里插入图片描述
    想要启动replica leavel DB 必须先启动所有的zookeper 服务,zookeper 的单机伪节点安装这里不细说了,主要说zookeper 复制三份后改配置文件,并让之自动生成 myid 文件,并将zk的端口改为之前表格中对应的端口 。这是conf 下的配置文件
    其具体配置为:
tickTime=2000
initLimit=10
syncLimit=5
clientPort=2191    // 自行设置
server.1=192.168.17.3:2888:3888
server.2=192.168.17.3:2887:3887
server.3=192.168.17.3:286:3886
dataDir=/zk_server/data/log1    // 自行设置

我设置了三个,此时方便起见可以写批处理脚本
#!/bin/sh // 注意这个必须写在第一行

cd /zk_server/zk_01/bin
./zkServer.sh  start

cd /zk_server/zk_02/bin
./zkServer.sh  start

cd /zk_server/zk_03/bin
./zkServer.sh  start   

编写这个 zk_batch.sh 之后, 使用

chmod  700    zk_batch.sh

命令即可让它变为可执行脚本, ./zk_batch.sh start 即可 (即启动了三个zk 的服务)
同理可以写一个批处理关闭zk 服务的脚本和 批处理开启mq 服务 关闭 mq 服务的脚本。

完成上述之后连接zk 的一个客户端

./zkCli.sh -server 127.0.0.1:2191

连接之后:
在这里插入图片描述
表示连接成功
查看我的三个节点: 我的分别是 0…… 3 …… 4 …… 5
在这里插入图片描述
查看我的节点状态

get /activemq/leveldb-stores/00000000003

在这里插入图片描述
此次验证表明 00000003 的节点状态是master (即为63631 的那个mq 服务) 而其余的(00000004 00000005) activemq 的节点是 slave
如此集群顺利搭建成功 !

此次测试表明只有 8161 的端口可以使用 经测试只有 61 可以使用,也就是61 代表的就是master
在这里插入图片描述

9.2、测试集群可用性:
首先:
在这里插入图片描述
修改代码

public static final String ACTIVEMQ_URL = "failover:(tcp://192.168.17.3:61616,tcp://192.168.17.3:61617,tcp://192.168.17.3:61618)?randomize=false";
public static final String QUEUE_NAME = "queue_cluster";

测试:
在这里插入图片描述

MQ服务收到三条消息:
在这里插入图片描述

消息接收

在这里插入图片描述

MQ 服务也将消息出队

在这里插入图片描述
以上代表集群可以正常使用

此时真正的可用性测试:
杀死 8061 端口的进程 !!!

在这里插入图片描述
刷新页面后 8161 端口宕掉,但是 8162 端口又激活了
在这里插入图片描述
在这里插入图片描述
当 61616 宕机,代码不变发消息 自动连接到 61617 了
在这里插入图片描述
这样! 集群的可用性测试成功!

10、面试题

  1. 引入消息队列如何保证其高可用
    持久化
    事务
    签收
    集群
  2. 异步投递Asyn Sends
    在这里插入图片描述

对于一个慢消费者,使用同步有可能造成堵塞,消息消费较慢时适合用异步发送消息
activemq 支持同步异步 发送的消息,默认异步。当你设定同步发送的方式和 未使用事务的情况下发持久化消息,这时是同步的。
如果没有使用事务,且发送的是持久化消息,每次发送都会阻塞一个生产者直到 broker 发回一个确认,这样做保证了消息的安全送达,但是会阻塞客户端,造成很大延时 。
在高性能要求下,可以使用异步提高producer 的性能。但会消耗较多的client 端内存,也不能完全保证消息发送成功。在 useAsyncSend = true 情况下容忍消息丢失。

// 开启异步投递
activeMQConnectionFactory.setUseAsyncSend(true);

在这里插入图片描述
如何在投递快还可以保证消息不丢失 ?
异步发送消息丢失的情况场景是: UseAsyncSend 为 true 使用 producer(send)持续发送消息,消息不会阻塞,生产者会认为所有的 send 消息均会被发送到 MQ ,如果MQ 突然宕机,此时生产者端尚未同步到 MQ 的消息均会丢失 。
故 正确的异步发送方法需要接收回调
同步发送和异步发送的区别就在于 :
同步发送send 不阻塞就代表消息发送成功
异步发送需要接收回执并又客户端在判断一次是否发送

在代码中接收回调的函数 :

activeMQConnectionFactory.setUseAsyncSend(true);
    ……  
    
 for (int i = 1; i < 4 ; i++) {
         textMessage = session.createTextMessage("msg--" + i);
      textMessage.setJMSMessageID(UUID.randomUUID().toString()+"--  orderr");
     String msgid = textMessage.getJMSMessageID();
            messageProducer.send(textMessage, new AsyncCallback() {
                @Override
                public void onSuccess() {
                    // 发送成功怎么样
                    System.out.println(msgid+"has been successful send ");
                }

                @Override
                public void onException(JMSException e) {
                    // 发送失败怎么样
                    System.out.println(msgid+" has been failure send ");
                }
            });
}    
  1. 延迟投递和定时投递
    ① 在配置文件中设置定时器开关 为 true
    在这里插入图片描述
    ② 代码编写
    Java 代码中封装的辅助消息类型 ScheduleMessage
    可以设置的 常用参数 如下:
    在这里插入图片描述
long delay = 3 * 1000 ;
long perid = 4 * 1000 ;
int repeat = 7 ;
for (int i = 1; i < 4 ; i++) {
    TextMessage textMessage = session.createTextMessage("delay msg--" + i);
    // 消息每过 3 秒投递,每 4 秒重复投递一次 ,一共重复投递 7 次
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,perid);
    textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);

    messageProducer.send(textMessage);
}
  1. ActiveMQ的消息重发机制

那些情况会引起消息的重发?
在这里插入图片描述
请说说重发时间间隔和次数?
默认:间隔:1s、重发次数:6

在这里插入图片描述
有毒消息Poison ACK 的理解?
在这里插入图片描述

死信队列的一些设置

修改消费代码块,当嫌6 次太多,设置为 3次

// 三次的意思是不计算本来发送的第一次 ,之后再次发送的第三次就被废弃
RedeliveryPolicy  redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);

在spring 中使用 死信机制
在这里插入图片描述
在业务逻辑中,如果一个订单系统没有问题,则使用正常的业务队列,当出现问题,则加入死信队列 ,此时可以选择人工干预还是机器处理 。

死信队列默认是全部共享的,但是也可以设置独立的死信队列
独立的死信队列配置
在这里插入图片描述
5.死信队列DLQ
什么时DLQ
在这里插入图片描述
死信队列配置
共享死信队列配置:
在这里插入图片描述
独立死信队列配置:
在这里插入图片描述
6、如何保证消息不被重复消费,幂等性的问题

  • 如果消息是做数据库的插入操作,给这个消息一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据 。
  • 如果不是,可以用redis 等的第三方服务,给消息一个全局 id ,只要消费过的消息,将 id ,message 以 K-V 形式写入 redis ,那消费者开始消费前,先去 redis 中查询有没消费的记录即可。

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值