【RabbitMQ】MQ基础

84 阅读5分钟

MQ基础

MQ提供接受消息、存储消息、发送消息的功能

MQ的好处

  1. 处理高并发任务 比如有一个系统每秒只能支持10000的处理量,但是高峰时期达到了20000,那么就用MQ去处理,先处理10000,再处理10000,最后统一返回结果。
  2. 应用解耦 有些系统有几个子系统,比如创建订单后,耦合调用支付系统、库存系统、订单系统。如果其中一个系统出了问题,要花一段时间去解决,那么就会造成下单异常。但是利用MQ,可以解除系统之间的互相调用,如果其中一个系统出了问题,消息会缓存在MQ中,下单操作可以完成,等出问题的系统恢复后继续处理。
  3. 处理分布式事务 MQ有多次提交的特点,在失败的情况下可以多次重试或者回滚,保证事务一致性。
  4. 异步处理 有些服务是异步的,比如A调用B,如果B需要花很长时间,A要知道B什么时候完成,有两种方式,要么每隔一段时间调用B的api查询B,要么等B结束后callback通知A。有了MQ后就可以B通知MQ,然后MQ把消息发送给A。

MQ的类型

  1. ActiveMQ
  2. Kafka 吞吐量最高,适合收集日志。
  3. RabbitMQ 中小公司用的最多。
  4. RocketMQ 阿里产品,适合金融场景。

安装RabbitMQ

我这里以mac为例 brew install rabbitmq brew services start rabbitmq; rabbitmq-plugins enable rabbitmq_management 输入localhost:15672 账号密码:guest guest 在这里插入图片描述

RabbitMQ整体架构

在这里插入图片描述

  • Publisher 发布者
  • exhange 交换机
  • queue 队列
  • consumer 订阅者
  • 一个MQ可以创建多个VirtualHost虚拟主机,起到数据隔离的作用。

客户端快速入门

发送消息

在这里插入图片描述

  1. 新建队列 在这里插入图片描述

  2. binding关联交换机 在这里插入图片描述

  3. 发送消息 在这里插入图片描述

数据隔离

在这里插入图片描述 此时只有一个默认的vh 在这里插入图片描述 添加用户 在这里插入图片描述 此时admin没有vh的操作权限,需要用他的账号创建一个vh 在这里插入图片描述 在这里插入图片描述 每个vh都有自己的交换机,每个user只能操作自己的vh,体现了数据隔离 在这里插入图片描述

JAVA RabbitMQ客户端

RabbitMQ的通信协议是AMQP(Advanced Message Queuing Protocol)高级消息队列协议 而Spring开发了一套基于AMQP协议的api规范SpringAMQP,此外SpringRabbit是SpringAMQP的唯一具体实现。

快速入门

在这里插入图片描述

  1. 引入依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  1. 配置MQ客户端信息
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /
    username: guest
    password: guest
  1. 发送消息,利用RabbitTemplate工具类
 @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void test(){
        String queueName="hello.queue1";
        String msg="How Are You?";
        rabbitTemplate.convertAndSend(queueName,msg);
    }
  1. 接受消息
@Component
public class Listener {
    @RabbitListener(queues = "hello.queue1")
    public void listen(String msg){
        System.out.println(msg);
    }
}

在这里插入图片描述

WorkQueue 任务模型

让多个消费者绑定一个队列 在这里插入图片描述 在默认情况下,一个queue绑定多个消费者,会采用轮询机制发送给消费者,不会管有没有消费者是否已经接受完消息,会导致消息堆积。

    @RabbitListener(queues = "workQueue")
    public void listen1(String msg) throws InterruptedException {
        System.out.println("消费者1接受到消息:"+msg);
        Thread.sleep(20);
    }
    @RabbitListener(queues = "workQueue")
    public void listen2(String msg) throws InterruptedException {
        System.err.println("消费者2接受到消息:"+msg);
        Thread.sleep(200);
    }

