【RabbitMq学习笔记】

一、概念

1、rabbitmq简介:

mq全称为message queue,即消息队列,rabbitmq是由erlang语言开发,基于AMQP(Advanced Message Queue高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法。

2、应用场景

(1)任务异步处理:将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行处理,提高了应用程序的响应时间。
(2)应用程序解耦合:MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦。

3、RabbitMQ的工作原理

3.1 RabbitMQ的基本结构:

在这里插入图片描述

3.2 组成部分说明:

(1)Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
(2)Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过滤。
(3)Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方
(4)producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ
(5)Consumer:消息消费者,即消费方客户端,接受MQ转发的消息

3.3 消息发布接收流程

(1)发送消息

生产者和broker建立tcp连接
生产者和broker建立通道
生产者通过通道消息发送Broker,由Exchange将消息转发
Exchange将消息转发到指定的Queue(队列)

(2)接收消息

消费者和Broker建立tcp链接
消费者和Broker建立通道
消费者监听指定的Queue(队列)
当有消息到达Queue时Broker默认将消息推送给消费者
消费者接收到消息

二、工作机制

1、简单模式

1.1 简单模式图示

在这里插入图片描述

1.2 简单模式之生产者示例

(1)先用rabbitMQ官方提供的java client测试,
(2)导入依赖:

<dependency>
	<groupId>com.rabbitmq</groupId>
	<artifactId>amqp-client</artifactId>
	<version>4.0.3</version><!--此版本与spring boot 1.5.9版本匹配-->
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-logging</artifactId>
</dependency>

(3)生产者部分代码示例:

//对列名称
private static final String QUEUE="helloworld";
//通过连接工厂创建心得连接和mq建立连接
ConnectionFactory  connectionFactory=new  ConnectionFactory();
connectionFactory.setHost("128.128.7.254");//安装RabbitMQ的IP
connectionFactory.setPort(5672);//端口
connectionFactory.setUsername("guest");//mq用户名
connectionFactory.setPassword("guest");//mq密码
//设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一台独立的mq
connectionFactory.setVirtualHost("/");
Connection connection=null;
try{
    //建立新连接
    connection=connectionFactory.newConnection();
    //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
    Channel channel =connection.createChannel();
    //声明队列,如果队列在mq中没有则要创建
    //参数:String queue, boolean durable,boolean exclusive, boolean autoDelete,Map<String,Object>arguments
    /**
     * 参数明细
     * 1、queue 队列名称
     * 2、durable 是否持久化,如果持久化,mq重启后队列还在
     * 3、exclusive 是否独占连接(即是否排外),一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,一般等于true的话用于一个队列只能有一个消费者来消费。
     * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列用不了就自动删除)
     * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可以设置存活时间
     */
    channel.queueDeclare(QUEUE,true,false,false,null);
    //发送消息
    /**
     * 1、exchange,交换机,如果不指定将使用mq的默认交换机
     * 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的消息队列,如果使用默认交换机,routingKey设置为队列的名称
     * 3、props,消息的属性
     * 4、body,消息内容
     */
    //消息内容
    String message="hello world to mq";
    channel.basicPublish("",QUEUE,null,message.getBytes());
    System.out.println("send to mq 成功 ----"+message);
}catch(Exception e){
    e.printStackTrace();
}finally{
    channel.close();
    connection.close();
}

1.2 简单模式之消费者示例

(1)导入依赖:

<dependency>
	<groupId>com.rabbitmq</groupId>
	<artifactId>amqp-client</artifactId>
	<version>4.0.3</version><!--此版本与spring boot 1.5.9版本匹配-->
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-logging</artifactId>
</dependency>

(2)消费者部分代码示例:

//对列名称
private static final String QUEUE="helloworld";
//通过连接工厂创建心得连接和mq建立连接
ConnectionFactory  connectionFactory=new  ConnectionFactory();
connectionFactory.setHost("127.0.0.1");//安装RabbitMQ的IP
connectionFactory.setPort(5672);//端口
connectionFactory.setUsername("guest");//mq用户名
connectionFactory.setPassword("guest");//mq密码
//设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一台独立的mq
connectionFactory.setVirtualHost("/");
Connection connection=null;
try{
    //建立新连接
    connection=connectionFactory.newConnection();
    //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
    Channel channel =connection.createChannel();
    //声明队列,如果队列在mq中没有则会创建
    //参数:String queue, boolean durable,boolean exclusive, boolean autoDelete,Map<String,Object>arguments
    /**
     * 参数明细
     * 1、queue 队列名称
     * 2、durable 是否持久化,如果持久化,mq重启后队列还在
     * 3、exclusive 是否独占连接(即是否排外),一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,一般等于true的话用于一个队列只能有一个消费者来消费。
     * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列用不了就自动删除)
     * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可以设置存活时间
     */
    channel.queueDeclare(QUEUE,true,false,false,null);
    //实现消费方法
    DefaultConsumer defaultConsumer =new DefaultConsumer(channel){
        /**
         * 当接收到消息后此方法将被调用
         * 1、consumerTag 消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsumer
         * 2、envelope 信封:通过envelope
         * 3、properties 消息属性
         * 4、body 消息内容
         */
        public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperties properties,byte[] body)throws IOException{
            //交换机
            String exchange=envelop.getExchange();
            //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
            long deliveryTag=envelope.getDeliveryTag();
            //消息内容
            String message=new String(body,"utf-8");
            System.out.println("receive message:"+message);
        }
    }
    //监听队列
    //参数:String queue,boolean autoAck,Consumer callback
    /**
     * 参数明细:
     * 1、queue 队列名称
     * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为true表示将自动回复mq,如果设置为false要通过编程实现回复,若不回复,则消息会一直在队列中
     * 3、callback 消费方法,当消费者接收到消息时要执行的方法
     */
    channel.basicConsume(QUEUE,truedefaultConsumer)
}catch(Exception e){
    e.printStackTrace();
}finally{
}

