* 消息中间件 - 2,RabbitMQ中消息发布权衡 与 消费权衡

目录

  • RabbitMQ
    • 消息中间件入门,AMQP 与 RabbitMQ
    • RabbitMQ中消息发布 与 权衡
    • RabbitMQ 中消息消费权衡 及 消息属性
    • RabbitMQ 异步处理实战
    • RabbitMQ 管理与高可用集群
  • Kafka
    • Kafka 入门,生产者 和 消费者实战
    • Kafka 高级进阶,消费者分区再均衡
    • 深入理解Kafka的内部机制
    • Kafka 集群及流计算实战
  • RocketMQ
    • RocketMQ 入门
    • 玩转RocketMQ 中的消息发送 与 消费
    • RocketMQ 的高级特性
    • RocketMQ 的存储以及高可用集群
    • RocketMQ限时订单实战

RabbitMQ中 消息发布(生产者)权衡

在这里插入图片描述

1、使用 RabbitMQ 原生 java 客户端进行消息通讯

1.1 集成

客户端需要amqp-client-5.0.0.jar和slf4j-api-1.6.1.jar

建议使用Maven:

<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>5.0.0</version>
</dependency>

注意:5系列的版本最好使用JDK8及以上, 低于JDK8可以使用4.x(具体的版本号到Maven的中央仓库查)的版本。本课程使用5系列版本,其余版本不予理会。

1.2 原生 java 客户端中使用 direct 交换器

在这里插入图片描述

生产者 和 消费者 一般用法:

生产者代码示例:

package com.h.rabbitmq.native2.exchange;

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

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

/**
 * @author h
 * @Description 生产者(直连式交换器)
 * @createTime 2020年06月13日 09:37
 */
public class DirectProduct {

    public static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2.设置工厂连接的地址 和 端口(默认端口为5672)
        connectionFactory.setHost("localhost");
        // 3.获取连接
        Connection connection = connectionFactory.newConnection();

        // 4.创建信道
        Channel channel = connection.createChannel();
        // 5.在信道中设置交换器      名称            交换器类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 6.声明路由键、消息体
        String routeKey = "king";
        String message = "hello rabbitmq";
        // 7.发布消息         交换器名称     路由键                             消息体
        channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());

        System.out.println("发送消息成功。");
        // 8.关闭信道、关闭连接
        channel.close();
        connection.close();
    }
}

消费者代码示例:消费者启动后,会一直监听队列中是否有消息可以消费

package com.h.rabbitmq.native2.exchange;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消费者,消费者保持长连接,一直监听队列中是否有消息可消费
 * @createTime 2020年06月13日 10:13
 */
public class NormalConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2.设置连接地址
        connectionFactory.setHost("localhost");
        // 4.获取连接
        Connection connection = connectionFactory.newConnection();
        // 5.创建信道
        Channel channel = connection.createChannel();
        // 6.在信道中设置交换器
        channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 7.声明队列
        String queueName = "queue_king";
        channel.queueDeclare(queueName, false, false, false, null);
        // 8.绑定,将队列 与 交换器、路由键绑定在一起
        String routeKey = "king";
        channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
        System.out.println("等待生产者发布消息。。。");

        // 9.声明一个消费者
        final Consumer consumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String s, Envelope envelope,
                                       AMQP.BasicProperties basicProperties,
                                       byte[] bytes) throws IOException {
                String message = new String(bytes, "UTF-8");
                System.out.println("routeKey: " + envelope.getRoutingKey() + ",message: " + message);
            }
        };

        // 10.消费者订阅指定队列上的消息
        channel.basicConsume(queueName, true, consumer);
    }
}
一个队列绑定到交换器时可以多个路由键,也就是多重绑定

队列 和 交换器 的多重绑定

package com.h.rabbitmq.native2.exchange;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 一个队列绑定到交换器时可以多个路由键,也就是多重绑定
 * @createTime 2020年06月13日 14:32
 */
public class MultiBindConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        // 设置交换器类型
        channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        // 声明一个随机队列
        String queueName = channel.queueDeclare().getQueue();

        // 队列绑定到交换器上时,时允许绑定多个路由键的,也就是多重绑定。
        String[] routeKeys = {"king", "mark", "james"};
        for (String routeKey : routeKeys) {
            channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
        }
        System.out.println(" [*] 多重绑定演示,等待消息。。");

        // 创建队列消费者
        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("接收到," + envelope.getRoutingKey() + ", " + message);
            }
        };
        // 通过信道将消费者与队列绑定建立绑定关系,即让消费者订阅队列
        channel.basicConsume(queueName, true, consumerA);
    }
}
一个连接多个信道

每个信道都可以收到同样的消息

package com.h.rabbitmq.native2.exchange;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 一个连接多个信道
 *      生产者发布的消息,每个信道都可以收到同样的消息
 * @createTime 2020年06月13日 14:53
 */
public class MultiChannelConsumer {

    private static class ConsumerWorker implements Runnable{

        final Connection connection;

        public ConsumerWorker(Connection connection){
            this.connection = connection;
        }

