rabbitmq学习笔记

 

目录:

  •  rabbitmq客户端使用:
    • rabbitmq  生产者的基本步骤
    • rabbitmq   消费者的基本步骤
    • pom依赖
    • simple简单模式
    • 工作队列模式
    • 工作队列模式(非公平模式
    • 发布者/订阅者模式
    • 路由模式
    • 主题模式
    • rabbitmq事务
    • 生产者确认消息到达队列,confirm机制
      • 串行确认
      • 异步确认
    • rabbitmq客户端api,及常用类说明
  • springboot整合rabbitmq
    • 基本使用
    • Queue队列
    • Exchange交换器
    • Binding绑定
    • Message消息
    • 简单模式,不绑交换机,直连队列
    • 工作队列模式,多个消费者消费同一个队列
    • 发布者/订阅者模式,绑定Fanout类型交换器
    • 路由模式,绑定direct类型交换器
    • 主题模式,绑定topic类型交换器
    • confirm模式
    • spring整合rabbitmq使用事务
    • 消费者限流
    • 快速启动多个消费者,concurrency属性

 

 

关于RabbitMQ的教程很多,我找了两篇个人感觉比较好的。

https://blog.csdn.net/hellozpc/article/details/81436980     RabbitMQ教程

https://www.cnblogs.com/yihuihui/p/12600797.html     【SpringBoot MQ 系列】RabbitListener 消费基本使用姿势介绍

我这里会放出这5中基本队列使用的代码。

rabbitmq学习笔记_第1张图片

 

 

 

 rabbitmq客户端使用:

 

rabbitmq  生产者的基本步骤:

1、创建连接工程ConnectionFactory,设置相关配置,主要就是host、port、username、password、vhost这5个属性

2、根据ConnectionFactory获取连接Connection 

3、声明通道Channel

4、声明交换机Exchange(可以没有Exchange,生产者直接将消息扔给队列,Exchange重复声明是没问题的,但是重复声明时决不能更改其声明属性,否则报错。)

5、声明队列Queue(Queue队列必须要有,如果队列存在了就可以不声明了,但重复声明也没啥问题,但是重复声明时决不能更改其声明属性,否则报错。)

6、队列和交换机绑定(如果是没有交换机那就没必要了)

 7、发送消息。

8、关闭channel和connection,释放资源。

 

rabbitmq   消费者的基本步骤:

1、创建连接工程ConnectionFactory,设置相关配置,主要就是host、port、username、password、vhost这5个属性

2、根据ConnectionFactory获取连接Connection 

3、声明通道Channel

4、声明队列Queue(Queue队列必须要有,如果队列存在了就可以不声明了,但重复声明也没啥问题,但是重复声明时决不能更改其声明属性,否则报错。)

5、定义一个消费回调(消费信息的api,新版rabbitmq和旧版的是不同的,我这里只用新版的)

6、将消费回调绑定到队列上,让其进行监听。(消费者千万不要在绑定监听后就关闭channel和connection,不然你是接收不到消息的,毕竟你都关闭了)

 

 

先上pom依赖:


    com.rabbitmq
    amqp-client

 

 

 

1、simple简单模式:

  这种模式是最简单的,就一个生产者,一个消费者,一个队列。

rabbitmq学习笔记_第2张图片

 

 代码:

生产者   Provider

package com.hongcheng.rabbitmq_demo.simple;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Provider {
    private static final String queue_name = "hongcheng_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        
        // 定义一个连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        
        // 设施服务地址
        connectionFactory.setHost("127.0.0.1");
        
        // 设置端口
        connectionFactory.setPort(5672);
        
        // 设置vhost虚拟主机
        connectionFactory.setVirtualHost("hongchengHost");
        
        // 设置用户名
        connectionFactory.setUsername("admin");
        
        // 设置密码
        connectionFactory.setPassword("admin");
        
        // 获取连接
        Connection connection = connectionFactory.newConnection();
        
        // 从连接中获取一个通道channel
        Channel channel = connection.createChannel();
        
        // 声明一个队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        String msg = "大傻子";
        
        // 发送消息
        channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
        
        channel.close();
        connection.close();
    }
}

 

 

消费者   Consumer 

package com.hongcheng.rabbitmq_demo.simple;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class Consumer {
    
    private static final String queue_name = "hongcheng_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 定义一个连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        
        // 设施服务地址
        connectionFactory.setHost("127.0.0.1");
        
        // 设置端口
        connectionFactory.setPort(5672);
        
        // 设置vhost虚拟主机
        connectionFactory.setVirtualHost("hongchengHost");
        
        // 设置用户名
        connectionFactory.setUsername("admin");
        
        // 设置密码
        connectionFactory.setPassword("admin");
        
        // 获取连接
        Connection connection = connectionFactory.newConnection();
        
        // 创建通道
        Channel channel = connection.createChannel();
        
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 定义一个消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 消费信息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String string = new String(body,"utf-8");
                System.err.println("消息队列收到的消息 ==>" + string);
            }
        };
        // 监听队列
        channel.basicConsume(queue_name, true,consumer);
    }
}

 

有些参数会在后面会在代码注释里面进行说明。

 

因为获取Connection连接后面会经常用到,所以我抽取出来弄成一个类

 

ConnectionUtils 

 

package com.hongcheng.rabbitmq_demo;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class ConnectionUtils {
    public static Connection getConnection() throws IOException, TimeoutException {
        // 定义一个连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        
        // 设施服务地址
        connectionFactory.setHost("127.0.0.1");
        
        // 设置端口
        connectionFactory.setPort(5672);
        
        // 设置vhost虚拟主机
        connectionFactory.setVirtualHost("hongchengHost");
        
        // 设置用户名
        connectionFactory.setUsername("admin");
        
        // 设置密码
        connectionFactory.setPassword("admin");
        
        return connectionFactory.newConnection();
    }
}

 

 

2、工作队列模式

  其实也就是在简单模式上多几个消费者而已

rabbitmq学习笔记_第3张图片

 

 代码:

生产者    Provider:

连续生成50条消息

package com.hongcheng.rabbitmq_demo.work;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Provider {
    
    private static final String queue_name = "work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 获取channel
        Channel channel = connection.createChannel();
        
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        for(int i = 1; i <= 50 ;i++) {
            String msg = "work消息" + i;
            channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
            System.err.println(msg);
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // 关闭
        channel.close();
        connection.close();
        
    }
}

 

消费者1   Consumer1

模拟处理一条消息需要1秒

package com.hongcheng.rabbitmq_demo.work;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
/***
 *     这种情况下,Consumer1和Consumer2轮流获取到mq的消息
 *     并不会因为Consumer2处理的快就能多获取几个消息
 * */ 
public class Consumer1 {

