RabbitMQ初学教程一(消息队列,持久化,发布确认)

一:RabbitMQ核心部分

六种模式:简单模式、工作模式、发布订阅模式、路由模式、主题模式、发布确认模式所有内容都是基于六大核心而来的。
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第1张图片

二:RabbitMQ消费者和生产者代码

Producer.java

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

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

/**
 *生产者:发消息
 */
public class Producer {
    private final static String QUEUE_NAME ="hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setUsername("admin");
        factory.setPassword("12345");
        // channel实现了自动close接口 自动关闭 不需要显示关闭
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        /**
         * 生成一个队列
         * 1.队列名称
         * 2.队列里面的消息是否持久化默认消息存储在内存中
         * 3.该队列是否指供一个消费者进行消费 是否进行共享true 可以多个消费者消费
         * 4.是否自动删除 最后一个消费者断开连接以后 该队类是否自动删除 true 自动删除
         * 5.其他参数
         */
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        String message = "hello world";
        /**
         * 发动一个消息
         * 1.发送到那个交换机
         * 2.路由的key是哪个
         * 3.其他的参数消息
         * 4.发送消息的消息体
         *
         */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("消息发送完毕");
    }


}

consumer.java


import com.rabbitmq.client.*;

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

public class Consumer {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setUsername("admin");
        factory.setPassword("12345");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        System.out.println("等待接收消息.....");
        // 推送的消息如何进行消费的接口回调
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());
            System.out.println(message);
        };
        //取消消费的一个回调接口 如在消费的时候队列被删除掉了
        CancelCallback cancelCallback=(consumerTag)->{
            System.out.println("消息消费被中断");
        };

        /**
         * 消费者消费消息
         * 1.消费者那个队列
         * 2.消费成功之后是否自动应答 true代表自动应答 false 代表手动应答
         * 3.消费者未成功消费的回调
         * 4.消费者消费被中断的回调
         */
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

三:RabbitMq的消息应答

rabbitmq的消息应答机制:消费者在接收到消息并且处理该消息之后,告诉rabbitmq他已经处理了,rabbitmq才可以将该消息删除。这样保证了数据不再丢失。
rabbitmq的自动应答机制在企业开发中很少使用,大多使用手动应答。
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第2张图片
1.手动应答的方法
A.Channel.basicAck(用于肯定确认)
RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
B.Channel.basicNack(用于否定确认) C.Channel.basicReject(用于否定确认) 与 Channel.basicNack 相比少一个参数
不处理该消息了直接拒绝,可以将其丢弃了
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第3张图片
multiple 的 true 和 false 代表不同意思
true 代表批量应答 channel 上未应答的消息
比如说 channel 上有传送 tag 的消息 5,6,7,8 当前 tag 是 8 那么此时
5-8 的这些还未应答的消息都会被确认收到消息应答
false 同上面相比
只会应答 tag=8 的消息 5,6,7 这三个消息依然不会被确认收到消息应答
通常公司选用false
2.消息手动应答实战
使用一个生产者,两个消费者,给两个消费者设置不同的消费时间,发送两条消息,将时间较长的消费者停止,得出(某个消费者突然断开连接,消息不会被丢失,会被重新分配)

/**
 * 消息手动应答
 */
public class Task02 {

    private static final String  ACK_QUEUE_NAME = "ack_queue";;

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitmqUtils.getChannel();
        channel.queueDeclare(ACK_QUEUE_NAME,false,false,false,null);
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish("", ACK_QUEUE_NAME, null, message.getBytes("UTF-8"));
            System.out.println("生产者发出消息" + message);
        }
    }
}

/**
 * 消息手动应答
 */