        @Override
        public void run() {
            try {
                // 创建一个信道,意味着每个线程单独一个信道
                Channel channel = connection.createChannel();
                // 信道设置交换器类型
                channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
                // 声明一个随机队列
                String queueName = channel.queueDeclare().getQueue();

                // 消费者名字,
                final String consumerName = Thread.currentThread().getName() + "-all";
                // 队列绑定到交换器上,是允许绑定多个路由键的,也就是多重绑定
                String[] routeKeys = {"king", "mark", "james"};
                System.out.println("["+consumerName+"]" + "等待消息。。");
                for (String routeKey : routeKeys) {
                    channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
                }

                // 创建队列消费者
                final Consumer consumer = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        String message = new String(body, "UTF-8");
                        System.out.println(consumerName + " 接收到消息," + envelope.getRoutingKey()
                        + ", " + message);
                    }
                };

                channel.basicConsume(queueName, true, consumer);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        for (int i = 0; i < 2; i++) {
            ConsumerWorker consumerWorker = new ConsumerWorker(connection);
            new Thread(consumerWorker).start();
        }
    }
}

打印结果如下:

[Thread-0-all]等待消息。。
[Thread-1-all]等待消息。。
Thread-0-all 接收到消息,king, hello rabbitmq
Thread-0-all 接收到消息,mark, hello rabbitmq
Thread-0-all 接收到消息,james, hello rabbitmq
Thread-1-all 接收到消息,king, hello rabbitmq
Thread-1-all 接收到消息,mark, hello rabbitmq
Thread-1-all 接收到消息,james, hello rabbitmq
一个队列多个消费者

一个队列有多个消费者,则会表现出消息在消费者之间的轮训发送

package com.h.rabbitmq.native2.exchange.direct;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 一个队列有多个消费者,则会表现出消息在消费者之间的轮训发送
 * @createTime 2020年06月13日 15:21
 */
public class MultiConsumerOneQueue {

    private static class ConsumerWorker implements Runnable{

        final Connection connection;
        final String queueName;

        public ConsumerWorker(Connection connection, String queueName) {
            this.connection = connection;
            this.queueName = queueName;
        }

        @Override
        public void run() {
            try {
                final Channel channel = connection.createChannel();
                channel.exchangeDeclare(DirectProduct.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
                channel.queueDeclare(queueName, false, false, false, null);
                final String consumerName = Thread.currentThread().getName();

                String[] routeKeys = {"king", "mark", "james"};
                for (String routeKey : routeKeys) {
                    channel.queueBind(queueName, DirectProduct.EXCHANGE_NAME, routeKey);
                }

                System.out.println("等待消息中。。");
                final Consumer consumerA = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        String message = new String(body, "UTF-8");
                        System.out.println(consumerName + ", " + envelope.getRoutingKey() + ", " + message);
                    }
                };

                channel.basicConsume(queueName, true, consumerA);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();

        // 3个线程共享一个队列,一个队列多个消费者
        String queueName = "focusAll";
        for (int i = 0; i < 3; i++) {
            ConsumerWorker consumerWorker = new ConsumerWorker(connection, queueName);
            new Thread(consumerWorker).start();
        }
    }
}

1.3 原生 java 客户端中使用 Fanout 交换器

在这里插入图片描述

生产者 和 消费者 一般用法

生产者

package com.h.rabbitmq.native2.exchange.fanout;

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

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

/**
 * @author h
 * @Description 生产者,使用广播式交换器
 * @createTime 2020年06月13日 15:39
 */
public class FanoutProduct {

    public static final String EXCHANGE_NAME_FANOUT = "EXCHANGE_NAME_FANOUT";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);

        String[] routeKeys = {"king", "mark", "james"};
        // 将路由键与交换器绑定在一起
        for(int i=0;i<3;i++){
            String routeKey = routeKeys[i%3];
            String message = "hello fanout " + i;
            // 发布消息
            channel.basicPublish(EXCHANGE_NAME_FANOUT, routeKey, null, message.getBytes());
            System.out.println("已发送消息" + message);
        }
        channel.close();
        connection.close();
    }
}

消费者1

package com.h.rabbitmq.native2.exchange.fanout;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description fanout 类型交换器,一个队列绑定多个路由键
 * @createTime 2020年06月13日 15:53
 */
public class Consumer1 {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(FanoutProduct.EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);
        String queueName = channel.queueDeclare().getQueue();
        String[] routeKeys = {"king", "mark", "james"};
        for (String routeKey : routeKeys) {
            channel.queueBind(queueName, FanoutProduct.EXCHANGE_NAME_FANOUT, routeKey);
            System.out.println("等待消息。。");
        }

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(envelope.getRoutingKey() + " 消费到消息, " + message);
            }
        };
        channel.basicConsume(queueName, true, consumerA);
    }
}

消费者2

package com.h.rabbitmq.native2.exchange.fanout;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description fanout 类型交换器,一个队列绑定多个路由键
 * @createTime 2020年06月13日 15:53
 */
