Rabbit MQ 进阶

消息应答与消息持久化

消息应答

boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);

boolean autoAck = true;(自动确认模式)

一旦 Rabbit MQ 将消息分发给消费者,就会从内存中删除,这种情况下,如果杀死正在执行的消费者,就会丢失正在处理的消息

boolean autoAck = false;(手动模式)

如果有一个消费者挂掉,就会交付给其它消费者,Rabbit MQ 支持消息应答,消费者发送一个消息应答,告诉 Rabbit MQ 这个消息我已经处理完成,你可以删了,然后 Rabbit MQ 就删除内存中的消息

消息应答默认是打开的,false

Message acknowledgment

大家想想如果我们的 Rabbit MQ 挂了,我们的消息仍然会丢失

消息的持久化

boolean durable = false;
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);

我们将程序中的 boolean durable = false; 改成 true; 是不可以的,尽管代码是正确的,它也不会运行成功,因为我们已经定义了一个叫 work_queue 的消息队列,这个 queue 是未持久化的,Rabbit MQ 不允许重新定义(不同参数)一个已存在的队列

订阅模式 publish/subscribe

模型

在这里插入图片描述

解读:

1.一个生产者,多个消费者

2.每一个消费者都有自己的队列

3.生产者没有直接把消息发送到队列 而是发到了交换机 转发器 exchange

4.每个队列都要绑定到交换机上

5.生产者发送的消息,经过交换机,到达队列,就能实现一个消息被多个消费者消费

注册 -> 邮件 -> 短信

生产者

public class Send {

    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();

        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        // 消息内容
        String message = "hello ps!";
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println("[x] Send '" + message + "'");

        channel.close();
        connection.close();
    }

}

在这里插入图片描述

消息哪去了???丢失了!!!因为交换机没有存储的能力,在 Rabbit MQ 中只有队列只有存储能力,因为这时候还没有队列绑定到交换机,所以数据丢失了

消费者 1

public class Receive1 {

    private final static String QUEUE_NAME = "queue_work1";

    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();

        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 保证一次只分发一个
        channel.basicQos(1);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    // 手动回执
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 监听队列,自动应答改为 false
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }
}

在这里插入图片描述

消费者 2

public class Receive2 {

    private final static String QUEUE_NAME = "queue_work2";

    private final static String EXCHANGE_NAME = "exchange_fanout";

    public static void main(String[] args) throws Exception {
        // 获取一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();

        // 创建队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 保证一次只分发一个
        channel.basicQos(1);

        // 定义队列的消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            /**
             * 获取到到达的消息
             * @param consumerTag
             * @param envelope
             * @param properties
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    // 手动回执
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 监听队列,自动应答改为 false
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

Exchange(交换机、转发器)

一方面是接收生产者的消息,另一方面是向队列推送消息

匿名转发 “”

Fanout(不处理路由键)

在这里插入图片描述

Direct(处理路由键)

在这里插入图片描述

路由模式

模型

在这里插入图片描述

生产者

public class Send {

    private static final 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 = "hello direct!";

        String routingKey = "error";
        channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());

        System.out.println("send " + msg);

        channel.close();
        connection.close();
    }

}

消费者 1

public class Receive1 {

    private static final String EXCHANGE_NAME = "exchange_direct";
    private static final String QUEUE_NAME = "queue_direct_1";

    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.basicQos(1);

        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "error");

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

消费者 2

public class Receive2 {

    private static final String EXCHANGE_NAME = "exchange_direct";
    private static final String QUEUE_NAME = "queue_direct_2";

    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.basicQos(1);

        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, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

Topic exchange

将路由键和某模式匹配

在这里插入图片描述
# 匹配一个或多个

* 匹配一个

Goods.#

模型

在这里插入图片描述

商品:发布 修改 删除 查询

生产者

public class Send {

    private final static String EXCHANGE_NAME = "exchange_topic";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        String msgString = "商品...";
        channel.basicPublish(EXCHANGE_NAME, "goods.add", null, msgString.getBytes());
        System.out.println("---send " + msgString);

        channel.close();
        connection.close();
    }

}

消费者 1

public class Receive1 {

    private static final String EXCHANGE_NAME = "exchange_topic";
    private static final String QUEUE_NAME = "queue_topic_1";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.add");

        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

消费者 2

public class Receive2 {

    private static final String EXCHANGE_NAME = "exchange_topic";
    private static final String QUEUE_NAME = "queue_topic_2";