    private static final String queue_name = "work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        // 获取channel
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        // 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer1消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 消费者监听
        boolean autoAck = true;        // 自动确认消费
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

消费者2   Consumer2

模拟处理一条消息需要2秒。

消费者2和消费者1其实代码基本都是一样的,只是消费者2每次休眠2秒,消费者1每次休眠1秒

package com.hongcheng.rabbitmq_demo.work;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

/***
 *     这种情况下,Consumer1和Consumer2轮流获取到mq的消息
 *     并不会因为Consumer2处理的快就能多获取几个消息
 * */
public class Consumer2 {
    
private static final String queue_name = "work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        // 获取channel
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        // 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer2消费了消息 ===> " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 消费者监听
        boolean autoAck = true;        // 自动确认消费
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

 

这种模式下,两个消费者会交替处理消息,mq不会管他们谁处理的的快,谁处理的慢。这算是一种公平模式吧。

 

工作队列的另外一种写法:非公平模式

 

非公平模式也就是说多个队列之间不存在交替处理,谁先处理完了,就给rabbitmq发送一条消息,告诉rabbitmq自己处理完了,那么rabbitmq就会给他发下一条消息。

这样子谁处理能力强,就能获得更多的消息。

 

生产者    Provider

package com.hongcheng.rabbitmq_demo.workunfair;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
 *     公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息
 *     另外消费者必须手动发送ack确认
 * */
public class Provider {
    
    private static final String queue_name = "work_queue_fair";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 获取channel
        Channel channel = connection.createChannel();
        
        // 控制mq每次只发送一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount );
        
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        for(int i = 1; i <= 50 ;i++) {
            String msg = "work消息" + i;
            /**
             *  @param exchange 交换机
             *  @param routingKey 路由键
             *  @param props 消息路由的headers信息
             *  @param body 消息内容
             * */
            channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
            System.err.println(msg);
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // 关闭
        channel.close();
        connection.close();
        
    }
}

 

消费者     Consumer1

package com.hongcheng.rabbitmq_demo.workunfair;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
/**
 *     公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息。
 *     另外消费者必须手动发送ack确认。
 *     这种情况下,那个消费者先消费完,就先获取下一个消息。
 *     最后每个消费者获取到的消息数量是不同的。
 * */
public class Consumer1 {

    private static final String queue_name = "work_queue_fair";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        // 获取channel
        Channel channel = connection.createChannel();
        // 控制mq每次只发送一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount );
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        // 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer2消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 手动执行确认
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };
        // 消费者监听
        boolean autoAck = false;        // 必须关闭自动确认消费
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

消费者     Consumer2

package com.hongcheng.rabbitmq_demo.workunfair;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
/**
 *     公平模式,需要控制mq每次只给消费者一个消息,并且消费者发送确认后才给下一个消息。
 *     另外消费者必须手动发送ack确认。
 *     这种情况下,那个消费者先消费完,就先获取下一个消息。
 *     最后每个消费者获取到的消息数量是不同的。
 * */
public class Consumer2 {
    
private static final String queue_name = "work_queue_fair";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        // 获取channel
        Channel channel = connection.createChannel();
        // 控制mq每次只发送一条消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount );
        // 声明队列
        /***
         *     队列声明后,其队列的配置是不可以修改的,也就是说
         *     channel.queueDeclare(queue_name, true, true, true, null);是会报错了。
         *     毕竟rabbitmq不允许重新定义(不同参数的)一个消息队列
         * */
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
         * queue:消息队列名
         * durable:是否持久化
         * exclusive:是否独占队列,仅限此次连接进操作
         * autoDelete:是否自动删除,服务器在不使用这个队列的时候会删除它
         * arguments:队列的其他属性参数
         * */
        channel.queueDeclare(queue_name, false, false, false, null);
        // 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             *     @param consumerTag 消费者标签
             *     @param envelope 消息信封,有四个属性:deliveryTag投递标签,redeliver是否重新投递,exchange交换机,routingKey路由键
             *     @param properties 基本属性
             *     @param body 消息内容
             * */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer2消费了消息 ===> " + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 手动执行确认
                    /**
                     *     @param deliveryTag 投递标签
                     *     @param multiple 是否确认多个消息,true会确认该标签以前的,被这个消费者接受的多个信息
                     * */
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };
        // 消费者监听
        /**
         *     autoAck = true :自动确认模式,一旦rabbitmq将消息分发给消费者,就会从内存中删除。可能存在消息分发给消费者后,
         *             消费者挂了,导致消息丢失。
         *     autoAck = false :手动确认,如果一个消费者挂了,就会把消息交付给其他消费者。
         *             消费者手动发一个消息应答给rabbitmq,意义说这个消息已经处理完了,你可以删除了,此时rabbitmq就将消息从内存中删除
         * */
        boolean autoAck = false;        // 必须关闭自动确认消费
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

 

 

3、发布者/订阅者模式

相比于上面两种模式,发布者/订阅者模式要求必须有Exchange交换机。

生产者将消息发给exchange交换机,交换机根据不同的策略将消息转发给队列,消费者订阅队列,获取消息进行消费。

这种模式引入了交换机,同时也带出接下来的几种模式routing(路由模式)和topic(主体模式)

 

exchange:交换机。一方面接收消费者的消息,另一方面将消息发给绑定的队列
交换机有四种模式:
   fanout:分散模式,也就是不处理路由键,选择广播消息,只要和该交换机绑定的队列都可以收到消息。
   direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。
   topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。
   header:header模式,很少用,通过发送消息是的header来进行匹配

 

我们这里所说的发布者/订阅者模式,其实就是用了fanout交换机,进行广播消息。

 

rabbitmq学习笔记_第4张图片