public class Consumer2 {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(FanoutProduct.EXCHANGE_NAME_FANOUT, BuiltinExchangeType.FANOUT);
        String queueName = channel.queueDeclare().getQueue();
        // 广播式交换器,不管消费者设置的绑定路由键是否正确,都可以消费到消息。
        String routeKey = "xxx";
        channel.queueBind(queueName, FanoutProduct.EXCHANGE_NAME_FANOUT, routeKey);
        System.out.println("等待消息。。");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(envelope.getRoutingKey() + " 消费到消息, " + message);
            }
        };
        channel.basicConsume(queueName, true, consumerA);
    }
}
看看路由键有无影响?

广播式交换器,不管消费者设置的绑定路由键是否正确,都可以消费到消息。

1.4 使用 RabbitMQ 原生 java 客户端进行消息通讯

在这里插入图片描述
路由键中的 “*” 和 “#”

”.”将路由键分为了几个标识符, “*”匹配1个,“#”匹配一个或多个

生成者 和 消费者使用 Topic,看看路由键中的 “*” 和 “#” 的实际效果

#
king.#
#.B
king.#.A 和 king.*.A
#.kafka.#
king.kafka.A

topic 类型交换器,生产者:

package com.h.rabbitmq.native2.exchange.topic;

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

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

/**
 * @author h
 * @Description
 * Topic类型的生产者
 * 假设有交换器 topic_course,
 * 讲课老师有king,mark,james,
 * 技术专题有kafka,jvm,redis,
 * 课程章节有 A、B、C,
 * 路由键的规则为 讲课老师+“.”+技术专题+“.”+课程章节,如:king.kafka.A。
 * 生产者--生产全部的消息3*3*3=27条消息
 * @createTime 2020年06月13日 16:18
 */
public class TopicProduct {

    public static final String EXCHANGE_NAME_TOPIC = "EXCHANGE_NAME_TOPIC";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME_TOPIC, BuiltinExchangeType.TOPIC);

        String[] techers = {"king", "mark", "james"};
        String[] modules = {"kafka", "jvm", "redis"};
        String[] servers = {"A", "B", "C"};
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < modules.length; j++) {
                for (int k = 0; k < servers.length; k++) {
                    String message = "hello topic_["+i+","+j+","+k + "_]";
                    String routeKey = techers[i%3] + "." + modules[j%3] + "." + servers[k%3];
                    channel.basicPublish(EXCHANGE_NAME_TOPIC, routeKey, null, message.getBytes());
                    System.out.println("[x] 发送 " + routeKey + ", " + message);
                }
            }
        }
        channel.close();
        connection.close();
    }
}

消费者:

package com.h.rabbitmq.native2.exchange.topic;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 关注所有课程
 * @createTime 2020年06月13日 16:50
 */
public class TopicConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(TopicProduct.EXCHANGE_NAME_TOPIC, BuiltinExchangeType.TOPIC);

        String queueName = channel.queueDeclare().getQueue();

        //绑定队列,# 号匹配所有的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#");
        // 匹配所有 kafka 相关的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#.kafka.#");
        // 匹配所有以 B 结尾的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "#.B");
        // 匹配所有以 king 开头, A 结尾的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.#.A");
        // 匹配所有以 king 开头 的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.#");
        // 匹配king.kafka.A的路由键
//        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.kafka.A");

        // 匹配 king.* 的路由键,不存在匹配的路由键
        channel.queueBind(queueName, TopicProduct.EXCHANGE_NAME_TOPIC, "king.*");

        System.out.println("等待生产者发送消息");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(envelope.getRoutingKey() + ", " + message);
            }
        };
        channel.basicConsume(queueName, true, consumerA);
    }
}

2、生产者-消息发布时的权衡

不做任何配置的情况下,生产者是不知道消息是否真正到达RabbitMQ,也就是说消息发布操作不返回任何消息给生产者。怎么保证我们消息发布的可靠性?有以下几种常用机制。
在这里插入图片描述

2.1 失败通知

发送消息时,设置 mandatory 标志。
在这里插入图片描述
生产者代码示例:

package com.h.rabbitmq.native2.exchange.producer_balance.mandatory;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 生产者,失败确认模式
 * @createTime 2020年06月13日 17:46
 */
public class ProducerMandatory {

    public static final String EXCHANGE_NAME = "mandatory_test";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        
        // 连接关闭,回调
        connection.addShutdownListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException cause) {
                
            }
        });
        
        // 信道关闭,回调
        channel.addShutdownListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException cause) {
                
            }
        });

        // 失败确认通知
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode, String replyText,
                                     String exchange, String routingKey,
                                     AMQP.BasicProperties properties,
                                     byte[] body) throws IOException {
                System.out.println("返回的replyCode " + replyCode);
                System.out.println("返回的replyText " + replyText);
                System.out.println("返回的exchange " + exchange);
                System.out.println("返回的routingKey " + routingKey);
            }
        });

        String[] routeKets = {"king", "mark", "james"};
        for (int i = 0; i < routeKets.length; i++) {
            String routingKey = routeKets[i];
            String message = "mandatory_test " + i;
            channel.basicPublish(EXCHANGE_NAME, routingKey, true, null, message.getBytes());
            System.out.println("---------------------------------");
            System.out.println("发送消息成功。" + routingKey + ", " + message);
            // 这里不休眠为什么会没有回调?
            Thread.sleep(200);
        }
        channel.close();
        connection.close();
    }
}