1.3 简单模式小结

(1)发送端操作流程

创建连接
创建通道
声明队列
发送消息

(2)接收端操作流程

创建连接
创建通道
声明队列
监听队列
接收消息
ack回复

2、工作模式(资源的竞争)

2.1 示意图

在这里插入图片描述

2.2 原理

工作模式是指多个相互竞争的消费者发送消息的模式,它包含一个生产者、两个消费者和一个队列。两个消费者同时绑定到一个队列上去,当消费者获取消息处理耗时任务时,空闲的消费者从队列中获取并消费队列。

2.3 特点:

work queues与入门程序相比,多了一个消费端,两个消费端共同消费同一个队列中的消息,他的特点如下:

一个生产者将消息发给一个队列
多个消费者共同监听一个队列的消息
消息不能被重复消费
RabbitMQ采用轮询的方式将消息平均发送给消费者

应用场景:红包;大项目中的资源调度(任务分配系统不需知道哪一个任务执行系统在空闲,直接将任务扔到消息队列中,空闲的系统自动争抢)

3、publish/subscribe发布订阅(共享资源)

3.1 示意图

在这里插入图片描述

3.2 特点:

(1)X代表交换机rabbitMQ内部组件,erlang 消息产生者是代码完成,代码的执行效率不高,消息产生者将消息放入交换机,交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费
(2)相关场景:邮件群发,群聊天,广播(广告)

4、routing路由模式

4.1 示意图

在这里插入图片描述

4.2 特点:

(1)消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息
(2)根据业务功能定义路由字符串
(3)从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误

5、topic 主题模式(路由模式的一种)

5.1 示意图

在这里插入图片描述

5.2 特点:

(1)星号井号代表通配符
(2)星号代表多个单词,井号代表一个单词
(3)路由功能添加模糊匹配
(4)消息产生者产生消息,把消息交给交换机
(5)交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费

5.3 代码实现(其他与之类似)

(1)工具类