 rabbitmq学习笔记_第5张图片

 

 

生产者     Provider

package com.hongcheng.rabbitmq_demo.pb;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Provider {
    
    private final static String exchange_name = "exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        // 声明交换机
        /**
         *     交换机是没有存储数据的能力的,rabbitmq里面只有队列才有存储数据的能力。
         *     这里我们并没有给交换机绑定队列,所以数据肯定会丢失。
         * */
        /**
         *     exchange:交换机。一方面接收消费者的消息,另一方面将消息发给绑定的队列
         *     交换机有四种模式:
         *         fanout:分散模式,也就是不处理路由键,选择广播消息,只要和该交换机绑定的队列都可以收到消息。
         *         direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。
         *         topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。
         *         header:header模式,很少用,通过发送消息是的header来进行匹配
         * */
        channel.exchangeDeclare(exchange_name, "fanout");
        
        // 发送消息
        String msg = "这里是交换机";
        // 发送消息去交换机,而不是队列
        channel.basicPublish(exchange_name, "", null, msg.getBytes(Charset.forName("utf-8")));
        
        System.err.println("成功发送消息 ==> " + msg);
        
        channel.close();
        
        connection.close();
    }
}

 

 

消费者1    Consumer1

package com.hongcheng.rabbitmq_demo.pb;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer1 {
    
    private final static String queue_name = "exchange_fanout_queue_1";
    
    private final static String exchange_name = "exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
// 绑定队列和交换机,路由键不填 channel.queueBind(queue_name, exchange_name,
"");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer1消费了消息 ===> " + msg); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }

 

 

消费者2    Consumer2

package com.hongcheng.rabbitmq_demo.pb;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer2 {
private final static String queue_name = "exchange_fanout_queue_2";
    
    private final static String exchange_name = "exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
// 绑定队列和交换器,路由键不填 channel.queueBind(queue_name, exchange_name,
"");// 创建消费者 DefaultConsumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException { String msg = new String(body,"utf-8"); System.err.println("Consumer2消费了消息 ===> " + msg); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }; // 消费者监听 boolean autoAck = true; channel.basicConsume(queue_name, autoAck, consumer); // 注意,不要关闭 } }

 

 

 

4、路由模式

路由模式是基于direct类型的Exchange衍生出来的

direct:路由模式,只有完全匹配路由键的队列才能收到消息,一个队列可以和一个交换机有多个routKey路由键。

消息的路由键和 exchange与queue绑定的路由键必须完全匹配,交换机才会把消息丢给队列

rabbitmq学习笔记_第6张图片

rabbitmq学习笔记_第7张图片

 

 

生产者     Provider

package com.hongcheng.rabbitmq_demo.routing;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Provider {
    
    private final static String exchange_name = "exchange_direct";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        // 声明交换机
        channel.exchangeDeclare(exchange_name, "direct");
        
        // 发送消息
//        String msg = "这里是direct交换机 ====> error消息";        // Consumer1和Consumer2都收到
//        String msg = "这里是direct交换机 ====> info消息";        // 仅Consumer2都收到
        String msg = "这里是direct交换机 ====> warning消息";    // 仅Consumer2都收到
        
//        String routKey = "error";
//        String routKey = "info";
        String routKey = "warning";
        
        channel.basicPublish(exchange_name, routKey, null, msg.getBytes(Charset.forName("utf-8")));
        
        System.err.println("成功发送消息 ==> " + msg);
        
        channel.close();
        
        connection.close();
    }
}

 

 

消费者1     Consumer1

package com.hongcheng.rabbitmq_demo.routing;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer1 {
    
    private final static String queue_name = "exchange_direct_queue_1";
    
    private final static String exchange_name = "exchange_direct";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 队列绑定交换机
        channel.queueBind(queue_name, exchange_name, "error");// 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer1消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
        };
        // 消费者监听
        boolean autoAck = true;        
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

消费者2     Consumer2

package com.hongcheng.rabbitmq_demo.routing;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer2 {
    
    private final static String queue_name = "exchange_direct_queue_2";
    
    private final static String exchange_name = "exchange_direct";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 队列和交换机绑定,可以帮多个
        channel.queueBind(queue_name, exchange_name, "error");
        channel.queueBind(queue_name, exchange_name, "info");
        channel.queueBind(queue_name, exchange_name, "warning");// 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer2消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
        };
        // 消费者监听
        boolean autoAck = true;        
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

 

 

5、主题模式

主题模式是基于topic类型的Exchange交换器出来的。

主题模式相比于路由模式的区别是:主体模式可以对路由键进行模糊匹配,而路由模式只能对路由键进行完全匹配

topic:主题模式,用.将路由键分成不同的主题,只要路由键能和队列的模式进行匹配上,就分发消息。#表示匹配一个或多个主题,*表示只匹配一个主题。

主题模式运行路由键按“.”(英文点)进行划分,分成多个主题topic,队列与交换器进行绑定时,绑定路由键可以使用通配符。

#  :表示任意个topic

*   :表示1个topic

例如:

exchange  --------------  A.B.#.F.*.K ------------ queue

消息1(A.B.C.D.E.F.T.K)可以发到队列里面

消息2(A.B.C)不能发到队列里面

消息3(A.B.C.F.E.K)能发到队列里面

消息4(A.B.C.F.E)不能发到队列面

rabbitmq学习笔记_第8张图片

 

 rabbitmq学习笔记_第9张图片

 

 生产者     Provider

package com.hongcheng.rabbitmq_demo.topic;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Provider {
    
    private final static String exchange_name = "exchange_topic";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        // 声明交换机
        channel.exchangeDeclare(exchange_name, "topic");
        
//        Consumer1 ==> A.#.D        *.C
//        Consumer2 ==> A.B.C.D      A.*.D.#
        
        // 发送消息
//        String msg = "这里是topic交换机 ====> A.B.C.D";    // Consumer2和Consumer1都收到
//        String msg = "这里是topic交换机 ====> A.C";        // 仅Consumer1都收到
//        String msg = "这里是topic交换机 ====> A.B.D.E.F";    // 仅Consumer2都收到
        String msg = "这里是topic交换机 ====> A.B.D";        // Consumer1和Consumer2都收到
        
//        String routKey = "A.B.C.D";
//        String routKey = "A.C";
//        String routKey = "A.B.D.E.F";
        String routKey = "A.B.D";
        
        channel.basicPublish(exchange_name, routKey, null, msg.getBytes(Charset.forName("utf-8")));
        
        System.err.println("成功发送消息 ==> " + msg);
        
        channel.close();
        
        connection.close();
    }
}

 

消费者1     consumer1

 

package com.hongcheng.rabbitmq_demo.topic;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer1 {
    
    private final static String queue_name = "exchange_topic_queue_1";
    
    private final static String exchange_name = "exchange_topic";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 队列绑定交换机
        channel.queueBind(queue_name, exchange_name, "A.#.D");
        channel.queueBind(queue_name, exchange_name, "*.C");
// 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer1消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
        };
        // 消费者监听
        boolean autoAck = true;        
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

消费者2     consumer2

package com.hongcheng.rabbitmq_demo.topic;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Consumer2 {
    
    private final static String queue_name = "exchange_topic_queue_2";
    
    private final static String exchange_name = "exchange_topic";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        
        Channel channel = connection.createChannel();
        
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 队列和交换机绑定
        channel.queueBind(queue_name, exchange_name, "A.B.C.D");
        channel.queueBind(queue_name, exchange_name, "A.*.D.#");// 创建消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String msg = new String(body,"utf-8");
                System.err.println("Consumer2消费了消息 ===> " + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } 
            }
        };
        // 消费者监听
        boolean autoAck = true;     
        channel.basicConsume(queue_name, autoAck, consumer);
        // 注意,不要关闭
    }
}

 

rabbitmq事务