    public static void main(String[] args) throws Exception {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "goods.#");

        channel.basicQos(1);

        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[2] recv msg:" + msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[2] done");
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }

}

Rabbit MQ 的消息确认机制(事务 + confirm)

在 Rabbit MQ 中,我们可以持久化数据解决 Rabbit MQ 服务器异常的数据丢失问题

问题:生产者将消息发送出去之后,消息到底有没有到达 Rabbit MQ 服务器,默认的情况是不知道的

两种方式:

1.AMQP 实现了事务机制

2.Confirm 模式

事务机制

txSelect txCommit txRollBack

txSelect:用户将当前 channel 设置成 transaction 模式

txCommit:用于提交事务

txRollBack:回滚事务

生产者

public class TxSend {

    private static final String QUEUE_NAME = "queue_tx";

    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);

        String msgString = "hello tx message";

        try {
            channel.txSelect();
            channel.basicPublish("", QUEUE_NAME, null, msgString.getBytes());
            System.out.println("send " + msgString);
            channel.txCommit();
        } catch (Exception e) {
            channel.txRollback();
            System.out.println("send message txRollBack");
        } finally {
            channel.close();
            connection.close();
        }
    }

}

消费者

public class TxReceive {

    private static final String QUEUE_NAME = "queue_tx";

    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.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("[recv] tx msg:" + new String(body, "utf-8"));
            }
        });
    }

}

此种模式还是很耗时的,采用这种方式降低了 Rabbit MQ 的消息吞吐量

Confirm 模式

生产者端 confirm 模式的实现原理

生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消息,都会被指派为一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker 回传给生产者的确认消息中,deliver-tag 域中包含了确认消息的序列号,此外,broker 也可以设置 basic.ack 的 multiple 域,表示到这个序列号之前的所有消息都已经得到了处理

Confirm 模式最大的好处在于它是异步的

Nack

开启 confirm 模式

channel.confirmSelect()

编程模式:

1.普通:发一条 waitForConfirms()

2.批量的:发一批 waitForConfirms()

3.异步 confirm 模式:提供一个回调方法

Confirm 单条

public class Send1 {

    private static final String QUEUE_NAME = "queue_confirm1";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.confirmSelect();
        String msgString = "hello confirm message!";
        channel.basicPublish("", QUEUE_NAME, null, msgString.getBytes());

        if (!channel.waitForConfirms()) {
            System.out.println("message send failed");
        } else {
            System.out.println("message send ok");
        }

        channel.close();
        connection.close();
    }

}

批量

public class Send2 {

    private static final String QUEUE_NAME = "queue_confirm2";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.confirmSelect();
        String msgString = "hello confirm message!";

        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", QUEUE_NAME, null, msgString.getBytes());
        }

        if (!channel.waitForConfirms()) {
            System.out.println("message send failed");
        } else {
            System.out.println("message send ok");
        }

        channel.close();
        connection.close();
    }

}

异步模式

Channel 对象提供的 confirmListener() 回调方法只包含 deliveryTag(当前 Channel 发出的消息序号),我们需要自己为每一个 Channel 维护一个 unconfirm 的消息序号集合,每 publish 一条数据,集合中元素加 1,没回调一次 handlerAck() 方法,unconfirm 集合删掉相应的一条(multiple=false)或多条(multiple=true)记录,从程序运行效率上看,这个 unconfirm 集合最好采用有序集合 SortedSet 存储结构

public class Send3 {

    private static final String QUEUE_NAME = "queue_confirm3";

    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.confirmSelect();
        final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());

        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long l, boolean b) throws IOException {
                if (b) {
                    System.out.println("---handleAck---multiple");
                    confirmSet.headSet(l + 1).clear();
                } else {
                    System.out.println("---handleAck---multiple false");
                    confirmSet.remove(l);
                }
            }

            @Override
            public void handleNack(long l, boolean b) throws IOException {
                if (b) {
                    System.out.println("---handleNack---multiple");
                    confirmSet.headSet(l + 1).clear();
                } else {
                    System.out.println("---handleNack---multiple false");
                    confirmSet.remove(l);
                }
            }
        });

        String msgStr = "ssssss";

        while (true) {
            long seqNo = channel.getNextPublishSeqNo();
            channel.basicPublish("", QUEUE_NAME, null, msgStr.getBytes());
            confirmSet.add(seqNo);
        }
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值