public class ConnectionUtil {
    //连接rabbitmq服务,共享一个工厂对象
    private static ConnectionFactory factory;
    static {
        factory=new ConnectionFactory();
        //设置rabbitmq属性
        factory.setHost("128.128.7.254);
        factory.setUsername("lwp");
        factory.setPassword("123456");
        factory.setVirtualHost("/sxslapp");
        factory.setPort(5672);
    }
    public static Connection getConnection(){
        Connection connection=null;
        try {
            //获取连接对象
            connection = factory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }
}

(2)生产者


//交换机和队列可以在提供方和消费方某一方创建,在两边同时创建也可以,只要创建的名称一致。保证,哪一方先运行则在哪一方创建
public class Provider {
    public static void main(String[] args) {
        try {
            //获取连接对象
            Connection connection = ConnectionUtil.getConnection();
            //获取通道对象
            Channel channel = connection.createChannel();
            //创建交换机(交换机没有存储数据的能力,数据存储在队列上,如果有交换机没队列的情况下,数据会丢失)   //1.参数一:交换机名称    参数二:交换机类型
            channel.exchangeDeclare("topic_exchange","topic");
            //向队列中发送消息
            for(int i=1;i<=10;i++){
                channel.basicPublish("topic_exchange",
                        "emp.hello world",  // #:匹配0-n个单词(之间以.区分,两点之间算一个单词,可以匹配hello world空格的情况)   *(匹配一个单词)
                        null,
                        ("Hello RabbitMQ!!!"+i).getBytes());
            }
            //断开连接
            connection.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(3)消费者1

public class Consumer {
    public static void main(String[] args) {
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获取通道对象
            Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare("topic_queue1",false,false,false,null);
            //绑定交换机(routingKey:路由键)  #:匹配0-n个单词(之间以.区分,两点之间算一个单词)
            channel.queueBind("topic_queue1","topic_exchange","emp.#");
            //监听队列中的消息
            channel.basicConsume("topic_queue1",true,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("消费者1获得消息为:"+new String(body,"utf-8"));
                }
            });
            //消费方不需要关闭连接,保持一直监听队列状态
            //connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

(4)消费者2

public class Consumer2 {
    public static void main(String[] args) {
        Connection connection = ConnectionUtil.getConnection();
        try {
            //获取通道对象
            Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare("topic_queue2",false,false,false,null);
            //绑定交换机(routingKey:路由键)  *:匹配1个单词(之间以.区分,两点之间算一个单词)
            channel.queueBind("topic_queue2","topic_exchange","emp.*");
            //监听队列中的消息
            channel.basicConsume("topic_queue2",true,new DefaultConsumer(channel){
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    System.out.println("消费者2获得消息为:"+new String(body,"utf-8"));
                }
            });
            //消费方不需要关闭连接,保持一直监听队列状态
            //connection.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

三、高级特性

1、消息的可靠投递

1.1 原理:

(1)在使用RabbitMQ的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ为我们提供了两种方式用来控制消息的投递可靠性模式。
confirm - 确认模式
return - 退回模式

(2)rabbitmq - 整个消息投递的路径为:
producer --> rabbitmq broker --> exchange --> queue --> consumer

(3)消息从producer --> exchange投递失败则会返回一个confirmCallback。
(4)消息从exchange --> queue投递失败则会返回一个returnCallback。
我们将利用这两个callback控制消息的可靠性投递

1.2 确认模式代码

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!-- 定义管理交换机、队列 -->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!-- 定义 rabbitTemplate 对象操作可以在代码中方便发送消息 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!-- 消息可靠性投递 - 生产端 -->
    <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"/>

    <rabbit:direct-exchange name="test_exchange_confirm">
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>
</beans>

(2)消息从producer --> exchange则会返回一个confirmCallback

/**
 * 生产者
 *
 * @author murphy
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/spring/spring-rabbitmq.xml")
public class ProducerTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 消息可靠性投递
     * 确认模式
     *      1. 确认模式开启:ConnectionFactory中开启publisher-confirms="true"
     *      2. 在rabbitTemplate定义ConfirmCallBack回调函数
     */
    @Test
    public void testConfirm() {
        // 定义回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * @param correlationData 相关配置信息
             * @param ack exchange交换机 是否成功收到了消息。true 成功,false代表失败
             * @param cause 失败原因
             */
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("Do Confirm.");
                if (ack) {
                    // 接收成功
                    System.out.println("Receiving Successful! " + cause);
                } else {
                    // 接收失败
                    System.out.println("Failed! " + cause);
                    // 业务处理 - 重发消息...
                }
            }
        });
        // 3. 发送消息
      	// - 失败案例 - 消息无法发送
        rabbitTemplate.convertAndSend("test_exchange_confirm1","confirm","Message Confirm.");
      	// - 成功案例
      	rabbitTemplate.convertAndSend("test_exchange_confirm","confirm","Message Confirm.");
    }
}

1.3 退回模式代码

(1)配置类与1.2相同
(2)消息从exchange --> queue投递失败则会返回一个returnCallback

/**
 * 回退模式: 当消息发送给Exchange后,Exchange路由到Queue失败是 才会执行 ReturnCallBack
 * 步骤:
 *      1. 开启回退模式:publisher-returns="true"
 *      2. 设置ReturnCallBack
 *      3. 设置Exchange处理消息失败的模式:setMandatory
 *
 * 如果消息没有路由到Queue,则丢弃消息(默认)
 * 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack
 */
@Test
public void testReturn() {
    // 设置交换机处理失败消息的模式 - false则直接丢弃消息,不进入回调 / true进入回调函数
    rabbitTemplate.setMandatory(true);
    // 2.设置ReturnCallBack
    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
        /**
         *
         * @param message - 消息对象
         * @param replyCode - 错误码
         * @param replyText - 错误信息
         * @param exchange - 交换机
         * @param routingKey - 路由键
         */
        public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
            System.out.println("Do ...");

            System.out.println(message);
            System.out.println(replyCode);
            System.out.println(replyText);
            System.out.println(exchange);
            System.out.println(routingKey);
            // 业务处理 - 重新发送
        }
    });
    // 3. 发送消息
    rabbitTemplate.convertAndSend("test_exchange_confirm","confirm - Wrong","Message Confirm.");
}