rabbitmq像普通数据库一样,也支持事务。rabbitmq的事务是指消费者发送消息去rabbitmq,要么全部发送成功,要么全部失败。

rabbitmq的事务依赖三个方法:

  channel.txSelect(); 开启事务

  channel.txCommit(); 提交事务

  channel.txRollback(); 回滚事务

 

rabbitmq的事务只针对生产者发布消息的时候,所以代码只在生产者上有变化,消费者没变化

 

生产者     Provider

package com.hongcheng.rabbitmq_demo.transitional;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

/**
 *     rabbitmq支持事务,他的事务是指消费者发送消息去rabbitmq,要么全部发送成功,要么全部失败。
 *     主要依靠下面三个方法:
 *         channel.txSelect();        开启事务
 *         channel.txCommit();        提交事务
 *         channel.txRollback();    回滚事务
 * */
public class Provider {
    private static final String queue_name = "hongcheng_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 从连接中获取一个通道channel
        Channel channel = connection.createChannel();
        
        // 声明一个队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        try {
            // 启动事务
            channel.txSelect();
            
            String msg = "大傻子";
            
            // 发送消息
            channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
            
       // 模拟程序出错
int a = 1 / 0; // 提交事务 channel.txCommit(); } catch (IOException e) { // 回滚事务 channel.txRollback(); } channel.close(); connection.close(); } }

 

消费者      Consumer

package com.hongcheng.rabbitmq_demo.transitional;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

public class Consumer {
    
    private static final String queue_name = "hongcheng_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        // 获取连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 创建通道
        Channel channel = connection.createChannel();
        
        // 声明队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 定义一个消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 消费信息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String string = new String(body,"utf-8");
                System.err.println("消息队列收到的消息 ==>" + string);
            }
        };
        // 监听队列
        channel.basicConsume(queue_name, true,consumer);
    }
}

 

 

生产者确认消息到达队列,confirm机制:

生产者将消息发送给rabbitmq后,消息究竟有没有到目标队列?生产者有时候是需要确定的,如果没有发到目标队列,就要重传或者别的操作。

消息发送后会有3种情况:

1、找不到交换器。

2、找不到队列。

3、成功到达目标队列。

消息confirm确认机制也是可以代替事务机制。如果生产者收不到mq的确认消息,就重传。

不过有一点需要注意的是:

  生产者收不到消息可能是因为网络问题,实际上mq是把消息发给队列了的,这时候生产者重新发送消息,就会导致消息重复。这里就要求消费者那边处理消息必须是幂等的。所谓幂等就是多次用同样的数据执行同样的操作,结果始终相同。例如删除操作,同样的数据也只能删除一条。但是如果是新增操作,就可能增加了两条或多条。

 

另外一点就是一个channel里面,confirm机制不能和事务机制同时存在。

confirm确认有3种写法。

  1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认
  2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。
  3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。

 

confirm确认只针对生产者,所以这里我只给出生产者的代码。

1、普通串行阻塞单条确认

package com.hongcheng.rabbitmq_demo.confirm;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
 *     rabbitmq的confirm确认模式:
 *             生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始)
 *         一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标
 *         队列。
 *             如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。
 * 
 *         ACK确认:     指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。
 *         confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。
 * 
 *     confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。
 * 
 *     confirm确认有三种方法:
 *         1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认
 *         2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。
 *                 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。
 *         3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。
 * */
public class Provider {
    private static final String queue_name = "hongcheng_confirm";
    
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 从连接中获取一个通道channel
        Channel channel = connection.createChannel();
        
        // 声明一个队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 生产者调用confirmSelect方法将channel设置成confirm模式。
        /**
         *     注意,channel不同同时txSelect()和confirmSelect()方法。
         *     channel要么是confirm模式,要么是事务模式,只能二选一。
         * */
        channel.confirmSelect();
        
        String msg = "大傻子";
        
        /**
         *     关于1、普通串行阻塞单条确认和2、普通串行阻塞批量确认的唯一区别就是:
         *         普通串行阻塞批量确认是通过循环一次次发送消息的。
         * 
         *     channel.waitForConfirms()本身就是对你上次发送的消息进行确认,至于你上次发了多少消息,你自己觉得,如果你发的多,那就确认多条消息。
         * */
        
        // 发送消息
        channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));/**
         *     阻塞接收mq的确认信息
         * */
        if(!channel.waitForConfirms()) {
            System.err.println("消息发送失败");
        }else {
            System.err.println("消息发送成功");
        }
        
        channel.close();
        connection.close();
    }
}

 

 

2、普通串行阻塞批量确认

package com.hongcheng.rabbitmq_demo.confirm;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
 *     rabbitmq的confirm确认模式:
 *             生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始)
 *         一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标
 *         队列。
 *             如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。
 * 
 *         ACK确认:     指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。
 *         confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。
 * 
 *     confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。
 * 
 *     confirm确认有三种方法:
 *         1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认
 *         2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。
 *                 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。
 *         3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。
 * */
public class Provider {
    private static final String queue_name = "hongcheng_confirm";
    
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 从连接中获取一个通道channel
        Channel channel = connection.createChannel();
        
        // 声明一个队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 生产者调用confirmSelect方法将channel设置成confirm模式。
        /**
         *     注意,channel不同同时txSelect()和confirmSelect()方法。
         *     channel要么是confirm模式,要么是事务模式,只能二选一。
         * */
        channel.confirmSelect();
        
        String msg = "大傻子";
        
        /**
         *     关于1、普通串行阻塞单条确认和2、普通串行阻塞批量确认的唯一区别就是:
         *         普通串行阻塞批量确认是通过循环一次次发送消息的。
         * 
         *     channel.waitForConfirms()本身就是对你上次发送的消息进行确认,至于你上次发了多少消息,你自己觉得,如果你发的多,那就确认多条消息。
         * */// 批量发送
        for (int i = 0; i < 5; i++) {
            channel.basicPublish("", queue_name, null, msg.getBytes(Charset.forName("utf-8")));
        }
        
        /**
         *     阻塞接收mq的确认信息
         * */
        if(!channel.waitForConfirms()) {
            System.err.println("消息发送失败");
        }else {
            System.err.println("消息发送成功");
        }
        
        channel.close();
        connection.close();
    }
}

 

 

3、异步模式

package com.hongcheng.rabbitmq_demo.confirm.asyn;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;