public class Work02 {
    private static final String ACK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitmqUtils.getChannel();
        System.out.println("c1等待消息处理时间较短");
        //消息消费的时候如何处理消息
        DeliverCallback deliverCallback = (consumerTag,delivery)->{
            String message = new String(delivery.getBody());
            SleepUtils.sleep(1);
            System.out.println("接收到消息:"+message);
            /**
             * 1.消息标记tag
             * 2.是否批量应答未应答消息
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        boolean autoAck = false;
        channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        });
    }
}
/**
 * 消息手动应答
 */
public class Work03 {
    private static final String ACK_QUEUE_NAME = "ack_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        Channel channel = RabbitmqUtils.getChannel();
        System.out.println("c2等待消息处理时间较长");
        //消息消费的时候如何处理消息
        DeliverCallback deliverCallback = (consumerTag,delivery)->{
            String message = new String(delivery.getBody());
            SleepUtils.sleep(30);
            System.out.println("接收到消息:"+message);
            /**
             * 1.消息标记tag
             * 2.是否批量应答未应答消息
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        boolean autoAck = false;
        channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        });
    }
}

四:RabbitMq的持久化

1.队列持久化
之前我们创建的队列都是非持久化的,rabbitmq 如果重启的化,该队列就会被删除掉,如果
要队列实现持久化 需要在声明队列的时候把 durable 参数设置为持久化
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第4张图片

但是需要注意的就是如果之前声明的队列不是持久化的,需要把原先队列先删除,或者重新
创建一个持久化的队列,不然就会出现下面的错误
在这里插入图片描述
以下为控制台中持久化与非持久化队列的 UI 显示区
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第5张图片
在这里插入图片描述
2.消息持久化
消息实现持久化需要在消息生产者修改代码MessageProperties.PERSISTENT_TEXT_PLAIN 添加这个属性
在这里插入图片描述
将消息标记为持久化并不能完全保证不会丢失消息。尽管它告诉 RabbitMQ 将消息保存到磁盘,但是这里依然存在当消息刚准备存储在磁盘的时候 但是还没有存储完,消息还在缓存的一个间隔点。此时并没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。

3.不公平分发
在最开始的时候我们学习到 RabbitMQ 分发消息采用的轮训分发,但是在某种场景下这种策略并不是很好,比方说有两个消费者在处理任务,其中有个消费者 1 处理任务的速度非常快,而另外一个消费者 2 处理速度却很慢,这个时候我们还是采用轮训分发的化就会到这处理速度快的这个消费者很大一部分时间处于空闲状态,而处理慢的那个消费者一直在干活,这种分配方式在这种情况下其实就不太好,但是RabbitMQ 并不知道这种情况它依然很公平的进行分发。
为了避免这种情况,我们可以设置参数 channel.basicQos(1);
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第6张图片

4.设置预取值
预取值就是给一个消费者设置大小,以避免缓冲区里面无限制的未确认消息问题。。虽然自动应答传输消息速率是最佳的,但是,在这种情况下已传递但尚未处理的消息的数量也会增加,从而增加了消费者的 RAM 消耗(随机存取存储器)。该值定义通道上允许的未确认消息的最大数量。
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第7张图片

五:发布确认

1.发布确认的原理
生产者发送消息到队列中,队列得到该消息并且将该消息储存到磁盘上才表示该消息不会丢失。
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第8张图片
开启确认发布的方法:
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第9张图片

2.单个发布确认

public static  final int MESSAGE_COUNT = 1000;
 public static void publicMessageIndividually() throws Exception{
        Channel channel = RabbitmqUtils.getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        long begin = System.currentTimeMillis();
        for(int i=-0;i<MESSAGE_COUNT ;i++){
            String message = i + "";
            channel.basicPublish("", queueName, null, message.getBytes());
            //服务端返回 false 或超时时间内未返回,生产者可以消息重发
            boolean flag = channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功");
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "个单独确认消息,耗时" + (end - begin) +
                "ms");


    }

3.批量发布确认

public static  final int MESSAGE_COUNT = 1000;
 //批量发布确认
    public static void publicMessageBatch() throws Exception{
        Channel channel = RabbitmqUtils.getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        long begin = System.currentTimeMillis();

        // 批量确认的长度
        int batchSize = 100;

        for(int i=-0;i<MESSAGE_COUNT ;i++){
            String message = i + "";
            channel.basicPublish("", queueName, null, message.getBytes());
            //服务端返回 false 或超时时间内未返回,生产者可以消息重发
           if(i%batchSize == 0){
               boolean flag = channel.waitForConfirms();
               if(flag){
                   System.out.println("消息发送成功");
               }
           }
        }
        long end = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "批量确认消息,耗时" + (end - begin) +
                "ms");


    }

4.异步发布消息确认
异步确认虽然编程逻辑比上两个要复杂,但是性价比最高,无论是可靠性还是效率都没得说,他是利用回调函数来达到消息可靠性传递的,这个中间件也是通过函数回调来保证是否投递成功。
RabbitMQ初学教程一(消息队列,持久化,发布确认)_第10张图片

 public static void publicMessageAsync() throws Exception{
        Channel channel = RabbitmqUtils.getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        /**
         * 线程安全有序的一个哈希表,适用于高并发的情况
         * 1.轻松的将序号与消息进行关联
         * 2.轻松批量删除条目 只要给到序列号
         * 3.支持并发访问
         */
        ConcurrentSkipListMap<Long, String> outstandingConfirms = new
                ConcurrentSkipListMap<>();
        /**
         * 确认收到消息的一个回调
         * 1.消息序列号
         * 2.true 可以确认小于等于当前序列号的消息
         * false 确认当前序列号消息
         */
        ConfirmCallback ackCallback = (sequenceNumber, multiple) -> {
            if (multiple) {
                //返回的是小于等于当前序列号的未确认消息 是一个 map
                ConcurrentNavigableMap<Long, String> confirmed =
                        outstandingConfirms.headMap(sequenceNumber, true);
                //清除该部分未确认消息
                confirmed.clear();
            }else{
                //只清除当前序列号的消息
                outstandingConfirms.remove(sequenceNumber);
            }
        };
        ConfirmCallback nackCallback = (sequenceNumber, multiple) -> {
            String message = outstandingConfirms.get(sequenceNumber);
            System.out.println("发布的消息"+message+"未被确认,序列号"+sequenceNumber);
        };
        /**
         * 添加一个异步确认的监听器
         * 1.确认收到消息的回调
         * 2.未收到消息的回调
         */
        channel.addConfirmListener(ackCallback, null);
        long begin = System.currentTimeMillis();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String message = "消息" + i;
            /**
             * channel.getNextPublishSeqNo()获取下一个消息的序列号
             * 通过序列号与消息体进行一个关联
             * 全部都是未确认的消息体
             */
            outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
            channel.basicPublish("", queueName, null, message.getBytes());
        }
        long end = System.currentTimeMillis();
        System.out.println("发布" + MESSAGE_COUNT + "个异步确认消息,耗时" + (end - begin) +
                "ms");
    } 

5.以上 3 种发布确认速度对比

  • 单独发布消息:同步等待确认,简单,但吞吐量非常有限。
  • 批量发布消息:批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是那条消息出现了问题。
  • 异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是实现起来稍微难些

你可能感兴趣的:(RabbitMQ,java-rabbitmq,rabbitmq,java)