在这里插入图片描述 解决方案: 配置加上

    listener:
      simple:
        prefetch: 1

每次只处理一条消息 在这里插入图片描述 能者多劳

交换机

如果没有交换机,直接通过queue发送消息,只有一个服务收到消息。

Fanout交换机

广播模式,会将消息发给每一个与之绑定的queue 在这里插入图片描述

    @Test
    public void fanouttest(){
        String exchange="test.fanout";
        String msg="How Are You?";
        rabbitTemplate.convertAndSend(exchange,null,msg);
    }
 @RabbitListener(queues = "fanout.q1")
    public void fanout1(String msg) throws InterruptedException {
        System.out.println("fanout消费者1接受到消息:"+msg);
    }
    @RabbitListener(queues = "fanout.q2")
    public void fanout2(String msg) throws InterruptedException {
        System.err.println("fanout消费者2接受到消息:"+msg);
    }

在这里插入图片描述

Direct交换机

定向路由给指定的queue(对暗号) 在这里插入图片描述 在这里插入图片描述 绑定时指定routingKey 在这里插入图片描述

    @Test
    public void directtest(){
        String exchange="test.direct";
        String msg="How Are You?";
        rabbitTemplate.convertAndSend(exchange,"red",msg);
    }
    @RabbitListener(queues = "direct.q1")
    public void direct1(String msg) throws InterruptedException {
        System.out.println("direct消费者1接受到消息:"+msg);
    }
    @RabbitListener(queues = "direct.q2")
    public void direct2(String msg) throws InterruptedException {
        System.err.println("direct消费者2接受到消息:"+msg);
    }

在这里插入图片描述

Topic交换机

与Direct不同在于,Topic的routingKey可以指定多个,以.分隔,并且可以用通配符#(0个或多个)或*(一个) 比如有RoutingKey:

  • China.news
  • China.weather
  • Japan.news
  • Japan.weather

那么可以指定RoutingKey:#.weather(所有国家的天气) 在这里插入图片描述

    @Test
    public void topictest(){
        String exchange="test.topic";
        String msg="china.news";
        rabbitTemplate.convertAndSend(exchange,"China.news",msg);
    }

    @RabbitListener(queues = "topic.q1")
    public void topic1(String msg) {
        System.out.println("topic消费者1接受到消息:"+msg);
    }
    @RabbitListener(queues = "topic.q2")
    public void topic2(String msg) {
        System.out.println("topic消费者2接受到消息:"+msg);
    }

在这里插入图片描述

Java声明队列和交换机

@Configuration
public class FanoutConfiguration {

    @Bean
    public FanoutExchange fanoutExchange(){
        // 创建交换机
        return new FanoutExchange("hmall.fanout2");
    }

    @Bean
    public Queue fanoutQueue3(){
        // 创建queue
        return new Queue("fanout.queue3");
    }

    @Bean
    public Binding fanoutBinding3(Queue fanoutQueue3, FanoutExchange fanoutExchange){
    	// 绑定方式1
        return BindingBuilder.bind(fanoutQueue3).to(fanoutExchange);
    }

    @Bean
    public Queue fanoutQueue4(){
        return new Queue("fanout.queue4");
    }

    @Bean
    public Binding fanoutBinding4(){
    	// 绑定方式2
        return BindingBuilder.bind(fanoutQueue4()).to(fanoutExchange());
    }
}

但是这种方式比较麻烦。 基于注解

 @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg) throws InterruptedException {
        System.out.println("消费者2 收到了 direct.queue2的消息:【" + msg +"】");
    }

消息转换器

默认情况下发送map类型会转换成字节码。 在这里插入图片描述 引入依赖

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>
@SpringBootApplication
public class PublisherApplication {
    public static void main(String[] args) {
        SpringApplication.run(PublisherApplication.class);
    }

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

这样获得的消息就是map类型。