import com.hongcheng.rabbitmq_demo.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
/**
 *     rabbitmq的confirm确认模式:
 *             生产者将channel设置成confirm模式,一旦信道进入confirm模式,所有在该信道上的发布的消息都会被指派一个唯一ID(从1开始)
 *         一旦消息被投递单所匹配的队列后,mq就会发送一个确认信息给生产者(包含消息的唯一ID),这使得生产者得知消息已经正确到达目标
 *         队列。
 *             如果消息是持久化的,那么确认消息将会在消息写入磁盘后发出。mq回传给生产者的确认消息中deliveryTag就是发布的消息的唯一ID。
 * 
 *         ACK确认:     指消费者消费完消息后,由消费者发出,mq接收,用于通知mq删除消息的。
 *         confirm确认:指生产者发布消息后,有mq发出,生产者接收,用于通知生产者消息发送成功的。
 * 
 *     confirm模式可以是异步的,通过回调来处理确认消息。也可以是同步的。
 * 
 *     confirm确认有三种方法:
 *         1、普通串行阻塞单条确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认
 *         2、普通串行阻塞批量确认:channel.waitForConfirms(),会一直阻塞,直到收到mq的确认。
 *                 返回true表示之前发的那一批消息都ok;false表示之前发的那一堆消息存在某些信息有问题,至于是哪些消息,有多少消息有问题是不确定的。
 *         3、异步模式:rabbitmq提供了一个确认监听器,可以监听消息确认成功ack,和消息确认失败nack,需要自己维护自己发送过得消息。
 * */
public class Provider {
    private static final String queue_name = "hongcheng_confirm";
    
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        
        // 从连接中获取一个通道channel
        Channel channel = connection.createChannel();
        
        // 声明一个队列
        channel.queueDeclare(queue_name, false, false, false, null);
        
        // 生产者调用confirmSelect方法将channel设置成confirm模式。
        /**
         *     注意,channel不同同时txSelect()和confirmSelect()方法。
         *     channel要么是confirm模式,要么是事务模式,只能二选一。
         * */
        channel.confirmSelect();
        
        // 保存未确认的消息标识,消息标识从1开始,依次递增,channel发送的第一条消息的deliveryTag就是1,第二条消息deliveryTag就是2,channel只是当前的channel
        SortedSet sortedSet = Collections.synchronizedSortedSet(new TreeSet());
        
        // 添加消息确认监听器
        channel.addConfirmListener(new ConfirmListener() {
            /***
             *     确认失败监听器
             *     @param deliveryTag 消息标识,消息标识从1开始,依次递增,channel发送的第一条消息的deliveryTag就是1,第二条消息deliveryTag就是2,channel只是当前的channel
             *     @param multiple 是否确认该标识及其前面所有信息
             * */
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if(multiple) {
                    System.err.println("------handleNack-----multiple(true)-------");
                    sortedSet.headSet(deliveryTag + 1).clear();
                }else {
                    System.err.println("------handleNack-----multiple(false)-------");
                    sortedSet.remove(deliveryTag);
                }
            }
            /***
             *     确认成功监听器
             *     @param deliveryTag 消息标识
             *     @param multiple 是否确认该标识及其前面所有信息
             * */
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if(multiple) {
                    System.err.println("------handleAck-----multiple(true)-------");
                    sortedSet.headSet(deliveryTag + 1).clear();
                }else {
                    System.err.println("------handleAck-----multiple(false)-------");
                    sortedSet.remove(deliveryTag);
                }
            }
        });
        
        String msg = "大傻子";

        // 批量发送
        for (int i = 0; i < 500; i++) {
            String temp = msg + i;
            long nextPublishSeqNo = channel.getNextPublishSeqNo();
            channel.basicPublish("", queue_name, null, temp.getBytes(Charset.forName("utf-8")));
            sortedSet.add(nextPublishSeqNo);
        }
        
        
        channel.close();
        connection.close();
    }
}

 

rabbitmq的客户端基本使用就这样了,接下来我们看一下上面用过的一些rabbitmq客户端api

ConnectionFactory.class

用于创建rabbitmq的连接Connection的工厂,里面可以进行各种参数的配置

下面的代码是最常用的:

 

        // 定义一个连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        
        // 设施服务地址
        connectionFactory.setHost("127.0.0.1");
        
        // 设置端口
        connectionFactory.setPort(5672);
        
        // 设置vhost虚拟主机
        connectionFactory.setVirtualHost("hongchengHost");
        
        // 设置用户名
        connectionFactory.setUsername("admin");
        
        // 设置密码
        connectionFactory.setPassword("admin");
        
        Connection connection = connectionFactory.newConnection();    

 

 

 

 

Connection.class

与rabbitmq进行通信的连接,他的文档告诉了我们怎样快速获取一个connection实例,是不是和我们上面的代码一样

rabbitmq学习笔记_第10张图片

 

 

 这是Connection的基本方法

 

rabbitmq学习笔记_第11张图片

 

 

 

 

 

 Channel.class

表示通道,channel是我们直接与rabbitmq进行通信的桥梁。里面包含了大量方法。

rabbitmq学习笔记_第12张图片

 

 

 

 

 rabbitmq学习笔记_第13张图片

 rabbitmq学习笔记_第14张图片

 

 rabbitmq学习笔记_第15张图片

 

 

 

 

DefaultConsumer.class

默认消费者回调,用于通过回调来异步处理消息。

rabbitmq学习笔记_第16张图片

     // 定义一个消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             *     接收到mq发的消息就会调用
             * */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
                    throws IOException {
                String string = new String(body,"utf-8");
                System.err.println("消息队列收到的消息 ==>" + string);
             }
            /**
             *     channel.basicConsume(queue_name, true,consumer);被调用时才执行的回调
             * */
            @Override
            public void handleConsumeOk(String consumerTag) {
                System.err.println("handleConsumeOk ===> " + consumerTag);
            }
            /**
             *     channel.basicCancel(consumerTag);被调用后才执行的回调
             * */
            @Override
            public void handleCancelOk(String consumerTag) {
                System.err.println("handleCancelOk ===> " + consumerTag);
            }
            /**
             *     这个实在不知道什么时候调用
             * */
            @Override
            public void handleCancel(String consumerTag) throws IOException {
                System.err.println("handleCancel ===> " + consumerTag);
            }
            /**
             *     当connection.close()或者channel.close()或者channel.abort();;被调用时才执行的回调
             * */
            @Override
            public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) {
                System.err.println("handleShutdownSignal ===> " + consumerTag);
            }
            /**
             *     当重新获取但之前已经发给消费者,但消费者为ack确认的消息,执行此回调
             * */
            @Override
            public void handleRecoverOk(String consumerTag) {
                System.err.println("handleRecoverOk ===> " + consumerTag);
            }
            
        };

 

 

springboot整合rabbitmq

基本使用

先上pom依赖


    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">
    4.0.0

    com.hongcheng
    rabbitmq-demo
    0.0.1-SNAPSHOT
    jar

    rabbitmq-demo
    http://maven.apache.org
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.0.RELEASE
          
    
    
        UTF-8
        1.8
        1.8
        1.8
    

    

        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        

    
    

