消息中间件RabbitMQ

RabbitMQ引言

解决的问题

在这里插入图片描述

  1. 能解决模块之间的耦合度高,导致一个模块宕机后,后续全部功能都不能用了的问题,达到解耦的作用。
  2. 能解决同步通讯的时间成本高的问题,达到异步通讯的作用,提升客户的体验。
  3. 能解决高并发导致系统压力过大的问题,达到流量削峰的作用,减轻服务器压力。

RabbitMQ介绍

市面上比较火爆的几款MQ:ActiveMQ,RocketMQ,Kafka,RabbitMQ。对比如下:
在这里插入图片描述

  1. 语言的支持:ActiveMQ,RocketMQ只支持JAVA语言,Kafka,RabbitMQ支持多门语言;
  2. 效率方面:ActiveMQ,RocketMQ,Kafka效率都是毫秒级别的,RabbitMQ是微秒级别的;
  3. 针对消息丢失,消息重复的问题它们四种都有其各自一套解决方法:RabbitMQ针对消息的持久化和重复性问题都有比较成熟的解决方案;
  4. 学习成本:RabbitMQ非常简单,简单到令人发指。

RabbitMQ效率高的原因:RabbitMQ是基于erlang开发,erlong有一个特点叫面向并发编程,所以并发能力很强,性能极好。

RabbitMQ最初是由Rabbit公司研发和维护的,最终是在Pivotal公司维护的,springboot就是Pivotal公司研发的。RabbitMQ严格遵守高效消息队列协议——AMQP协议,帮助我们在进程之间传递异步消息。
详细介绍请看

RabbitMQ架构

官方的简单的架构图

  1. Publisher:生产者,发布消息到RabbitMQ中的Exchange
  2. Consumer:消费者,监听RabbitMQ中的Queue中的消息
  3. Exchange:交换机,和生产者建立连接并接收生产者的消息
  4. Queue:队列,Exchange会将消息分发到指定的Queue,Queue和消费者进行交互
  5. Routes:路由,交换机以什么样的策略将消息发布到Queue
    在这里插入图片描述

RabbitMQ的使用

RabbitMQ的通讯方式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Java连接RabbitMQ

  1. 创建Maven项目
  2. 导入依赖
	<dependencies>
        <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.9.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>
  1. 创建工具类,连接RabbitMQ
public class RabbitMQClient {

    public static Connection getConnection() {
        // 创建Connection工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");
        factory.setPort(5672);
        factory.setUsername("test");
        factory.setPassword("test");
        factory.setVirtualHost("/test");

        // 创建Connection
        Connection connection = null;
        try {
            connection = factory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 返回Connection
        return connection;
    }

}
  1. 创建测试类,连接RabbitMQ
public class Demo1 {

    @Test
    public void getConnection() throws IOException {
        // 获取Connection
        Connection connection = RabbitMQClient.getConnection();

        // 其他操作
        System.in.read();

        // 关闭Connection
        connection.close();
    }
}

连接成功,RabbitMQ控制台会有一个Connection,如下图:
在这里插入图片描述

通讯方式一:Hello-World

一个生产者,一个默认的交换机,一个队列,一个消费者

  1. 创建生产者,创建一个channel,发布消息到exchange,指定路由规则,发送到队列
public class Publisher {

