RabbitMQ异步通信(持续更新)

 1、同步通信和异步通信

异步通信是消息代理(通知) 

同步调用的方式存在下列问题:

  • 拓展性差

  • 性能下降

  • 级联失败

异步调用的优势包括:

  • 耦合度更低

  • 性能更好

  • 业务拓展性强

  • 故障隔离,避免级联失败

 选用规则

  • 不在意调用结果

  • 性能要求高(调用量太长)

技术选型

消息Broker,目前常见的实现方案就是消息队列(MessageQueue),简称为MQ.

目比较常见的MQ实现:

  • ActiveMQ

  • RabbitMQ,并发量10w左右,微秒

  • RocketMQ

  • Kafka

2、安装

docker run \
 -e RABBITMQ_DEFAULT_USER=itheima \
 -e RABBITMQ_DEFAULT_PASS=123321 \
 -v mq-plugins:/plugins \
 --name mq \
 --hostname mq \
 -p 15672:15672 \
 -p 5672:5672 \
 --network hm-net\
 -d \
 rabbitmq:3.8-management

 基本介绍

其中包含几个概念:

  • publisher:生产者,也就是发送消息的一方

  • consumer:消费者,也就是消费消息的一方

  • queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理

  • exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。

  • virtual host:虚拟主机(DB同理),起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue

 具体流程:p发出信息发到交换机ex,交换机ex再把消息路由给队列que(可以路由一个、或者多个),消费者监听队列

测试使用

交换机负责路由,转发消息的,本身没有存储消息的能力

需要拿队列来绑定交换机(双向绑定)

数据隔离 

Users:Name:hmall PS:123

一个账号-对应一个项目-对应一个虚拟主机-数据隔离了

3、Java客户端

配置MQ

spring:
  rabbitmq:
    host:  # 你的虚拟机IP
    port: 5672 # 端口  15672是控制台 5672是发消息的
    virtual-host: /hmall # 虚拟主机
    username: hmall # 用户名
    password: 123 # 密码

基础收发

 消息发送

@SpringBootTest
public class SpringAmqpTest {

    @Autowired(required = false)
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, spring amqp!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

消息接收

@Slf4j
@Component
public class MqListener {
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者收到了simple.queue消息:"+msg);
    }
}

记得要配地址同发送方

接收消息是实时的,如果接收方启动中,发送方再次发送依然可以收到

WorkQueue(一个队列绑定多个消费者)(消费堆积)

发送方

@Test
    void testWorkQueue() throws InterruptedException {
        // 队列名称
        String queueName = "work.queue";
        for (int i = 1; i <=50; i++) {
            // 消息
            String message = "hello,worker,message_"+i;
            // 发送消息
            rabbitTemplate.convertAndSend(queueName, message);
            Thread.sleep(20);
        }
    }

接收方

@RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String msg){
        System.out.println("消费者1收到了work.queue消息:"+msg);
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String msg){
        System.err.println("消费者2收到了work.queue消息:"+msg);
    }

得到:当我们向队列中发送一条消息时,如果队列绑定了两个消费者,一条消息只能被一个消费者消费,平均的分配给每一个消费者

对消费者进行设置(改变两者处理能力)
@RabbitListener(queues = "work.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1收到了work.queue消息:"+msg);
        Thread.sleep(20);
    }

    @RabbitListener(queues = "work.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2收到了work.queue消息:"+msg);
        Thread.sleep(200);
    }

并未考虑消费者的能力还是平分

能者多劳(你必须全部处理完了才能继续处理)
spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

当单个消费者会处理不过来,队列的信息就会积累的越来越多

消息堆积问题

一方面是使用work模型,二方面是本身处理的速度要加快 

4、交换机类型

交换机的类型有四种:

  • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机

  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列

  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符

  • Headers:头匹配,基于MQ的消息头匹配,用的较少。

 发送者发送到交换机,不同交换机以不同的方式路由给其他队列