消费者代码示例:

package com.h.rabbitmq.native2.exchange.producer_balance.mandatory;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description TODO
 * @createTime 2020年06月13日 17:59
 */
public class ConsumerProducerMandatory {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(ProducerMandatory.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = channel.queueDeclare().getQueue();
        channel.queueBind(queueName, ProducerMandatory.EXCHANGE_NAME, "king");
        System.out.println("等待生产者,发布消息");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println(envelope.getRoutingKey() + ", " + message);
            }
        };
        
        channel.basicConsume(queueName, consumerA);
    }
}

2.2 事务

事务的实现主要是对信道(Channel)的设置
主要分为:启动事务、提交事务、回滚事务。

但是事务机制本身也会带来问题:

  • 1,严重的性能问题,性能下降 2-10 倍不等
  • 2,使生产者应用程序产生同步

在这里插入图片描述

生产者使用事务代码示例:

package com.h.rabbitmq.native2.exchange.producer_balance.transaction;

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

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

/**
 * @author h
 * @Description 生产者-事务模式
 *      使用事务的方式,性能会下降 2-10 倍不等
 * @createTime 2020年06月13日 21:13
 */
public class ProducerTransaction {

    public static final String EXCHANGE_NAME = "producer_transaction";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String[] routeKeys = {"king", "mark", "james"};
        try {
            // 加入事务
            channel.txSelect();
            for (int i = 0; i < routeKeys.length; i++) {
                String message = "hello world, " + i;
                String routeKey = routeKeys[i];
                channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
                System.out.println("发送消息," + routeKey + ", " + message);
                Thread.sleep(200);
            }
            channel.txCommit();
        } catch (Exception e) {
            channel.txRollback();
            System.out.println("事务回滚...");
        }
        channel.close();
        connection.close();
    }
}

消费者代码示例:

package com.h.rabbitmq.native2.exchange.producer_balance.transaction;

import com.rabbitmq.client.*;

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

/**
 *类说明:消费者——事务模式
 */
public class ConsumerTransaction {

    public static void main(String[] argv)
            throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        channel.exchangeDeclare(ProducerTransaction.EXCHANGE_NAME,
                BuiltinExchangeType.DIRECT);

        String queueName = "producer_confirm";
        channel.queueDeclare(queueName,false,false,
                false,null);

        //只关注king的日志
        String routekey="king";
        channel.queueBind(queueName, ProducerTransaction.EXCHANGE_NAME,
                routekey);

        System.out.println(" [*] Waiting for messages......");

        // 创建队列消费者
        final Consumer consumerB = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                //记录日志到文件:
                System.out.println( "Received ["+ envelope.getRoutingKey()
                        + "] "+message);
            }
        };
        channel.basicConsume(queueName, true, consumerB);
    }
}

2.3 发送方确认模式

疑问:

  • 发布一个无法路由的路由键,走了失败通知,还会返回 ACK 这个是有问题的吗

发送方确认模式比事务模式更轻量,性能影响几乎可以忽略不计。
在这里插入图片描述

2.3.1 一般确认

生产者代码示例:

package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;

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

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

/**
 * @author h
 * @Description 发送方确认模式-一般确认(单个确认)
 * @createTime 2020年06月14日 09:51
 */
public class ProducerConfirm {

    public static final String EXCHANGE_NAME = "producer_confirm";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        channel.confirmSelect();
        for (int i = 0; i < 2; i++) {
            String message = "hello producer confirm";
            String routeKey = "king";
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
            System.out.println("发送消息成功。。" + routeKey + ", " + message);
            // 发送方确认
            if(channel.waitForConfirms()) {
                System.out.println("发送方确认, 发送成功");
            }else {
                System.out.println("发送方确认, 发送失败");
            }
        }
        channel.close();
        connection.close();
    }
}
2.3.2 批量确认
package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;

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

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

/**
 * @author h
 * @Description 发送者确认-批量确认
 * @createTime 2020年06月14日 10:15
 */
public class ProducerBatchConfirm {

    public static final String EXCHANGE_NAME = "producer_batch_confirm";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        channel.confirmSelect();
        for (int i = 0; i < 2; i++) {
            String message = "hello producer batch confirm" + i;
            String routeKey = "king";
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
            System.out.println("消息已发送 " + routeKey + ", " + message);
        }
        // 批量确认
        channel.waitForConfirmsOrDie();

        channel.close();
        connection.close();
    }
}
2.3.3 异步监听确认
package com.h.rabbitmq.native2.exchange.producer_balance.producerconfirm;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 发送者确认-异步确认
 * @createTime 2020年06月14日 10:29
 */
public class ProducerConfirmAsync {