    @Test
    public void publish() throws Exception{
        // 1.获取连接Connection
        Connection connection = RabbitMQClient.getConnection();

        // 2.创建Channel
        Channel channel = connection.createChannel();

        // 3.发布消息到Exchange,同时指定路由的规则
        /* 参数1:指定exchange,使用默认的,传"";
        * 参数2:指定路由的规则,使用具体的队列名称;
        * 参数3:指定传递的消息所携带的properties,当前不需要传什么信息,使用null
        * 参数4:指定发布的具体消息,byte[]类型 */
        String msg = "我要成为高级开发工程师!!!";
        channel.basicPublish("","HelloWorld",null,msg.getBytes(StandardCharsets.UTF_8));
        //Ps: exchange是不会帮你将消息持久化到本地的,Queue才会帮你持久化消息

        System.out.println("生产者发布消息成功!");
        // 释放资源
        channel.close();
        connection.close();
    }
}
  1. 创建消费者,创建一个channel,创建一个队列,并且去消费当前队列。
public class Consumer {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列-HellWorld
        /* 参数1:queue - 指定队列名称
        * 参数2:durable - 当前队列是否需要持久化(true)
        * 参数3:exclusive - 是否排外(connection.close() - 当前队列只能被一个消费者消费)
        * 参数4:autoDelete - 如果这个队列没有消费者消费是否要自动删除
        * 参数5:arguments - 指定当前队列的其他消息 */
        channel.queueDeclare("HelloWorld",true,false,false,null);

        // 4. 开启监听Queue,并指定消费者consumer
        /* 参数1:queue - 指定消费那个队列
        * 参数2:autoAck - 指定是否自动ACK(true,接收消息后,会立即告诉RabbitMQ)
        * 参数3:consumer - 指定消费回调 */
        channel.basicConsume("HelloWorld",true,new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受到的消息是:" + new String(body,"UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }
}
RabbitMQ通讯方式二:Work

只需要在消费者端,添加Qos能力以及更改为手动ack,即可让消费者根据自己的能力去消费指定队列中的消息,而不是默认情况下由RabbitMQ平均分配了。
示例:一个生产者,一个默认的交换机,一个队列,两个消费者

  1. 创建生产者,创建一个channel,发布消息到exchange,指定路由规则,发送到队列
public class Publisher {