2、 Consumer Ack

2.1 原理:

(1)ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。 有三种确认方式:
自动确认:acknowledge=“none”
手动确认:acknowledge=“manual”
根据异常情况确认:acknowledge=“auto” (这种方式使用麻烦,不作讲解)

(2)其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应message从RabbitMQ的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

2.2 部分代码

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!-- 扫描包 -->
    <context:component-scan base-package="com.murphy.listener"/>

    <!-- 监听器 -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual">
        <rabbit:listener ref="ackListener" queue-names="test_queue_confirm"></rabbit:listener>
    </rabbit:listener-container>
</beans>

(2)AckListener

/**
 * 消费端 - 收到消息的确认方式
 *
 * @author murphy
 * @since 2021/9/12 3:30 下午
 */
@Component
public class AckListener implements ChannelAwareMessageListener {

    public void onMessage(Message message, Channel channel) throws Exception {
        long deliverTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 1. 接收转换消息
            System.out.println(new String(message.getBody()));
            // 2. 处理业务逻辑
            System.out.println("- 处理业务逻辑 -");
            // 模拟业务处理异常
            int i = 3/0;
            // 3. 手动签收
            channel.basicAck(deliverTag,true);

        } catch (Exception e) {
            e.printStackTrace();
            // 4. 拒绝签收 - requeue:true - 如果设置为true,则消息重新回到queue,broker会重新发送该消息给消费端
            channel.basicNack(deliverTag,true,true);
            channel.basicReject(deliverTag,true);
        }
    }

    public void onMessage(Message message) {

    }
}

3、消费端限流

3.1 原理图

在这里插入图片描述

3.2 部分代码

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!-- 扫描包 -->
    <context:component-scan base-package="com.murphy.listener"/>

    <!-- 监听器 -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
        <rabbit:listener ref="qsListener" queue-names="test_queue_confirm"></rabbit:listener>
    </rabbit:listener-container>
</beans>

(2)Consumer 限流机制

/**
 * Consumer 限流机制
 *      1. 确保ack机制为手动确认
 *      2. listener-container配置属性 -
 *          prefetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉去下一条消息。
 * @author murphy
 */