    public static final String EXCHANGE_NAME = "producer_confirm_async";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 开启发送者确认模式
        channel.confirmSelect();

        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("发送成功,回调ACK。。,id:" + deliveryTag + ", 是否批量确认:" + multiple);
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("发送失败,回调NACK。。。" + deliveryTag + ", " + multiple);
            }
        });

        for (int i = 0; i < 2; i++) {
            String message = "hello producer confirm async" + i;
            String routeKey = "king";
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
            System.out.println("消息已发送 " + routeKey + ", " + message);
        }
//        channel.close();
//        connection.close();
    }
}

2.4 备用交换器

如果主交换器无法路由消息,那么消息将被路由到这个新的备用交换器。

注意:如果有设置mandatory标志,会怎样?

  • 不会走路由键失败通知,因为发布的消息无法路由时,都到备用交换器中了。
    在这里插入图片描述
    代码示例,生产者:
package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;

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

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

/**
 * @author h
 * @Description 备用交换器-生产者
 * @createTime 2020年06月14日 11:08
 */
public class BackupExchangeProducer {
    public static final String EXCHANGE_NAME = "main-exchange";
    public static final String BAK_EXCHANGE_NAME = "bak-exchange";


    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        Map<String, Object> hashMap = new HashMap<>();
        hashMap.put("alternate-exchange", BAK_EXCHANGE_NAME);
        // 主交换器
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT,
                false, false, hashMap);
        // 备用交换器
        channel.exchangeDeclare(BAK_EXCHANGE_NAME, BuiltinExchangeType.FANOUT,
                true, false, null);
        // 所有消息
        String[] routeKeys = {"king", "mark", "james"};
        for (int i = 0; i < 3; i++) {
            String routeKey = routeKeys[i % 3];
            String message = "hello back Exchange " + i;
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
            System.out.println("消息已发送, " + routeKey + ", " + message);
        }
        channel.close();
        connection.close();
    }
}

代码示例,主消费者:

package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 主消费者
 * @createTime 2020年06月17日 20:57
 */
public class MainConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        final Channel channel = connection.createChannel();

        // 声明一个队列
        String queueName = "backupExchange";
        String routingKey = "king";
        channel.queueDeclare(queueName, false, false,
                false, null);
        channel.queueBind(queueName, BackupExchangeProducer.EXCHANGE_NAME, routingKey);

        System.out.println("等待生产者发布消息。。。");
        final Consumer consumerB = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("消费者消费消息:" + envelope.getRoutingKey() + ": " + message);
            }
        };
        channel.basicConsume(queueName, true, consumerB);
    }
}

代码示例,备用交换器:

package com.h.rabbitmq.native2.exchange.producer_balance.backupexchange;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 备用交换器,消费者
 * @createTime 2020年06月17日 20:48
 */
public class BackupExConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        final Channel channel = connection.createChannel();

        // 声明一个队列
        String queueName = "fetchOther";
        channel.queueDeclare(queueName, false,
                false, false, null);
        channel.queueBind(queueName, BackupExchangeProducer.BAK_EXCHANGE_NAME, "#");
        
        System.out.println("备用,等待生产者发布消息。。。");
        final Consumer consumerB = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("消费者消费消息:" + envelope.getRoutingKey() + ": " + message);
            }
        };
        channel.basicConsume(queueName, true, consumerB);
    }
}

2.5 消息发布时的权衡-总结

在这里插入图片描述

3、消费者–消费消息时的权衡

在这里插入图片描述

3.1 消息消费时的方式

3.1.1 消息的获取方式
拉取 Get(不推荐)

在这里插入图片描述
代码示例,生产者:

package com.h.rabbitmq.native2.exchange.consumer_balance.geimessage;

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

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

/**
 * @author h
 * @Description 普通生产者
 * @createTime 2020年06月17日 22:27
 */
public class GetMessageProducer {

    public static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        for (int i = 0; i < 3; i++) {
            String message = "hello get message producer " + i;
            channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());
            System.out.println("发送消息," + message);
        }
        channel.close();
        connection.close();
    }
}

代码示例,消费者:

package com.h.rabbitmq.native2.exchange.consumer_balance.geimessage;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消费者-拉取模式
 * @createTime 2020年06月17日 22:33
 */
public class GetMessageConsumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();

        channel.exchangeDeclare(GetMessageProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

//        String queueName = channel.queueDeclare().getQueue(); // 这里不能使用随机队列,否则消息不进行手动确认,也不会有问题
        String queueName = "focusError";
        channel.queueDeclare(queueName, false, false, false, null);
        channel.queueBind(queueName, GetMessageProducer.EXCHANGE_NAME, "error");
        System.out.println("等待生产者发布消息。。。");

        while (true) {
            // 拉取一条,autoAck 设置为false表示需要手动确认,设置为true时表示自动确认
            GetResponse response = channel.basicGet(queueName, false);
            if(null != response) {
                System.out.println("RoutingKey:" + response.getEnvelope().getRoutingKey()
                        + ": " + new String(response.getBody(), "UTF-8"));
            }
            // 确认(手动),消息确认后rabbitMQ认为这条消息已经被消费,会从队列中删除
            channel.basicAck(0, true);
            Thread.sleep(1000);
        }
    }
}
推送 Consume

在这里插入图片描述

消息消费,避免拉取
消息的应答

自动确认
手动确认