application.yml

server:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-demo-springboot
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: admin
    password: admin
    #虚拟host 可以不设置,使用server默认host
    virtual-host: hongchengHost

 

RabbitConfig.java

package com.hongcheng.rabbitmq_demo.springboot.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 *     spring按照rabbitmq抽象出四个组件:Queue、Exchange、Binding、Message。
 *     我们需要配置Queue、Exchange、Binding
 * */
@Configuration
public class RabbitConfig {
    
    
    @Bean
    public Queue getQueue() {
        /**
         *  private final String name;            队列名

            private final boolean durable;        是否持久化,默认是false,持久化队列:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效

            private final boolean exclusive;    exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable

            private final boolean autoDelete;    autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。

            private volatile String actualName;    队列实际名,如果队列名传入为空,就自动创建一个默认名
            
            Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)
         * */
        return new Queue("springboot_queue_direct",false,false,false,null);
    }
 
    
    @Bean
    public DirectExchange getDirectExchange() {
        /**
         *  private final String name;          交换机名

            private final boolean durable;        是否持久化,默认是false,持久化交换器:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效

            private final boolean autoDelete;    autoDelete:是否自动删除,当没有生产者使用此交换机,该会自动删除。
            
            5中交换器类型:
                direct:DirectExchange
                topic:HeadersExchange
                fanout:FanoutExchange
                headers:TopicExchange
                system:
                自定义:CustomExchange
                
            AbstractExchange(String name, boolean durable, boolean autoDelete, Map arguments) 
         * */
        return new DirectExchange("springboot_exchange_direct",false,false,null);
    }
 
    @Bean
    public Binding bindingDirect() {
        /***
         *  Binding 用于将交换机和队列进行绑定
         * 
         *  private final String destination;    目标对象名,可以是交换机,也可以是队列        

            private final String exchange;        源交换机名

            private final String routingKey;    路由键

            private final DestinationType destinationType;    目标对象类型,QUEUE、EXCHANGE
            
            rabbitmq支持交换机与队列/交换机进行绑定。
            
            当Exchange和Exchange绑定时,可以形成复杂的拓扑结构。
            
            Binding(String destination, DestinationType destinationType, String exchange, String routingKey, Map arguments)
            
            创建Binding也可以使用BindingBuilder来构建
            
            BindingBuilder.bind(目标对象,这里可以是队列,也可以是交换器).to(源交换机名,这里只能是交换器).with(路由键)
            
            如果你要绑定多个路由键,就创建多个Binding吧
         * */
        return BindingBuilder.bind(getQueue()).to(getDirectExchange()).with("rabbitmq.springboot.direct");
    }
 
 
 
}

 

生产者

package com.hongcheng.rabbitmq_demo.springboot.controller;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/rabbitmq-springboot")
public class SendMessageController {
    
    @Autowired
    RabbitTemplate rabbitTemplate;
    
    @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct
        rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map);
        return "ok";
    }
    
    @GetMapping("/sendDirectMessage/{num}")
    public String sendDirectMessage(@PathVariable("num")int num) {
        for(int i = 0; i< num;i++) {
            String messageId = String.valueOf(UUID.randomUUID());
            String messageData = "test message, hello ==>" + i;
            String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            Map map=new HashMap<>();
            map.put("messageId",messageId);
            map.put("messageData",messageData);
            map.put("createTime",createTime);
            //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct
            rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map);
        }
        return "ok";
    }
}

 

 

消费者

package com.hongcheng.rabbitmq_demo.springboot.listener;

import java.util.Map;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component

public class DirectRabbitListener2 {
    
    /**
     *     @param testMessage springboot整合rabbitmq后,是可以发送java对象的,由spring自己序列化对象,默认是使用jdk序列化
     *             也可以自己更换成Jackson2JsonMessageConverter
     *         我们也可以改成用org.springframework.amqp.core.Message来接收原始的消息对象,如public void process( Message testMessage)
     * */
    @RabbitListener(queues = "springboot_queue_direct")
    @SuppressWarnings("rawtypes")
    public void process( Map testMessage) {
        System.out.println("DirectReceiver消费者2 ==> 收到消息  : " + testMessage.toString());
    }
}

 

 

 

相比以前直接使用rabbitmq的客户端,整合springboot后消费者和生产者更加简单了,代码量更少了。同时也将交换机、队列、绑定提取到配置类了。

 

spring按照rabbitmq抽象出四个组件:Queue、Exchange、Binding、Message。

下面我们将一一探讨这四个东东

1、Queue队列

rabbitmq学习笔记_第17张图片

  private final String name; 队列名

  private final boolean durable; 是否持久化,默认是false,持久化队列:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效

  private final boolean exclusive; exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable

  private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。

  private volatile String actualName; 队列实际名,如果队列名传入为空,就自动创建一个默认名

构造函数   =====>      Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map arguments)

 

2、Exchange交换器

他的类结构:

rabbitmq学习笔记_第18张图片

 

 

 基本方法:

rabbitmq学习笔记_第19张图片

  private final String name; 交换机名

  private final boolean durable; 是否持久化,默认是false,持久化交换器:会被存储在磁盘上,mq重启时仍然存在;非持久化仅在当前连接有效

  private final boolean autoDelete; autoDelete:是否自动删除,当没有生产者使用此交换机,该会自动删除。

  5种交换器类型

  交换器类型  :  实现类
  direct     :  DirectExchange
  topic      :  HeadersExchange
  fanout      :  FanoutExchange
  headers     :  TopicExchange
  自定义       :  CustomExchange

  构造函数       ======>    AbstractExchange(String name, boolean durable, boolean autoDelete, Map arguments)

 

 3、Binding绑定

rabbitmq学习笔记_第20张图片

 

 

 

  Binding 用于将交换机和队列进行绑定


    private final String destination; 目标对象名,可以是交换机,也可以是队列

    private final String exchange; 源交换机名

    private final String routingKey; 路由键

    private final DestinationType destinationType; 目标对象类型,QUEUE、EXCHANGE

  rabbitmq支持交换机与队列/交换机进行绑定。

  当Exchange和Exchange绑定时,可以形成复杂的拓扑结构。

  构造函数      =======>          Binding(String destination, DestinationType destinationType, String exchange, String routingKey, Map arguments)

  创建Binding也可以使用BindingBuilder来构建

  BindingBuilder.bind(目标对象,这里可以是队列,也可以是交换器).to(源交换机名,这里只能是交换器).with(路由键)

  如果你要绑定多个路由键,就创建多个Binding吧

 

4、Message消息

rabbitmq学习笔记_第21张图片

 

 

 spring在发送消息给rabbitmq时,会带上许多属性一起发送,同时我们也可以直接发送java对象,由spring自己序列化对象,默认是使用jdk序列化,也可以自己更换成Jackson2JsonMessageConverter

