RabbitMQ 使用

目录

简介

下载、安装

RabbitMQ原始API使用

生产者

消费者

基于SpringAMQP的API使用

simpleQueue简单队列

WorkQueue任务队列

发布(Publish)、订阅(Subscribe)

Fanout 广播

Direct定向

Topic 通配饰

消息转换器


MQ,中文是消息队列(MessageQueue),字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。

比较常见的MQ实现:

  • ActiveMQ

  • RabbitMQ

  • RocketMQ

  • Kafka

几种常见MQ的对比:

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP,XMPP,SMTP,STOMP OpenWire,STOMP,REST,XMPP,AMQP 自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒以内
消息可靠性 一般 一般

 

追求可用性:Kafka、 RocketMQ 、RabbitMQ

追求可靠性:RabbitMQ、RocketMQ

追求吞吐能力:RocketMQ、Kafka

追求消息低延迟:RabbitMQ、Kafka

简介

RabbitMQ 使用_第1张图片

 

RabbitMQ中的一些角色:

  • publisher:生产者

  • consumer:消费者

  • exchange:交换机,负责消息路由

  • queue:队列,存储消息

  • virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离

最好是每一个用户拥有不同的virtualHost,这样就保证只能看到属于自己的虚拟主机下的exchange和queue。具有很好的隔离性

下载、安装

RabbitMQ 下载、安装_naki_bb的博客-CSDN博客

RabbitMQ原始API使用

依赖

    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.9.RELEASE
        
    

        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
        
            org.springframework.boot
            spring-boot-starter-test
        

生产者

 public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.168.128");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("root");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

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

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
    }

消费者

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.168.128");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("root");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

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

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
    }

注意设置VirtuaHost、username、password需要对应,可以在管理后台查询 或者 创建对应的用户和VirtuaHost。

RabbitMQ 使用_第2张图片

也 可以在管理后台查看连接、channels, exchange, queue的其他信息。

基于SpringAMQP的API使用

SpringAMQP是基于AMQP协议定义的一套API规范,提供模板发送和接受消息,

其中spring-amqp是基础抽象,spring-rabbit是底层默认实现。

SpringAmqp的官方地址:Spring AMQP

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系

  • 基于注解的监听器模式,异步接收消息

  • 封装了RabbitTemplate工具,用于发送消息

simpleQueue简单队列

 simpleQueue没有exchange交换机,直接发往queue,消费一经被消息,就没有了。

依赖

    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.9.RELEASE
        
    

        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
        
            org.springframework.boot
            spring-boot-starter-test
        

配置

spring:
  rabbitmq:
    host: 192.168.168.128
    port: 5672
    username: root
    password: root
    virtual-host: /