Qos预取模式

代码示例,生产者:

package com.h.rabbitmq.native2.exchange.consumer_balance.qos;

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

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

/**
 * @author h
 * @Description
 * @createTime 2020年06月17日 22:59
 */
public class QosProducer {
    public static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        // 发送210条消息,其中第210条数据表示被批消息结束
        for (int i = 0; i < 210; i++) {
            String message = "hello qos producer " + i;
            if(i == 209) {
                message = "stop";
            }
            channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());
            System.out.println("发送消息,message: " + message);
        }
        channel.close();
        connection.close();
    }
}

代码示例,消费者,单个确认:

package com.h.rabbitmq.native2.exchange.consumer_balance.qos;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description Qos 普通消费者
 * @createTime 2020年06月17日 23:04
 */
public class QosConsumerMain {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");

        String queueName = "qos_consumer_main";
        channel.queueDeclare(queueName, false, false,
                false, null);
        channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("消费到消息," + envelope.getRoutingKey() + ":" + message);
                // 单条确认
                channel.basicAck(envelope.getDeliveryTag(), true);
            }
        };
        // 100 表示一次预取的数量,最后不够100条时则全部预取出来
        channel.basicQos(100, true);
        // 消费者正式开始在指定的队列上消费消息
        channel.basicConsume(queueName, consumerA);
    }
}

代码示例,消费者,批量确认:

package com.h.rabbitmq.native2.exchange.consumer_balance.qos;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消费者 - Qos 批量确认
 * @createTime 2020年06月17日 23:26
 */
public class AosBatchAckConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");

        String queueName = "qos_consumer_main";
        channel.queueDeclare(queueName, false, false,
                false, null);
        channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
        System.out.println("等待生产者发布消息。。。");

        BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
        channel.basicConsume(queueName, false, batchAckConsumer);
    }

    static class BatchAckConsumer extends DefaultConsumer {
        private int messageCount = 0;
        /**
         * Constructs a new instance and records its association to the passed-in channel.
         *
         * @param channel the channel to which this consumer is attached
         */
        public BatchAckConsumer(Channel channel) {
            super(channel);
            System.out.println("批量确认消费者启动。。。");
        }

        @Override
        public void handleDelivery(String consumerTag,
                                   Envelope envelope,
                                   AMQP.BasicProperties properties,
                                   byte[] body) throws IOException {
            String message = new String(body, "UTF-8");
//            System.out.println("批量消费者--- routeKey " + envelope.getRoutingKey() + " message " + message);

            messageCount++;
            // 批量确认,50一批
            if(messageCount % 50 == 0) {
                this.getChannel().basicAck(envelope.getDeliveryTag(), true);
                System.out.println("批量消息消费进行消息确认。。。");
            }
            // 如果是最后一条消息,则把剩余的消息进行确认
            if(message.equals("stop")) {
                this.getChannel().basicAck(envelope.getDeliveryTag(), true);
                System.out.println("批量消费者进行最后业务消息的确认......");
            }
        }
    }
}

Qos 预取单个确认 和 批量确认 同时使用的场景:
注意: 如果Qos 的预取单个确认,和批量确认都同时使用,是轮训消费。

package com.h.rabbitmq.native2.exchange.consumer_balance.qos;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消费者 - Qos 批量确认
 * @createTime 2020年06月17日 23:26
 */
public class AosOneAckAndBatchAckConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(QosProducer.EXCHANGE_NAME, "direct");

        String queueName = "qos_consumer_main";
        channel.queueDeclare(queueName, false, false,
                false, null);
        channel.queueBind(queueName, QosProducer.EXCHANGE_NAME, "error");
        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("消费到消息," + envelope.getRoutingKey() + ":" + message);
                // 单条确认
                channel.basicAck(envelope.getDeliveryTag(), true);
            }
        };
        // 100 表示一次预取的数量,最后不够100条时则全部预取出来
        channel.basicQos(100, true);
        // 消费者正式开始在指定的队列上消费消息
        channel.basicConsume(queueName, consumerA);

        BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
        channel.basicConsume(queueName, false, batchAckConsumer);
    }

    static class BatchAckConsumer extends DefaultConsumer {
        private int messageCount = 0;
        /**
         * Constructs a new instance and records its association to the passed-in channel.
         *
         * @param channel the channel to which this consumer is attached
         */
        public BatchAckConsumer(Channel channel) {
            super(channel);
            System.out.println("批量确认消费者启动。。。");
        }

        @Override
        public void handleDelivery(String consumerTag,
                                   Envelope envelope,
                                   AMQP.BasicProperties properties,
                                   byte[] body) throws IOException {
            String message = new String(body, "UTF-8");
            System.out.println("批量消费者--- routeKey " + envelope.getRoutingKey() + " message " + message);

            messageCount++;
            // 批量确认,50一批
            if(messageCount % 50 == 0) {
                this.getChannel().basicAck(envelope.getDeliveryTag(), true);
                System.out.println("批量消息消费进行消息确认。。。");
            }
            // 如果是最后一条消息,则把剩余的消息进行确认
            if(message.equals("stop")) {
                this.getChannel().basicAck(envelope.getDeliveryTag(), true);
                System.out.println("批量消费者进行最后业务消息的确认......");
            }
        }
    }
}
事务模式