@Component
public class QsListener implements ChannelAwareMessageListener {
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);
        // 1. 获取消息
        System.out.println(new String(message.getBody()));
        // 2. 处理业务逻辑
        // 3. 签收 - 不签收的情况下只能打印一条 message
        //channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    }
}

4、TTL - Time To Live

4.1 管控台设置

(1)管控台设置队列TTL
在这里插入图片描述
(2)管控台创建交换机
在这里插入图片描述
(3)绑定交换机
在这里插入图片描述
(4)模拟发送消息 - 存在时间仅10S
在这里插入图片描述

4.2 部分代码实现

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}" />
    <!-- 定义管理交换机、队列 -->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!-- 定义 rabbitTemplate 对象操作可以在代码中方便发送消息 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!-- TTL -->
    <rabbit:queue name="test_queue_ttl" id="test_queue_ttl">
        <rabbit:queue-arguments>
            <entry key="x-message-ttl" value="100000" value-type="java.lang.Integer"></entry>
        </rabbit:queue-arguments>
    </rabbit:queue>
    
    <rabbit:topic-exchange id="test_exchange_ttl" name="test_exchange_ttl">
        <rabbit:bindings>
            <rabbit:binding pattern="ttl.#" queue="test_queue_ttl"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

(2)测试实现

/**
 * TTL:过期时间
 * 1. 队列统一过期
 * 2. 消息单独过期
 * 
 * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
 * 队列过期后,会将队列所有消息全部移除。
 * 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉)
 */
@Test
public void testTTL() {
//        for (int i=0; i<10; i++) {
//            // 发送消息
//            rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.murphy","Message TTL." + (i+1));
//        }

    // 消息后处理对象,设置一些消息的参数信息
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        public Message postProcessMessage(Message message) throws AmqpException {
            // 1. 设置message的信息
            // 消息的过期时间
            message.getMessageProperties().setExpiration("5000");
            // 2. 返回该消息
            return message;
        }
    };

//        // 消息的单独过期
//        rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.murphy",
//                "Message TTL.", messagePostProcessor);

    for (int i = 0; i < 10; i++) {
        if (i == 5) {
            // 消息单独过期 - 消息过期后,只有消息在队列顶端,才会判断其是否过期(移除掉) - 因此下述设置无效
            rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.murphy","Message TTL.", messagePostProcessor);
        } else {
            // 不过期的消息
            rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.murphy","Message TTL." + (i+1));
        }
    }

}

5、死信队列

5.1 原理

(1)概念
死信队列(DLX) 。Dead Letter Exchange(死信交换机),当消息成为Dead Message后,可以被重新发送到另一个交换机,这个交换机就是DLX
在这里插入图片描述
(2) 消息成为死信的三种情况
1)队列消息长度到达限制;
2)消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
3)原队列存在消息过期设置,消息到达超时时间未被消费;
(3)队列绑定死信交换机
给队列设置参数:x-dead-letter-exchange和x-dead-letter-routing-key
在这里插入图片描述

5.2 管控台实现

(1)设置死信队列 - queue_dlx
在这里插入图片描述
(2)设置死信交换机 - exchange_dlx
在这里插入图片描述
(3)死信交换机绑定死信队列
在这里插入图片描述
(4)设置正常队列 - test_queue_dlx
在这里插入图片描述
(5)设置正常交换机 - test_exchange_dlx
在这里插入图片描述
(6)绑定正常队列
在这里插入图片描述
(7)发送正常消息 - 10秒后成为死信 - 通过死信交换机进入死信队列
在这里插入图片描述
(8)死信进入死信队列
在这里插入图片描述

