消息队列需求场景

与服务之间的通信方式有两种:同步调用 和 异步消息调用

同步调用:远程过程调用,REST和RPC

异步消息调用:消息队列

RTT消息队列 消息队列通信_RTT消息队列

消息队列概念

MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。

消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

RTT消息队列 消息队列通信_java_02


RabbitMQ 稳定可靠,数据一致,支持多协议,有消息确认,基于erlang语言

Kafka 高吞吐,高性能,快速持久化,无消息确认,无消息遗漏,可能会有有重复消息,依赖于zookeeper,成本高.

ActiveMQ 不够灵活轻巧,对队列较多情况支持不好.

RocketMQ 性能好,高吞吐,高可用性,支持大规模分布式,协议支持单一

搭建RabbitMQ

创建用户

RTT消息队列 消息队列通信_rabbitmq_03

创建虚拟主机

RTT消息队列 消息队列通信_发送消息_04

RTT消息队列 消息队列通信_发送消息_05

创建Maven项目

RTT消息队列 消息队列通信_java_06


RTT消息队列 消息队列通信_java_07


RTT消息队列 消息队列通信_spring_08

添加依赖

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

简单模式

RTT消息队列 消息队列通信_rabbitmq_09

入门工程-生产者

RTT消息队列 消息队列通信_RTT消息队列_10

// 1.创建连接工厂(设置RabbitMQ的连接参数)
//2.创建连接
//3.创建频道
//4.声明队列
//5.发送消息
//6.关闭资源

package com.itheima.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//简单模式:发送消息
public class Producer {
    static final String QUEUE_NAME = "simple";
    public static void main(String[] args) throws IOException, TimeoutException {
       // 1.创建连接工厂(设置RabbitMQ的连接参数)
        ConnectionFactory connectionFactory =new ConnectionFactory();
        //主机地址;默认为 localhost
        connectionFactory.setHost("47.103.193.112");
        //连接端口;默认为 5672
        connectionFactory.setPort(5672);
        //虚拟主机名称;默认为 /
       connectionFactory.setVirtualHost("/jiajia");
        //连接用户名;默认为guest
        connectionFactory.setUsername("jiajia");
        //连接密码;默认为guest
        connectionFactory.setPassword("123456");
        //2.创建连接
        Connection connection = connectionFactory.newConnection();
        //3.创建频道
        Channel channel = connection.createChannel();
        //4.声明队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列(消息会持久保存在服务器)
         * 参数3:是否独占本次连接
         * 参数4:是否在不使用的时候自动删除队列
         * 参数5:队列其它参数
         */
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //5.发送消息
        String message = "hello world";
        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("已发消息:"+message);
        //6.关闭资源
        channel.close();
        connection.close();
    }
}

入门工程-消费者

//1.创建连接工厂
    //2.创建连接
    //3.创建频道
    //4.声明(创建)队列
    //5.创建消费者;并设置消息处理
    //6.监听消息
package com.itheima.rabbitmq.simple;
import com.itheima.rabbitmq.util.ConnectionUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
    public static void main(String[] args) throws Exception {
        //1.创建连接工厂
        //2.创建连接
       Connection connection =  ConnectionUtil.getConnection();
        //3.创建频道
        Channel channel = connection.createChannel();
        //4.声明(创建)队列
        channel.queueDeclare(Producer.QUEUE_NAME,true,false,false,null);
        //5.创建消费者;并设置消息处理
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));
              }
            };
        //6.监听消息
            /**
             * 参数1:队列名称
             * 参数2:是否自动确认,设置为true为表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,设置为false则需要手动确认
             * 参数3:消息接收到后回调
             */
            channel.basicConsume(Producer.QUEUE_NAME,true,defaultConsumer);
            //不关闭资源,应该一直监听消息
            //channel.close();
            //connection.close();
    }
}
//ConnectionUtil.java
package com.itheima.rabbitmq.util;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ConnectionUtil {
    public static Connection getConnection() throws IOException, TimeoutException {
        // 1.创建连接工厂(设置RabbitMQ的连接参数)
        ConnectionFactory connectionFactory =new ConnectionFactory();
        //主机地址;默认为 localhost
        connectionFactory.setHost("47.103.193.112");
        //连接端口;默认为 5672
        connectionFactory.setPort(5672);
        //虚拟主机名称;默认为 /
        connectionFactory.setVirtualHost("/jiajia");
        //连接用户名;默认为guest
        connectionFactory.setUsername("jiajia");
        //连接密码;默认为guest
        connectionFactory.setPassword("123456");
        //2.创建连接
        return connectionFactory.newConnection();
    }
}