3.2 消息消费的拒绝

在这里插入图片描述

3.2.1 消息的拒绝方式
Reject

单条拒绝

Nack

单条 或 批量拒绝

消息的重新投递

requeue=true

消费者拒绝流程示意图:
在这里插入图片描述

代码示例,普通生产者:

package com.h.rabbitmq.native2.rejectmsg;

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

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

/**
 * @author h
 * @Description 普通生产者
 * @createTime 2020年06月18日 20:25
 */
public class RejectProducer {

    public static final String EXCHANGE_NAME = "direct_logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        for (int i = 0; i < 10; i++) {
            String message = "hello reject message " + (i+1);
            String routeKey = "error";
            channel.basicPublish(EXCHANGE_NAME, routeKey, null, message.getBytes());
            System.out.println("发布消息," + message);
        }
        channel.close();
        connection.close();
    }
}

代码示例,普通消费者A:

package com.h.rabbitmq.native2.rejectmsg;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 普通消费者
 *      注意:三个消费者要绑定同一个队列
 * @createTime 2020年06月18日 20:35
 */
public class NormalConsumerA {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "focuserError";
        channel.queueDeclare(queueName, false, false, false, null);
        String routeKey = "error";
        channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);

        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("收到消息," + envelope.getRoutingKey() + ": " + message);
                // 手动确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(queueName, false, consumerA);
    }
}

代码示例,普通消费者B:

package com.h.rabbitmq.native2.rejectmsg;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 普通消费者
 *      注意:三个消费者要绑定同一个队列
 * @createTime 2020年06月18日 20:35
 */
public class NormalConsumerB {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "focuserError";
        channel.queueDeclare(queueName, false, false, false, null);
        String routeKey = "error";
        channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);

        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerB = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("收到消息," + envelope.getRoutingKey() + ": " + message);
                // 手动确认
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        channel.basicConsume(queueName, false, consumerB);
    }
}

代码示例,拒绝消费者:
当发生异常时的解决措施

package com.h.rabbitmq.native2.rejectmsg;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 拒绝消费者,消息重新回到队列中
 *      注意:三个消费者要绑定同一个队列
 * @createTime 2020年06月18日 20:35
 */
public class RejectlConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(RejectProducer.EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String queueName = "focuserError";
        channel.queueDeclare(queueName, false, false, false, null);
        String routeKey = "error";
        channel.queueBind(queueName, RejectProducer.EXCHANGE_NAME, routeKey);

        System.out.println("等待生产者发布消息。。。");

        final Consumer rejectConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                try {
                    String message = new String(body, "UTF-8");

                    // 模拟业务发生异常
                    throw new RuntimeException("业务异常,消息消费失败。" + message);
                } catch (Exception e) {
                    // requeue=true 表示,将消息放回到原队列中
                    channel.basicReject(envelope.getDeliveryTag(), true);
                    // requeue=false 消息会丢失
//                    channel.basicReject(envelope.getDeliveryTag(), false);

                    // Nack 既可以单条,也可以多条拒绝
//                    channel.basicNack(envelope.getDeliveryTag(), false, true);
                    e.printStackTrace();
                }
            }
        };
        channel.basicConsume(queueName, false, rejectConsumer);
    }
}
3.2.2 死信交换器DLX
产生死信消息的场景:
  • 消息过期
  • 队列达到最大长度
  • 消息被拒绝,并且requeue=false
  • 示意图如下:
    在这里插入图片描述

代码示例,普通生产者生产者:

package com.h.rabbitmq.native2.dlx;

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

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

/**
 * @author h
 * @Description 普通生产者
 * @createTime 2020年06月18日 21:49
 */
public class DlxProducer {
    public static final String EXCAHNGE_NAME = "dlx_exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCAHNGE_NAME, BuiltinExchangeType.TOPIC);

        String[] routeKeys = {"king", "mark", "james"};
        for (int i = 0; i < 3; i++) {
            String routeKey = routeKeys[i];
            String msg = "hello dlx exchange" + (i+1);
            channel.basicPublish(EXCAHNGE_NAME, routeKey, null, msg.getBytes());
            System.out.println("生产者发布消息。。。。" + routeKey + ", " + msg);
        }
        channel.close();
        connection.close();
    }
}

代码示例,普通消费者,
但是如果自己无法消费的消息,将投入到死信队列中

package com.h.rabbitmq.native2.dlx;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 普通的消费者,但是自己无法消费的消息,将投入到死信队列中
 * @createTime 2020年06月18日 21:58
 */