5.3 部分代码实现

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!-- 定义管理交换机、队列 -->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!-- 定义 rabbitTemplate 对象操作可以在代码中方便发送消息 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!-- 消息可靠性投递 - 生产端 -->
    <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"/>

    <rabbit:direct-exchange name="test_exchange_confirm">
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- 1. 声明正常的队列(test_queue_dlx)和交换机(test_exchange_dlx) -->
    <rabbit:queue id="test_queue_dlx" name="test_queue_dlx">
        <!-- 3. 正常队列绑定死信交换机 -->
        <rabbit:queue-arguments>
            <!-- 3.1 x-dead-letter-exchange:死信交换机名称 -->
            <entry key="x-dead-letter-exchange" value="exchange_dlx"/>
            <!-- 3.2 x-dead-letter-routing-key:发送给死信交换机的routing key -->
            <entry key="x-dead-letter-routing-key" value="dlx.murphy"/>
            <!-- 4.1 设置队列的过期时间 ttl -->
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
            <!-- 4.2 设置队列的长度限制 max-length -->
            <entry key="x-max-length" value="10" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="test_exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="test.dlx.#" queue="test_queue_dlx"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 2. 声明死信队列(queue_dlx)和死信交换机(exchange_dlx) -->
    <rabbit:queue id="queue_dlx" name="queue_dlx"></rabbit:queue>
    <rabbit:topic-exchange name="exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.#" queue="queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

(2)生产端测试

/**
 * 发送测试死信消息:
 *      1. 过期时间
 *      2. 长度限制
 *      3. 消息拒收
 */
@Test
public void testDlx() {
    // 1. 测试过期时间,死信消息
    rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.murphy",
            "Dead Letter - TTL");

    // 2. 测试长度限制后,消息死信
    for (int i=0; i<20; i++) {
        rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.murphy",
                "Dead Letter - OutOfLength - " + (i+1));
    }

    // 3. 测试消息拒收
    rabbitTemplate.convertAndSend("test_exchange_dlx","test.dlx.murphy",
            "Dead Letter - Reject");
}

(3)消息端监听器

/**
 * 死信监听
 *
 * @author murphy
 */
@Component
public class DlxListener implements ChannelAwareMessageListener {
    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 1. 接收转换消息
            System.out.println(new String(message.getBody()));
            // 2. 处理业务逻辑
            System.out.println("Do..");
            // 出现错误
            int i = 3 / 0;
            // 3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("出现异常,拒绝接收!");
            // 4. 拒绝签收,不重回队列 - requeue=false
            channel.basicNack(deliveryTag,true,false);
        }
    }
}

// 监听器注册
<!-- 监听器 -->
<rabbit:listener-container connection-factory="connectionFactory">
    <rabbit:listener ref="dlxListener" queue-names="test_queue_dlx"></rabbit:listener>
</rabbit:listener-container>

6、延迟队列

6.1 原理

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

需求:
下单后,30分钟未支付,取消订单,回滚库存。
新用户注册成功 7 天后,发送短信问候。

实现方式:
定时器
延迟队列

在这里插入图片描述

在RabbitMQ中并未提供延迟队列功能,但是可以使用:TTL+死信队列组合实现延迟队列的效果。
在这里插入图片描述

6.2 代码实现

(1)配置文件

<?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:rabbit="http://www.springframework.org/schema/rabbit"
       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/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd ">

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:properties/rabbitmq.properties"/>
    <!-- 定义 rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"
                               publisher-confirms="true"
                               publisher-returns="true" />
    <!-- 定义管理交换机、队列 -->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!-- 定义 rabbitTemplate 对象操作可以在代码中方便发送消息 -->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>

    <!-- 消息可靠性投递 - 生产端 -->
    <rabbit:queue id="test_queue_confirm" name="test_queue_confirm"/>

    <rabbit:direct-exchange name="test_exchange_confirm">
        <rabbit:bindings>
            <rabbit:binding queue="test_queue_confirm" key="confirm"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!--
        延迟队列:
            1. 定义正常交换机(order_exchange)和正常消息队列(order_queue)
            2. 定义死信交换机(order_exchange_dlx)和死信消息队列(order_queue_dlx)
            3. 绑定,设置正常队列过期时间为30分钟
    -->
    <!-- 1. 定义正常交换机(order_exchange)和队列(order_queue) -->
    <rabbit:queue id="order_queue" name="order_queue">
        <!-- 3. 绑定,设置正常队列过期时间为30分钟 -->
        <rabbit:queue-arguments>
            <entry key="x-dead-letter-exchange" value="order_exchange_dlx"/>
            <entry key="x-dead-letter-routing-key" value="dlx.order.cancel"/>
            <entry key="x-message-ttl" value="10000" value-type="java.lang.Integer"/>
        </rabbit:queue-arguments>
    </rabbit:queue>
    <rabbit:topic-exchange name="order_exchange">
        <rabbit:bindings>
            <rabbit:binding pattern="order.#" queue="order_queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- 2. 定义死信交换机(order_exchange_dlx)和死信队列(order_queue_dlx) -->
    <rabbit:queue id="order_queue_dlx" name="order_queue_dlx"></rabbit:queue>
    <rabbit:topic-exchange name="order_exchange_dlx">
        <rabbit:bindings>
            <rabbit:binding pattern="dlx.order.#" queue="order_queue_dlx"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>