生产者

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringRabbitMQPublisherTest {
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void simpleQueueSend(){
        String queueName = "simple.queue";
        String message = "hello, Spring amqp";
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitMQListener {

    @RabbitListener(queues = "simple.queue")
    public void simpleQueueListener(String msg) {
        System.out.println("consumer 接受到了simpleQueue : " + msg);
    }
}

WorkQueue任务队列

Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

RabbitMQ 使用_第3张图片

 

当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。

此时就可以使用work 模型,多个消费者共同处理消息处理,速度就能大大提高了。

生产者

和simpleQueue在生产者没有区别

    @Test
    public void workQueueSend() {
        String queueName = "simple.queue";
        String message = "hello, Spring amqp__";
        for (int i = 0; i < 50; i++) {
            rabbitTemplate.convertAndSend(queueName, message + i);
        }
    }

消费者

    @RabbitListener(queues = "simple.queue")
    public void workQueueListener1(String msg) throws InterruptedException {
        System.out.println("consumer1 接受到了simpleQueue : " + msg);
        Thread.sleep(20);
    }

    @RabbitListener(queues = "simple.queue")
    public void workQueueListener2(String msg) throws InterruptedException {
        System.err.println("consumer2 ..... 接受到了simpleQueue : " + msg);
        Thread.sleep(200);
    }

现在有2个consumer监听同一个队列,但是处理速度不一样,看消息时如何分配的

RabbitMQ 使用_第4张图片

 可以发现consumer1消费的都是偶数消息,cosumer2消费的都是奇数消息,他们是平均分配的,但是由于每个消费者的消费能力不同,造成了,要消费所有的消息时间完全取决于消费能力差的消费者。这时RabbitMQ默认的预分配,在没有消费完上一消息时,就会提前从queue拿回来其他的消息。这样显然有问题,浪费了消费能力好的,最好是能者多劳。减少总体消费全部消息的时间。

所以需要添加以下配置,设置一次能获取一条消息,处理完毕在获取下一个消息,这样就可以完全利用发挥性能好的消费者

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

发布(Publish)、订阅(Subscribe)

模型

RabbitMQ 使用_第5张图片

 

可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

  • Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:

    • Fanout:广播,将消息交给所有绑定到交换机的队列

    • Direct:定向,把消息交给符合指定routing key 的队列

    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

  • Consumer:消费者,与以前一样,订阅队列,没有变化

  • Queue:消息队列也与以前一样,接收消息、缓存消息。

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

Fanout 广播

RabbitMQ 使用_第6张图片

 

在广播模式下,消息发送流程是这样的:

  • 1) 可以有多个队列

  • 2) 每个队列都要绑定到Exchange(交换机)

  • 3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定

  • 4) 交换机把消息发送给绑定过的所有队列

  • 5) 订阅队列的消费者都能拿到消息

我们的实现步骤:

  • 创建一个交换机 demo.fanout,类型是Fanout

  • 创建两个队列fanout.queue1和fanout.queue2,绑定到交换机demo.fanout

消费者

方式一:声明方式

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfig {

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("demo.fanout");
    }

    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout.queue1");
    }

    @Bean
    public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1) {
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    @Bean
    public Queue fanoutQueue2() {
        return new Queue("fanout.queue2");
    }

    @Bean
    public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitMQListener {

    @RabbitListener(queues = "fanout.queue1")
    public void fanoutQueue1Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了queueQueue1 : " + msg);
    }

    @RabbitListener(queues = "fanout.queue2")
    public void fanoutQueue2Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了queueQueue2 : " + msg);
    }
}

方式二:注解方式

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("fanout.queue1"),
                    exchange = @Exchange(value = "demo.fanout", type = ExchangeTypes.FANOUT)
            )})
    public void fanoutQueue1Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了fanoutQueue1 : " + msg);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("fanout.queue2"),
                    exchange = @Exchange(value = "demo.fanout", type = ExchangeTypes.FANOUT)
            )})
    public void fanoutQueue2Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了fanoutQueue2 : " + msg);
    }

生产者

直接发送消息给exchange,具体到达哪些queue,生产者不关心

    @Test
    public void fanoutQueueSend() {
        String exchange = "demo.fanout";
        String message = "hello, Spring amqp fanout";
        rabbitTemplate.convertAndSend(exchange, "", message);
    }

总结

交换机的作用是什么?

  • 接收publisher发送的消息

  • 将消息按照规则路由到与之绑定的队列

  • 不能缓存消息,路由失败,消息丢失

  • FanoutExchange的会将消息路由到每个绑定的队列

声明队列、交换机、绑定关系的Bean是什么?

  • Queue

  • FanoutExchange

  • Binding

Direct定向

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

RabbitMQ 使用_第7张图片

 

在Direct模型下:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)

  • 消息的发送方在向 Exchange发送消息时,也必须指定消息的 RoutingKey

  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息

案例如下:

  1. 利用@RabbitListener声明Exchange、Queue、RoutingKey

  2. 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2

  3. 在publisher中编写测试方法,向itcast. direct发送消息

RabbitMQ 使用_第8张图片

 消费者

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("direct.queue1"),
                    exchange = @Exchange(value = "itcast.direct", type = ExchangeTypes.DIRECT),
                    key = {"blue", "red"}
            )
    })
    public void directQueue1Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了directQueue1 : " + msg);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("direct.queue2"),
                    exchange = @Exchange(value = "itcast.direct", type = ExchangeTypes.DIRECT),
                    key = {"yellow", "red"}
            )
    })
    public void directQueue2Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了directQueue2: " + msg);
    }