    @Test
    public void publish() throws Exception{
        // 1.获取连接Connection
        Connection connection = RabbitMQClient.getConnection();

        // 2.创建Channel
        Channel channel = connection.createChannel();

        // 3.发布消息到Exchange,同时指定路由的规则
        for (int i = 1; i <= 10; i++) {
            String msg = i + ". 我要成为高级开发工程师!!!";
            channel.basicPublish("","Work",null,msg.getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("生产者发布消息成功!");
        // 释放资源
        channel.close();
        connection.close();
    }
}
  1. 创建消费者1,创建一个channel,创建一个队列,设置消费能力-Qos,去消费当前队列,并关闭自动ACK,打开手动ACK。
public class Consumer1 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列-Work
        channel.queueDeclare("Work", true, false, false, null);

        // 指定当前消费者一次消费多少个消息
        channel.basicQos(1);

        // 指定消费者每次消费多少个消息后,需要将自动ack关闭,消费者需要手动ack
        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("Work", false, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者1号接受到的消息是:" + new String(body, "UTF-8"));

                // 手动ack
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建消费者2,创建一个channel,创建一个队列,设置消费能力-Qos,去消费当前队列,并关闭自动ACK,打开手动ACK。
public class Consumer2 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列-Work
        channel.queueDeclare("Work", true, false, false, null);

        // * 指定当前消费者一次消费多少个消息
        channel.basicQos(2);

        // 指定消费者每次消费多少个消息后,* 需要将自动ack关闭,消费者需要手动ack
        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("Work", false, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者2号接受到的消息是:" + new String(body, "UTF-8"));

                // * 手动ack
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
通讯方式三:Publish/Subscribe

基于Work在生产者中声明Fanout类型的exchange,并且可以将exchange和多个queue绑定在一起,绑定的方式是直接绑定。
示例:一个生产者,一个交换机,两个队列,两个消费者。

  1. 创建生产者,创建一个Fanout类型的channel,发布消息到exchange,指定路由规则,发送到队列
public class Publisher {

    @Test
    public void publish() throws Exception {
        // 1.获取连接Connection
        Connection connection = RabbitMQClient.getConnection();

        // 2.创建Channel
        Channel channel = connection.createChannel();

        // 3.创建exchange - * 绑定某些队列
        /* 参数1:exchange的名称
        * 参数2:指定exchange的类型:FANOUT - pubsub,DIRECT - Routing,TOPIC - Topic */
        channel.exchangeDeclare("pubsub-exchange", BuiltinExchangeType.FANOUT);
        channel.queueBind("pubsub-queue1", "pubsub-exchange", "");
        channel.queueBind("pubsub-queue2", "pubsub-exchange", "");

        // 4.发布消息到Exchange,同时指定路由的规则
        for (int i = 1; i <= 10; i++) {
            String msg = i + ". 我要成为高级开发工程师!!!";
            channel.basicPublish("pubsub-exchange", "", null, msg.getBytes(StandardCharsets.UTF_8));
        }

        System.out.println("生产者发布消息成功!");
        // 5.释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建消费者1,创建一个channel,创建一个队列,指定监听的队列,去消费当前队列
public class Consumer1 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列:pubsub-queue1
        channel.queueDeclare("pubsub-queue1", true, false, false, null);

        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("pubsub-queue1", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1号接受到的消息是:" + new String(body, "UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建消费者2,创建一个channel,创建一个队列,指定监听的队列,去消费当前队列
public class Consumer2 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列:pubsub-queue2
        channel.queueDeclare("pubsub-queue2", true, false, false, null);

        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("pubsub-queue2", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2号接受到的消息是:" + new String(body, "UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
通讯方式四:Routing

生产者的channel绑定多个队列,并指定RoutingKey,发送消息时指定RoutingKey,就会将消息发送到绑定相同的Routing的队列上,然后消费者监听哪个队列就会收到哪个队列上的消息了。
示例:一个生产者,一个默认交换机,两个队列,三个路由,两个消费者

  1. 创建生产者,创建一个channel,绑定上两个队列,一个队列指定一个路由,一个队列指定两个路由
public class Publisher {

    @Test
    public void publish() throws Exception {
        // 1.获取连接Connection
        Connection connection = RabbitMQClient.getConnection();

        // 2.创建Channel
        Channel channel = connection.createChannel();

        // 3.创建exchange - 绑定某一个队列
        /* 参数1:exchange的名称
         * 参数2:指定exchange的类型:FANOUT - pubsub,DIRECT - Routing,TOPIC - Topic */
        channel.exchangeDeclare("routing-exchange", BuiltinExchangeType.DIRECT);
        channel.queueBind("routing-queue-error", "routing-exchange", "ERROR");
        channel.queueBind("routing-queue-error", "routing-exchange", "DEBUG");
        channel.queueBind("routing-queue-info", "routing-exchange", "INFO");

        // 4.发布消息到Exchange,同时指定路由的规则
        String msg = "我要成为高级开发工程师!!!";
        channel.basicPublish("routing-exchange", "INFO", null, msg.getBytes(StandardCharsets.UTF_8));
        channel.basicPublish("routing-exchange", "INFO", null, msg.getBytes(StandardCharsets.UTF_8));
        channel.basicPublish("routing-exchange", "INFO", null, msg.getBytes(StandardCharsets.UTF_8));
        msg = msg + "实现失败!";
        channel.basicPublish("routing-exchange", "ERROR", null, msg.getBytes(StandardCharsets.UTF_8));
        msg = msg + "这是一个BUG!";
        channel.basicPublish("routing-exchange", "DEBUG", null, msg.getBytes(StandardCharsets.UTF_8));

        System.out.println("生产者发布消息成功!");
        // 5.释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建消费者1,创建一个channel,绑定其中一个的队列,监听队列,消费消息
public class Consumer1 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列:pubsub-queue1
        channel.queueDeclare("routing-queue-error", true, false, false, null);

        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("routing-queue-error", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者ERROR接受到的消息是:" + new String(body, "UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建消费者2,创建一个channel,绑定另一个的队列,监听队列,消费消息
public class Consumer2 {

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列:pubsub-queue2
        channel.queueDeclare("routing-queue-info", true, false, false, null);

        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume("routing-queue-info", true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者INFO接受到的消息是:" + new String(body, "UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}
通讯方式五:Topic

生产者的channel绑定多个队列,并指定RoutingKey,这里指定RoutingKey可以使用通配符‘#’和占位符‘’,发送消息时指定适配的RoutingKey,就会将消息发送到绑定适配的Routing的队列上,然后消费者监听哪个队列就会收到哪个队列上的消息了。
示例:一个生产者,一个默认交换机,八个队列,使用通配符 ‘#’ 和占位符 ‘
’ 的路由,两个消费者

  1. 创建生产者,创建一个channel,绑定上八个队列,一个队列指定一种路由
public class Publisher {
    // 交换机名称
    private static final String exchangeName = "topic-exchange";

    @Test
    public void publish() throws Exception {
        // 1.获取连接Connection
        Connection connection = RabbitMQClient.getConnection();

        // 2.创建Channel
        Channel channel = connection.createChannel();

        // 3.创建exchange - 绑定某一个队列
        /* 参数1:exchange的名称
         * 参数2:指定exchange的类型:FANOUT - pubsub,DIRECT - Routing,TOPIC - Topic */
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC);
        // topic与Routing不同的是:指定RoutingKey可以使用通配符#,占位符*
        channel.queueBind("topic-queue1", exchangeName, "order.*");
        channel.queueBind("topic-queue2", exchangeName, "order.#");
        channel.queueBind("topic-queue3", exchangeName, "*.order");
        channel.queueBind("topic-queue4", exchangeName, "#.order");
        channel.queueBind("topic-queue5", exchangeName, "#.order.*");
        channel.queueBind("topic-queue6", exchangeName, "*.order.*");
        channel.queueBind("topic-queue7", exchangeName, "#.order.#");
        channel.queueBind("topic-queue8", exchangeName, "*.order.#");

        // 4.发布消息到Exchange,同时指定路由的规则
        String msg = "我要成为高级开发工程师!!!";
        channel.basicPublish(exchangeName, "order.item", null, msg.getBytes(StandardCharsets.UTF_8));
        channel.basicPublish(exchangeName, "macbook.order", null, msg.getBytes(StandardCharsets.UTF_8));
        channel.basicPublish(exchangeName, "macbook.order.item", null, msg.getBytes(StandardCharsets.UTF_8));

        System.out.println("生产者发布消息成功!");
        // 5.释放资源
        channel.close();
        connection.close();
    }

}
  1. 创建八个消费者分别监听这个八个队列
public class Consumer1 {
    // 队列名称
    private static final String queueName = "topic-queue1";

    @Test
    public void consume() throws Exception {
        // 1. 获取连接对象
        Connection connection = RabbitMQClient.getConnection();

        // 2. 创建channel
        Channel channel = connection.createChannel();

        // 3. 声明队列:pubsub-queue1
        channel.queueDeclare(queueName, true, false, false, null);

        // 4. 开启监听Queue,并指定消费者consumer
        channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者ERROR接受到的消息是:" + new String(body, "UTF-8"));
            }
        });

        System.out.println("消费者开始监听消息...");
        // 实时监听,保证程序不能停止
        System.in.read();

        // 5. 释放资源
        channel.close();
        connection.close();
    }

}

此处就创建一个,其余七个一样,需要改一下queueName和类名。

测试结果:
topic-queue1 收到 1 条消息;
topic-queue2 收到 1 条消息;
topic-queue3 收到 3 条消息;
topic-queue4 收到 2 条消息;
topic-queue5 收到 3 条消息;
topic-queue6 收到 2 条消息;
topic-queue7 收到 3 条消息;
topic-queue8 收到 2 条消息;

测试结果出人意料:为什么topic-queue3会收到三条消息?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值