入门工程测试

运行Producer.java

RTT消息队列 消息队列通信_RTT消息队列_11

RTT消息队列 消息队列通信_rabbitmq_12


RTT消息队列 消息队列通信_java_13


RTT消息队列 消息队列通信_rabbitmq_14


运行Consumer.java

RTT消息队列 消息队列通信_java_15


此时:

RTT消息队列 消息队列通信_java_16

Work queues工作队列模式

RTT消息队列 消息队列通信_发送消息_17

在同一个队列中有多个消费者,竞争关系
生产者:发30个消息
消费者:两个消费者监听同一个消息队列,查看两个消费者接收消息是否重复。

//Producer.java
//5.发送消息
        for (int i = 0; i <= 30 ; i++) {
            String message = "hello world,work模式" + i;
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("已发消息: "+message);
        }

Consumer1.java

RTT消息队列 消息队列通信_spring_18

Consumer2.java (同Consumer1.java)

RTT消息队列 消息队列通信_发送消息_19


RTT消息队列 消息队列通信_spring_20


RTT消息队列 消息队列通信_java_21


RTT消息队列 消息队列通信_RTT消息队列_22


结论:一个消息只能被一个消费者接收,其他消费者不能接收到同一条消息

应用场景:消费者处理任务比较耗时时,添加同一个队列的消费者来提高任务处理能力。

订阅模式

RTT消息队列 消息队列通信_RTT消息队列_23

比前两种多了Exchange交换机,接收生产者发送的消息并决定如何投递消息到其绑定的队列。消息的投递取决于交换机的类型。
交换机类型:广播(fanout),定向(direct),通配符(topic)
交换机只做消息转发,自身不存储数据。

生产者(发送10个消息):
1.创建连接
2.创建频道
3.声明交换机
4.声明队列
5.队列绑定到交换机
6.发送消息
7.关闭资源
消费者(至少两个)

  1. 创建连接
  2. 创建频道
  3. 声明交换机
  4. 声明队列
  5. 队列绑定到交换机
  6. 创建消费者
  7. 监听队列


    Consumer2.java






    结论:一个消息可以被多个消费者接收;一个消费者对应的队列,该队列只能被一个消费者监听。使用了订阅模式中的交换机类型为:广播。

路由模式

RTT消息队列 消息队列通信_java_24


生产者:发送两条消息(路由key分别为:insert,update)

消费者:创建两个消费者,监听的队列分别绑定路由key为:insert,update

1.消息中路由key为insert 的,会被绑定路由为insert的队列接收,并被其监听的消费者接收、处理

2.消息中路由key为update的,会被绑定路由为update的队列接收,并被其监听的消费者接收、处理

RTT消息队列 消息队列通信_RTT消息队列_25


RTT消息队列 消息队列通信_spring_26


RTT消息队列 消息队列通信_rabbitmq_27


RTT消息队列 消息队列通信_rabbitmq_28


RTT消息队列 消息队列通信_java_29


RTT消息队列 消息队列通信_rabbitmq_30


RTT消息队列 消息队列通信_rabbitmq_31


RTT消息队列 消息队列通信_spring_32


结论:Routing模式要求队列绑定到交换机的时候指定路由key;消费发送时候需要携带路由key;只有消息的路由key与队列的路由key完全一致,才能让该队列接收到消息。

通配符模式

RTT消息队列 消息队列通信_rabbitmq_33


绑定 rouer key时可以使用通配符

#:匹配1个或多个

*:匹配不多不少恰好一个

生产者:发送包含有 item.insert 、item.update 、 item.delete的3种路由key消息

消费者1:监听的队列绑定的交换机类型为 item.insert 、item.delete

消费者1:监听的队列绑定的交换机类型为 item.*

RTT消息队列 消息队列通信_spring_34

RTT消息队列 消息队列通信_java_35


RTT消息队列 消息队列通信_RTT消息队列_36


RTT消息队列 消息队列通信_发送消息_37


RTT消息队列 消息队列通信_发送消息_38


RTT消息队列 消息队列通信_spring_39


RTT消息队列 消息队列通信_RTT消息队列_40