发送方

    @Test
    public void directQueueSend() {
        String exchange = "itcast.direct";
        String colorKey = "blue";
        String message = "hello, " + colorKey;
        rabbitTemplate.convertAndSend(exchange, colorKey, message);

        colorKey = "yellow";
        message = "hello, " + colorKey;
        rabbitTemplate.convertAndSend(exchange, colorKey, message);

        colorKey = "red";
        message = "hello, " + colorKey;
        rabbitTemplate.convertAndSend(exchange, colorKey, message);
    }

结果如下:

consumer ..... 接受到了directQueue1 : hello, blue
consumer ..... 接受到了directQueue2: hello, yellow
consumer ..... 接受到了directQueue1 : hello, red
consumer ..... 接受到了directQueue2: hello, red

只有订阅相关key的consumer才能收这个key的消息

描述下Direct交换机与Fanout交换机的差异?

  • Fanout交换机将消息路由给每一个与之绑定的队列

  • Direct交换机根据RoutingKey判断路由给哪个队列

  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似

Topic 通配饰

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

 #:匹配一个或多个词

*:匹配不多不少恰好1个词

举例:

item.#:能够匹配item.spu.insert 或者 item.spu

item.*:只能匹配item.spu

RabbitMQ 使用_第9张图片

 

解释:

  • Queue1:绑定的是china.# ,因此凡是以 china.开头的routing key 都会被匹配到。包括china.news和china.weather

  • Queue2:绑定的是#.news ,因此凡是以 .news结尾的 routing key 都会被匹配。包括china.news和japan.news

案例:

RabbitMQ 使用_第10张图片

 消费者

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("topic.queue1"),
                    exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC),
                    key = "china.#"
            )
    })
    public void topicQueue1Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了topicQueue1: " + msg);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue("topic.queue2"),
                    exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC),
                    key = "#.news"
            )
    })
    public void topicQueue2Listener(String msg) throws InterruptedException {
        System.err.println("consumer ..... 接受到了topicQueue2: " + msg);
    }

生产者

    @Test
    public void topicQueueSend() {
        String exchange = "itcast.topic";
        String key = "china.news";
        String message = "hello, " + key;
        rabbitTemplate.convertAndSend(exchange, key, message);

        key = "china.weather";
        message = "hello, " + key;
        rabbitTemplate.convertAndSend(exchange, key, message);
    }

运行结果:

consumer ..... 接受到了topicQueue2: hello, china.news
consumer ..... 接受到了topicQueue1: hello, china.news
consumer ..... 接受到了topicQueue1: hello, china.weather

总结

Direct交换机与Topic交换机的差异?

  • Topic交换机接收的消息RoutingKey必须是多个单词,以 **.** 分割

  • Topic交换机与队列绑定时的bindingKey可以指定通配符

  • #:代表0个或多个词

  • *:代表1个词

消息转换器

pring会把你发送的消息序列化为字节发送给MQ,接收消息的时候,还会把字节反序列化为Java对象。

只不过,默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:

  • 数据体积过大

  • 有安全漏洞

  • 可读性差

RabbitMQ 使用_第11张图片

 在消息生产者消费者都加入转json的依赖


    com.fasterxml.jackson.dataformat
    jackson-dataformat-xml
    2.9.10

并且都加入JsonMessageConverter的类

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

生产者

    @Test
    public void objectQueueSend() {
        String queueName = "object.queue";
        Map map = new HashMap<>();
        map.put("name", "张三");
        map.put("age", 22);
        rabbitTemplate.convertAndSend(queueName, map);
    }

RabbitMQ 使用_第12张图片 

 消费者


    @RabbitListener(queues = "object.queue")
    public void objectQueueListener(Map map) {
        System.out.println("consumer 收到object.queue : " + map);
    }

RabbitMQ 使用_第13张图片

 

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