public class WillMakeDlxConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(DlxProducer.EXCAHNGE_NAME, BuiltinExchangeType.TOPIC);

        // 绑定死信队列交换器
        String queueName = "dlx_make";
        Map<String, Object> param = new HashMap<>();
        param.put("x-dead-letter-exchange", DlxProcessConsumer.DLX_EXCHANGE_NAME);
        channel.queueDeclare(queueName, false, false, false, param);

        channel.queueBind(queueName, DlxProducer.EXCAHNGE_NAME, "#");

        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerA = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                // 如果是King的消息确认
                if(envelope.getRoutingKey().equals("king")) {
                    System.out.println("消费消息," + envelope.getRoutingKey() + ", " + message);
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }else {
                    // 其他消息拒绝 并且设置 requeue=false,成为死信消息
                    // 这里是模拟异常情况,如何将消息放入死信队列中
                    System.out.println("will reject "+ envelope.getRoutingKey() + ", " + message);
                    channel.basicReject(envelope.getDeliveryTag(), false);
                }
            }
        };
        // 消费者正式开始消费指定队列上的消息,这里需要手动确认
        channel.basicConsume(queueName, false, consumerA);
    }
}

代码示例,死信消息消费者:

package com.h.rabbitmq.native2.dlx;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 普通的消费者,负责消费死信队列 dlx_accept
 * @createTime 2020年06月18日 22:02
 */
public class DlxProcessConsumer {
    public static final String DLX_EXCHANGE_NAME = "dlx_accept";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(DLX_EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        String queueName = "dlx_accept";
        channel.queueDeclare(queueName, false, false,
                false, null);
        channel.queueBind(queueName, DLX_EXCHANGE_NAME, "#");

        System.out.println("等待死信消息产生。。。");

        final Consumer consumerB = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received dead letter["
                        +envelope.getRoutingKey()
                        +"]"+message);
            }
        };
        channel.basicConsume(queueName, true, consumerB);
    }
}

4、队列的控制

在这里插入图片描述

4.1 临时队列

4.1.1 自动删除队列
  • 删除时会把整个队列删除,autoDelete=true
  • 自动删除判断条件
    • 消费者断开
    • 生产者断开
4.1.2 单消费者队列
  • 不管一个队列绑定了多少个消费者,都只往一个消费者中投递消息及消息独占
  • exclusive=true
4.1.3 自动过期队列
  • 设置参数:x-expires
  • 从什么时候开始计算这10秒钟?
    • 队列没有 Get 操作
    • 队列没有 consumer 消费者连上去
    • 如果以上两个条件都满足,即使生产者在不断的往队列中送入消息,这个队列也会被删除。
  • 在这里插入图片描述

4.2 永久队列

队列的持久性

  • 设置参数:durable=true
  • 队列每投递一个消息到消费者,都会将这个消息持久化到本地磁盘
  • 效率低

4.3 队列级别消息过期

4.4 队列保留参数列表

在这里插入图片描述

5、消息的属性

Request - Response 模式

在这里插入图片描述

应答模式:
在这里插入图片描述
在这里插入图片描述
代码示例,生产者:

package com.h.rabbitmq.native2.replyto;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消息的属性的控制-生产者
 * @createTime 2020年06月23日 21:45
 */
public class ReplyToProducer {
    public static final String EXCHANGE_NAME = "replyto";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        // 声明一个响应队列,消费者将会把要返回的消息发送到该队列中
        String responseQueue = channel.queueDeclare().getQueue();
        String msgId = UUID.randomUUID().toString();
        AMQP.BasicProperties properties = new AMQP.BasicProperties()
                .builder()
                .replyTo(responseQueue)
                .messageId(msgId)
                .build();

        final Consumer consumerA = 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("生产者收到反馈:" + envelope.getRoutingKey()
                + ", " + msg);
            }
        };
        // 消费者正式开始在指定的队列上消费消息
        channel.basicConsume(responseQueue, true, consumerA);

        String msg = "hello rabbitMQ reply to";
        channel.basicPublish(EXCHANGE_NAME, "error",
                properties,
                msg.getBytes());
        System.out.println("发送消息成功。。。");

    }
}

代码示例,消费者:

package com.h.rabbitmq.native2.replyto;

import com.rabbitmq.client.*;

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

/**
 * @author h
 * @Description 消息的属性的控制-消费者
 * @createTime 2020年06月23日 22:03
 */
public class ReplyToConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("localhost");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(ReplyToProducer.EXCHANGE_NAME,
                BuiltinExchangeType.DIRECT, false);

        String queueName = "replyTo";
        channel.queueDeclare(queueName, false, false, false, null);

        // 绑定,将队列和交换器通过路由键进行绑定
        String routeKey = "error";
        channel.queueBind(queueName, ReplyToProducer.EXCHANGE_NAME, routeKey);
        System.out.println("等待生产者发布消息。。。");

        final Consumer consumerB = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("收到生产者发布的消息:" + envelope.getRoutingKey() + ", "
                        + message);

                AMQP.BasicProperties respProperties
                        = new AMQP.BasicProperties
                        .Builder()
                        .replyTo(properties.getReplyTo())
                        .correlationId(properties.getMessageId())
                        .build();
                // 收到生产者发布的消息,做出响应
                channel.basicPublish("", respProperties.getReplyTo(),
                        respProperties, ("hi ," + message).getBytes("UTF-8"));
            }
        };

        // 消费者正式开始在指定的队列上消费
        channel.basicConsume(queueName, true, consumerB);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值