RTT消息队列 消息队列通信_rabbitmq_41


结论:可以根据路由key将信息传递到对应的路由key的队列;队列绑定到交换机的路由key可以有多个;通配符模式中路由key可以使用*#;使用了通配符模式后对应路由key配置更灵活。

RabbitMQ模式总结

不直接Exchange交换机(默认交换机)
1.simple简单模式:一个生产者生产一个消息,到一个队列,被一个消费者接收
2.work工作队列模式:生产者发送消息到一个队列中,然后可以被多个消费者监听该队列; -一个消息只能被一个消费者接收,消费者之间是竞争关系

使用Exchange交换机;订阅模式(交换机:广播fanout、 定向direct、 通配符topic )
1.发布与订阅模式:使用了fanout广播类型的交换机,可以将一个消息发送到所有绑定了该交换机的队列
2. 路由模式:使用了direct定向类型的交换机,消费会携带路由key ,交换机根据消息的路由key与队列的路由key进行对比,一致的话那么该队列可以接收到消息
3.通配符模式:使用了topic通配符类型的交换机,消费会携带路由key(*, #) , 交换机根据消息的路由key与队列的路由key进行对比,匹配的话那么该队列可以接收到消息

整合springboot

springboot提供了AMQP的整合;可以使用RabbitTemplate发送消息,使用**@RabbitListener**接收消息。
生产者工程:spring-boot-rabbitmq-producer:发送消息
消费者工程:spring-boot-rabbitmq-customer:接收消息

file new moudle maven

RTT消息队列 消息队列通信_RTT消息队列_42


RTT消息队列 消息队列通信_rabbitmq_43

<!--  pom.xml(producer)-->
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
 </parent>
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
  </dependencies>
<!--  pom.xml(customer)-->
<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
 </parent>
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  </dependencies>

启动引导类,配置文件yml

RTT消息队列 消息队列通信_rabbitmq_44

配置生产者工程

使用通配符模式
1.配置RabbitMQ的连接参数:主机、连接端口、虚拟主机、用户名、密码
2.声明交换机、队列并将队列绑定到交换机,指定的路由key

spring:
  rabbitmq:
    host: 47.103.193.112
    post: 5672
    virtual-host: /jiajia
    username: jiajia
    password: 123456
@Configuration
public class RabbitMQConfig {
    //声明交换机
    public static final String ITEM_TOPIC_EXCHANGE = "item_topic_exchange";
    //声明队列
    public static final String ITEM_QUEUE = "item_queue";
    //声明交换机
    @Bean("itemTopicExchange")
    public Exchange topicExchange() {
        return ExchangeBuilder.topicExchange(ITEM_TOPIC_EXCHANGE).durable(true).build();
    }
    //声明队列
    @Bean("itemQueue")
    public Queue itemQueue() {
        return QueueBuilder.durable(ITEM_QUEUE).build();
    }
    //将队列绑定到交换机
    @Bean
    public Binding itemQueueExchange(@Qualifier("itemQueue") Queue queue,
                                     @Qualifier("itemTopicExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
    }
}

配置消费者工程

1.配置applixation.yml文件,设置RibbitMQ连接参数
2.编写消息监听器监听接收队列(item_queue)消息;使用注解 @RabbitListener接收队列消息

spring:
  rabbitmq:
    host: 47.103.193.112
    post: 5672
    virtual-host: /jiajia
    username: jiajia
    password: 123456
@Component
public class MyListener {
    @RabbitListener(queues = "item_queue")
    public void myListener1(String message) {
        System.out.println("消费者接收到消息:" + message);
    }
}

测试消息发送接收

生产者:编写测试类RabbitMQTest,利用RabbitTemplate发送3条消息,路由key 分别为 item.insert 、item.update 、 item.delete
消费者:查看能否收到数据

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMQTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void test() {
        rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
                "item.insert", "商品新增,路由key为item.insert");
        rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
                "item.update", "商品修改,路由key为item.update");
        rabbitTemplate.convertAndSend(RabbitMQConfig.ITEM_TOPIC_EXCHANGE,
                "item.delete", "商品删除,路由key为item.delete");
    }
}

先启动测试类,再启动consumerApplication

测试成功

RTT消息队列 消息队列通信_rabbitmq_45


RTT消息队列 消息队列通信_RTT消息队列_46


RTT消息队列 消息队列通信_RTT消息队列_47