</beans>

(2)消费端监听

/**
 * 订单系统监听 - 案例30分钟
 *
 * @author murphy
 */
@Component
public class OrderListener implements ChannelAwareMessageListener {

    public void onMessage(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            // 1. 接收转换消息
            System.out.println(new String(message.getBody()));

            // 2. 处理业务逻辑
            System.out.println("处理业务逻辑...");
            System.out.println("根据订单ID查询其状态...");
            System.out.println("判断订单状态是否执行成功...");
            System.out.println("取消订单,回滚库存...");
            // 3. 手动签收
            channel.basicAck(deliveryTag,true);
        } catch (Exception e) {
            System.out.println("出现异常,拒绝签收!");
            // 4. 拒绝签收,不重回队列 requeue=false
            channel.basicNack(deliveryTag,true,false);
        }
    }
}

(3)注册监听器

<!-- 监听器:在消费者配置文件中注册监听器 -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="manual" prefetch="1">
    <rabbit:listener ref="orderListener" queue-names="order_queue_dlx"></rabbit:listener>
</rabbit:listener-container>

(4)生产者端测试

@Test
public void testDelay() throws InterruptedException {
    // 1. 发送订单消息。 将来是在订单系统中,下单成功后,发送消息
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    rabbitTemplate.convertAndSend("order_exchange", "order.msg",
            "订单信息:ID=91231,Time=" + sdf.format(new Date()));
    // 2. 打印倒计时 10s
    for (int i = 0; i < 10; i++) {
        System.out.println((i+1) + "...");
        Thread.sleep(1000);
    }
}

(5)运行结果 - 10秒后判断订单状态,若没有被消费,则取消订单,进行相关业务处理
在这里插入图片描述

四、集群搭建

1、安装环境

(1)安装C++编译环境,使用如下命令:

yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC unixODBC-devel httpd python-simplejson

(2)安装文件获取,我直接用的是源帖子的版本,这个可自由匹配,只需要符合版本要求即可。获取的文件,在当执行命令的目录下,使用ls命令即可查看到

wget http://erlang.org/download/otp_src_19.2.tar.gz

在这里,可能会碰到wget未找到命令的问题,执行如下命令安装

yum -y install wget

(3)下载完erlang安装包后,解压文件

tar -xzvf otp_src_19.2.tar.gz

然后进入对应的目录:cd otp_src_19.2(具体路径对应自己的路径)

(4)编译安装erlang语言环境,prefix=/usr/local/erlang 为安装目录

./configure --prefix=/usr/local/erlang --enable-smp-support --enable-threads --enable-sctp --enable-kernel-poll --enable-hipe --with-ssl --without-javac

参数说明:

–prefix 指定安装目录 
–enable-smp-support启用对称多处理支持(Symmetric Multi-Processing对称多处理结构的简称)
–enable-threads启用异步线程支持
–enable-sctp启用流控制协议支持(Stream Control Transmission Protocol,流控制传输协议)
–enable-kernel-poll启用Linux内核poll
–enable-hipe启用高性能Erlang –with-ssl 启用ssl包 –without-javac 

这里要注意的是,--前面,只能有一个空格(当初在这里也看了好久,尴尬)

(5)编译,执行make命令

(6)安装,执行make install 命令

不要把make 和make install 命令写在一块,即:make && make install 这种形式,可能会报没有规则,停止的错误)、

(7)编译配置文件:/etc/profile,配置环境变量,