rabbitmq学习笔记_第22张图片

 

 

 

rabbitmq学习笔记_第23张图片

 

 

之前我们用rabbitmq的客户端实现了5种模式,简单模式,工作队列模式、发布者/订阅者模式、路由模式、主题模式,其实换成springboot整合后也差不多

 

1、简单模式,不绑交换机,直连队列

RabbitConfig.java增加下面代码

   @Bean
    public Queue getQueue1() {
        // 普通队列,不绑定交换器
        return new Queue("springboot_queue1",false,false,false,null);
    }

 消费者增加下面代码

  @RabbitListener(queues = "springboot_queue1")
    public void process2( Message message) {
        System.out.println("springboot_queue1队列 ==> 收到消息  : " + new String(message.getBody()));
    }

生产者增加下面代码,发送消息时,exchange填空,路由键填队列名

@GetMapping("/sendMessage")
    public String sendMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "普通队列,不绑定交换器";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
       //将消息携带绑定键值:springboot_queue1 发送到队列springboot_queue1
        rabbitTemplate.convertAndSend("", "springboot_queue1", map);
        return "ok";
    }

 

2、工作队列模式

RabbitConfig.java增加下面代码

  @Bean
    public Queue getQueue_work() {
        // 工作队列模式,一个队列多个消费者
        return new Queue("springboot_queue_work",false,false,false,null);
    }

 消费者增加下面代码

  @RabbitListener(queues = "springboot_queue_work")
    public void process5( Message message) {
        System.out.println("springboot_queue_work消费者1 ==> 收到消息  : " + new String(message.getBody()));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    @RabbitListener(queues = "springboot_queue_work")
    public void process6( Message message) {
        System.out.println("springboot_queue_work消费者2 ==> 收到消息  : " + new String(message.getBody()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

生产者增加下面代码

  @GetMapping("/sendWorkMessage")
    public String sendWorkMessage() {
        for(int i = 0; i< 20;i++) {
            String messageId = String.valueOf(UUID.randomUUID());
            String messageData = "工作队列,绑定Fanout交换器";
            String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            Map map=new HashMap<>();
            map.put("messageId",messageId);
            map.put("messageData",messageData);
            map.put("createTime",createTime);
            //将消息携带绑定键值: springboot_queue_work 发送到队列springboot_queue_work
            rabbitTemplate.convertAndSend("", "springboot_queue_work", map);
        }
        return "ok";
    }

 

 

 

3、发布者/订阅者模式,绑定Fanout类型交换器

RabbitConfig.java增加下面代码

    @Bean
    public Queue getQueue_fanout1() {
        // 广播队列,绑定广播交换器
        return new Queue("springboot_queue_fanout1",false,false,false,null);
    }
    @Bean
    public Queue getQueue_fanout2() {
        // 广播队列,绑定广播交换器
        return new Queue("springboot_queue_fanout2",false,false,false,null);
    }
    @Bean
    public FanoutExchange getFanoutExchange() {
        // 广播队列,绑定广播交换器
        return new FanoutExchange("springboot_exchange_fanout",false,false,null);
    }
    @Bean
    public Binding bindingFanout1() {
        // 广播队列,绑定广播交换器
        return BindingBuilder.bind(getQueue_fanout1()).to(getFanoutExchange());
    }
    @Bean
    public Binding bindingFanout2() {
        // 广播队列,绑定广播交换器
        return BindingBuilder.bind(getQueue_fanout2()).to(getFanoutExchange());
    }

 消费者增加下面代码

  @RabbitListener(queues = "springboot_queue_fanout2")
    public void process3( Message message) {
        System.out.println("springboot_queue_fanout2队列 ==> 收到消息  : " + new String(message.getBody()));
    }
    
    @RabbitListener(queues = "springboot_queue_fanout1")
    public void process4( Message message) {
        System.out.println("springboot_queue_fanout1队列 ==> 收到消息  : " + new String(message.getBody()));
    }

生产者增加下面代码

  @GetMapping("/sendFanoutMessage")
    public String sendFanoutMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "工作队列,绑定Fanout交换器";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息不携带绑定键, 发送到交换机springboot_exchange_fanout
        rabbitTemplate.convertAndSend("springboot_exchange_fanout", "", map);
        return "ok";
    }

 

 

4、路由模式,绑定direct类型交换器

代码和发布者/订阅者模式类似,只是交换器换成DirectExchange,路由键换成完整的键值

RabbitConfig.java增加下面代码

   @Bean
    public Queue getQueue() {
        return new Queue("springboot_queue_direct",false,false,false,null);
    }
    
    @Bean
    public DirectExchange getDirectExchange() {
        return new DirectExchange("springboot_exchange_direct",false,false,null);
    }
 
    @Bean
    public Binding bindingDirect() {
        return BindingBuilder.bind(getQueue()).to(getDirectExchange()).with("rabbitmq.springboot.direct");
    }

 

 消费者增加下面代码

  @RabbitListener(queues = "springboot_queue_direct")
    public void process( Message message) {
        System.out.println("springboot_queue_direct队列 ==> 收到消息  : " + new String(message.getBody()));
    }

 

生产者增加下面代码

  @GetMapping("/sendDirectMessage")
    public String sendDirectMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "你还,rabbitmq!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //将消息携带绑定键值:rabbitmq.springboot.direct 发送到交换机springboot_exchange_direct
        rabbitTemplate.convertAndSend("springboot_exchange_direct", "rabbitmq.springboot.direct", map);
        return "ok";
    }

 

 

5、主题模式,绑定topic类型交换器

代码和路由模式类似,只是交换器换成TopicExchange,路由键换成完整的键值

RabbitConfig.java增加下面代码

   @Bean
    public Queue getQueue_Topic() {
        return new Queue("springboot_queue_topic",false,false,false,null);
    }
    
    @Bean
    public TopicExchange getTopicExchange() {
        return new TopicExchange("springboot_exchange_topic",false,false,null);
    }
 
    @Bean
    public Binding bindingTopic() {  return BindingBuilder.bind(getQueue_Topic()).to(getTopicExchange()).with("#.topic"); }

 消费者增加下面代码

  @RabbitListener(queues = "springboot_queue_topic")
    public void process7( Message message) {
        System.out.println("springboot_queue_topic队列 ==> 收到消息  : " + new String(message.getBody()));
    }

生产者增加下面代码

  @GetMapping("/sendTopicMessage")
    public String sendTopicMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "你还,rabbitmq!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map map=new HashMap<>(); map.put("messageId",messageId); map.put("messageData",messageData); map.put("createTime",createTime); //将消息携带绑定键值:rabbitmq.springboot.topic 发送到交换机springboot_exchange_topic rabbitTemplate.convertAndSend("springboot_exchange_topic", "rabbitmq.springboot.topic", map); return "ok"; }

 

 

前面使用rabbitmq的客户端时我们用过confirm模式,在spring中如何实现呢?

application.yml增加

server:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-demo-springboot
  #配置rabbitMq 服务器
  rabbitmq:
    host: 114.115.255.201
    port: 5672
    username: admin
    password: hongcheng
    #虚拟host 可以不设置,使用server默认host
    virtual-host: hongchengHost

    #确认消息已发送到交换机(Exchange)
    publisher-confirms: true
    #确认消息已发送到队列(Queue)
    publisher-returns: true

 

RabbitConfig.java增加下面代码

  @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        // 消息序列化,这个你可以加也可以用默认的jdk序列化,为了好看我用json序列化
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);
 
        /**
         *     总体来说,消息发送有四种情况:
         *      ①消息推送到server,但是在server里找不到交换机。 
                ②消息推送到server,找到交换机了,但是没找到队列。 
                ③消息推送到sever,交换机和队列啥都没找到。 
                ④消息推送成功。 
            
             1.如果消息没有到exchange,则ConfirmCallback回调,ack=false。
             2.如果消息到达exchange,则ConfirmCallback回调,ack=true。
             3.exchange到queue成功,则不回调ReturnCallback。
             4.exchange到queue失败,则回调ReturnCallback(需设置mandatory=true,否则不回调,消息就丢了)
             5.如果消息成功到达exchange和queue,两个回调都不调用
                
            看配置文件
            #确认消息已发送到交换机(Exchange)
            publisher-confirms: true
            #确认消息已发送到队列(Queue)
            publisher-returns: true
         * */
        
        // Confirm是消息发到交换机时做的检测
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                /**
                 *     correlationData,是会返回为null的,也就是说我们能知道消息发送到exchange失败,但是具体是哪个消息失败,我们不知道。
                 *     所以需要我们在发送消息的时候将Message 和 CorrelationData手动绑定,并自己缓存起来,当ConfirmCallback被调用时在
                 *     手动找到响应的消息。
                 * */
                System.out.println("ConfirmCallback:     "+"相关数据:"+correlationData);
                System.out.println("ConfirmCallback:     "+"确认情况:"+ack);
                System.out.println("ConfirmCallback:     "+"原因:"+cause);
            }
        });
 
        // Return是消息发送到队列时做的检测
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                /**
                 *     Message是会返回的
                 * */
                System.out.println("ReturnCallback:     "+"消息:"+message);
                System.out.println("ReturnCallback:     "+"回应码:"+replyCode);
                System.out.println("ReturnCallback:     "+"回应信息:"+replyText);
                System.out.println("ReturnCallback:     "+"交换机:"+exchange);
                System.out.println("ReturnCallback:     "+"路由键:"+routingKey);
            }
        });
 
        return rabbitTemplate;
    }

 

 