Fanout交换机(广播)

  1. 声明一个名为hmall.direct的交换机

  2. 声明队列direct.queue1,绑定hmall.directbindingKeybludred

  3. 声明队列direct.queue2,绑定hmall.directbindingKeyyellowred

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

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

Direct交换机 (定向路由)

在Direct模型下:

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

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

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

Topic交换机

 queue1只关心中国、queue2只关心新闻

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。

只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符!

通配符规则:

  • #:匹配一个或多个词

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

  • topic.queue1:绑定的是china.# ,凡是以 china.开头的routing key 都会被匹配到,包括:

    • china.news

    • china.weather

  • topic.queue2:绑定的是#.news ,凡是以 .news结尾的 routing key 都会被匹配。包括:

    • china.news

    • japan.news

topic可以是分割,但direct必须是固定的 

5、声明队列和交换机

在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来,交给运维。在这个过程中是很容易出现错误的。

因此推荐的做法是由程序启动时检查队列和交换机是否存在,如果不存在自动创建。

fanout交换机声明
bean声明
package com.itheima.consumer.config;

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 {
    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("hmall.fanout");
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}
注解声明
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue1"),
    exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
    key = {"red", "blue"}
))
public void listenDirectQueue1(String msg){
    System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue2"),
    exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
    key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg){
    System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}
direct交换机声明
bean声明
package com.itheima.consumer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectConfig {

    /**
     * 声明交换机
     * @return Direct类型交换机
     */
    @Bean
    public DirectExchange directExchange(){
        return ExchangeBuilder.directExchange("hmall.direct").build();
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue directQueue1(){
        return new Queue("direct.queue1");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithRed(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue1WithBlue(Queue directQueue1, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue directQueue2(){
        return new Queue("direct.queue2");
    }

    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithRed(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }
    /**
     * 绑定队列和交换机
     */
    @Bean
    public Binding bindingQueue2WithYellow(Queue directQueue2, DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }
}
注解声明
@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue1"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "china.#"
))
public void listenTopicQueue1(String msg){
    System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue2"),
    exchange = @Exchange(name = "hmall.topic", type = ExchangeTypes.TOPIC),
    key = "#.news"
))
public void listenTopicQueue2(String msg){
    System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
}

6、消息转换器

 

接收到了对象转化成了字节(丑)

 显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。

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

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
现如今,随着互联网的迅速发展,对于高并发,大流量的处理需求越来越多。在这样的情况下,传统的同步通信模式无法满足这种需求,因为同步通信方式会导致系统的实时性下降,响应速度变慢等问题。所以异步通信就成了重要的解决方案,而rabbitmq正是一种被广泛使用的异步通信工具之一。 rabbitmq是一个高度可靠,可扩展的消息代理,实现了AMQP协议。它可以完成消息的发送,接受和分发,适用于各种场景,比如:微服务架构,货物物流跟踪等。在使用rabbitmq的时候,常用的几个概念就是:producer,exchange,queue,consumer。 producer(生产者):使用rabbitmq client发送消息。 exchange(交换机):接收producer发送的消息,并将它分配给其它的queue进行存储。 queue(队列):存储exchange分配的消息。 consumer(消费者):从queue里面取出消息进行处理。 如何整合rabbitmq? 1.在pom.xml文件中加入rabbitmq的依赖。(可自行在maven仓库搜索) 2.在项目中编写producer和consumer,使用client进行消息发送和接受。 3.编写配置类,配置队列,交换机和bindings,这个非常重要,因为这决定了消息的路由规则。 4.使用spring提供的@RabbitListener注解,监听队列。@RabbitListener注解标注了一个方法,该方法会在监听到前面参数上配置的queue中有消息时自动执行。 5.在producer中使用rabbitTemplate对象发送消息。 整合rabbitmq异步通信可以完美地解决高并发,高流量,高吞吐量等问题。总结一下需要做的工作,就是编写producer和consumer,配置队列,交换机和bindings,使用@RabbitListener注解监听队列,使用rabbitTemplate对象发送消息。这些步骤都需要仔细考虑,只有正确的使用和配置才能达到最佳的使用效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值