vi /etc/profile 在后面增加:export PATH=$PATH:/usr/local/erlang/bin

(8)重启配置:source /etc/profile

(9)测试erlang安装是否成功,使用如下命令:

erl Erlang/OTP 17 [erts-6.2] [source] [smp:2:2] [async-threads:10] [kernel-poll:false]

2、安装rabbitmq

(1)cd /usr/local
(2)下载RabbitMQ安装包

    wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.1/rabbitmq-server-generic-unix-3.6.1.tar.xz 

(3)解压文件

xz -d rabbitmq-server-generic-unix-3.6.1.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.6.1.tar

(4)将rabbitmq-server-3.6.1 重命名为rabbitmq以便记忆(不一定需要这样做)

mv rabbitmq_server-3.6.1/ rabbitmq

(5)配置rabbitmq环境变量:

vi /etc/profile
在后面加上:export PATH=$PATH:/usr/local/rabbitmq/sbin ,这个路径是自定义的路径

(6)重启配置

source /etc/profile

(7)启动服务

rabbitmq-server -detached

(8)查看rabbitmq 的状态

rabbitmqctl status

启动服务:rabbitmq-server -detached【 /usr/local/rabbitmq/sbin/rabbitmq-server -detached 】
查看状态:rabbitmqctl status【 /usr/local/rabbitmq/sbin/rabbitmqctl status 】
关闭服务:rabbitmqctl stop【 /usr/local/rabbitmq/sbin/rabbitmqctl stop 】
列出角色:rabbitmqctl list_users

(9)然后启用插件:

rabbitmq-plugins enable rabbitmq_management

(10)配置防火墙,

配置linux 端口 15672 网页管理 5672 AMQP端口:

firewall-cmd --permanent --add-port=15672/tcp
firewall-cmd --permanent --add-port=5672/tcp
systemctl restart firewalld.service

(11)打开后台管理界面:http://ip+15672

默认情况下,是不允许登录的,因为没有账号。可通过如下命令,增加角色和账号配置:

rabbitmqctl add_user adim adim //添加用户,后面两个参数分别是用户名和密码,我这都用adim了。
rabbitmqctl set_permissions -p / adim ".*" ".*" ".*" //添加权限
rabbitmqctl set_user_tags adim administrator //修改用户角色

3、搭建rabbitmq集群

(1)克隆多台服务器,或者在原有的无力服务器上安装单机版的rabbitmq。

   为了便于说明,假设有两台服务器:server1和server2,ip分别是:128.128.7.253 、128.128.7.254

(2)修改server1和server2的host,命令:vi /etc/hosts,在hosts下面添加一下内容

     128.128.7.253 server1
     1128.128.7.254 server2

(3)修改完后,重启电脑,这一步很重要,否则服务重置hostname

(4)修改server1、server2 的.erlang.cookie 的内容,这是erlang的cookie,相当于集群通信密钥的意思。

如果是克隆版本的服务器,这里不需要再修改。.erlang.cookie在安装目录下,我是在/root目录下安装的,路径就是/root/.erlang.cookie 

(5)修改.erlang.cookie的权限,使用命令:

chmod 400 /root/.erlang.cookie 

(6)开通防火墙端口,使用以下命令:

     firewall-cmd --permanent --add-port={4369/tcp,25672/tcp}
     然后重启防火墙:systemctl restart firewalld.service。

(7)在启动了server1、server2 的rabbitmq 的前提下,把server2 添加到server1集群中(也可以把server1添加到server2集群中,都一样的),执行以下命令:

     rabbitmqctl stop_app
    rabbitmqctl join_cluster rabbit@server1
    rabbitmqctl start_app

(8)登录:http://ip+15672,这个ip可以是集群的任意一个ip,即可以看到集群已经成功,具体如下图所示:
在这里插入图片描述

注意:

1、可能会碰到连接不到集群的node的情况,这种情况下重启下系统,一般都可以解决问题。
2、可能会碰到后台管理登录不上的情况,这种情况下有两种可能:
      a、账号不存在或密码不正确 
      b、开通防火墙端口后,没有重启防火墙
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值