spring整合rabbitmq使用事务

这里我只能找到的可以实现事务的方法只有rabbitmq客户端的事务三件套方法,但是要用rabbitTemplate创建一个新的channel,而且发送消息要用channel发送,不能用rabbitTemplate

@GetMapping("/sendWorkMessage")
    public String sendWorkMessage() throws IOException   {
        Channel channel = rabbitTemplate.getConnectionFactory().createConnection().createChannel(true);
        try {
            channel.txSelect();
            for(int i = 0; i< 20;i++) {
                String messageId = String.valueOf(UUID.randomUUID());
                String messageData = "工作队列,绑定Fanout交换器";
                String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                Map map=new HashMap<>();
                map.put("messageId",messageId);
                map.put("messageData",messageData);
                map.put("createTime",createTime);
                //将消息携带绑定键值: springboot_queue_work 发送到队列springboot_queue_work
                channel.basicPublish("", "springboot_queue_work", null, map.toString().getBytes());
                if( i == 8) {
                    int a = 1/0;
                }
            }
            channel.txCommit();
        }catch (Exception e) {
            e.printStackTrace();
            channel.txRollback();
        }
        return "ok";
    }

 

 

消费者限流。

如果mq有充足的消息,那么消费者每次都会预取一定数量的消息(默认250),当消费者处理消息缓慢时,可能会造成消息积压,同时增大消费者的内存。

此时我们可以减少预取的消息数量,缓解当前消费者的压力,同时也将消息分摊给其他性能更好的消费者。

另外如果一个队列有多个消费者,那这些消费者会轮流平分处理这些消息,不管谁性能好谁性能坏,所以很有必要进行消费者限流。

RabbitConfig.java增加下面代码:

   @Bean("myListenerFactory")
    public RabbitListenerContainerFactory getQueue_work(ConnectionFactory connectionFactory) {
        // 工作队列模式,一个队列多个消费者
        SimpleRabbitListenerContainerFactory simpleMessageListenerContainer = new SimpleRabbitListenerContainerFactory();
        simpleMessageListenerContainer.setConnectionFactory(connectionFactory);
        simpleMessageListenerContainer.setPrefetchCount(1);
        return simpleMessageListenerContainer;
    }

 

消费者代码(需要开启手动确认):

  @RabbitListener(queues = "springboot_queue_work",ackMode = "MANUAL",containerFactory = "myListenerFactory")
    public void process5( Message message,Channel channel) throws IOException {
        System.out.println("springboot_queue_work消费者1 ==> 收到消息  : " + new String(message.getBody()));
        try {
            Thread.sleep(1000);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
    @RabbitListener(queues = "springboot_queue_work",ackMode = "MANUAL",containerFactory = "myListenerFactory")
    public void process6( Message message,Channel channel) throws IOException {
        System.err.println("springboot_queue_work消费者2 ==> 收到消息  : " + new String(message.getBody()));
        try {
            Thread.sleep(2000);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

 

如果是使用rabbitmq客户端的话,就只需要调用basicQos(int prefetchSize, int prefetchCount, boolean global)这个方法,同时开启手动ack确认就好了。

但是整合了spring后用了这个方法也不起作用。

 

 

快速启动多个消费者,concurrency属性

消费者代码(concurrency表示消费者个数,只要写这个属性就可以了):

  @RabbitListener(queues = "springboot_queue_direct",ackMode = "MANUAL",concurrency = "4")
    public void process( Message message,Channel channel) throws IOException, ClassNotFoundException, InterruptedException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(message.getBody()));
        System.out.println("DirectReceiver消费者1 ==> 收到消息  : " + objectInputStream.readObject());
        // 拒绝消息,并让他重回队列,要注意不要死循环了
        // channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        // 错误信息,并让他重回队列,要注意不要死循环了
        // channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
        // 确认消息
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        Thread.sleep(1000);
    }

 

你可能感兴趣的:(rabbitmq学习笔记)