RabbitMQ-入门

介绍

消息队列

消息:指的是应用间传递的数据。

消息队列是一种应用程序对应用程序直接的通信方法,应用程序通过读出队列的消息来通信,是一种跨进程的、异步的通信机制,用于上下游传递信息,消息包括文本字符和json等。

消息中间件,利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,在分布式环境下扩展进程间的通信。

消息队列的传递模式分为两种方式:点对点,这种方式基于队列实现;还有一种是发布订阅模式(广播模式)。

RabbitMQ

采用Erlang语言实现AMQP协议的消息中间件,用于分布式系统中存储转发消息。轻量级,易于在本地和云端部署。支持多种消息传递协议。RabbitMQ可以部署在分布式和联合配置中,以满足大规模、高可用性的要求。

RabbitMQ整体上是一个生产者和消费者模型,主要负责接收、存储和转发消息。

RabbitMQ安装

使用docker拉取RabbitMQ镜像

  • docker search rabbitmq:查找镜像

  • docker  pull rabbitmq :3.8.3-management:一般拉取-management结尾的版本(有图形化界面)

    RabbitMQ-入门_第1张图片

  • docker run -d --name myrabbitmq -p 15672:15672 -p 5672:5672 rabbitmq:3.8.3-management:启动镜像服务

    • -d :后台运行;

    • --name :设置镜像的名称;

    • -p 15672:15672 :rabbitmq的后台管理端口映射;【这里需要做端口映射才能正常访问】

    • -p 5672:5672 :RabbitMQ服务端口映射;

  • 登录:虚拟ip+管理端ip15672,用户名:guest;密码:guest。如果正常访问,登录成功,即为安装成功。

    RabbitMQ-入门_第2张图片

整体架构

架构图

RabbitMQ-入门_第3张图片

运转流程

内部大致的运转流程如下:

RabbitMQ-入门_第4张图片

生产者发送消息:

RabbitMQ-入门_第5张图片

  • 生产者连接到RabbitMQ Broker,建立了一个连接(Connection),开启信道(Channel);

  • 生产者声明一个交换器,并设置交换机类型、是否持久化等相关属性;

  • 生产者声明一个队列,并设置相关属性:是否排他、是否持久化、是否自动删除等;

  • 生产者通过路由键将交换器和队列进行绑定;

  • 生产者发送消息到RabbitMQ Broker,包含路由键、交换器等信息;

  • 交换器根据收到的路由键查询匹配的队列,如果找到了队列,将消息存入相应的队列中,否则,根据生产者配置的属性选择丢弃还是回退给生产者;

  • 最后关闭信道、关闭连接。

消费者接收消息:

RabbitMQ-入门_第6张图片

  • 消费者连接到RabbitMQ Broker,建立一个连接,开启一个信道(Channel);

  • 向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及一些准备工作。

  • 等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收消息;

  • 消费者确认(ack)接收到的消息;

  • RabbitMQ从队列中删除相应已经被确认的消息;

  • 最后关闭信道,关闭连接。

相关概念解释

  • Producer:生产者,消息的投递方;

生产者创建信息,发布到RabbitMQ,消息一般是两个部分:一个是消息体,是一个带有业务逻辑结构的数据;另一部分是消息的标签,用来表述这条消息,比如一个交换器的名称和一个路由键。

  • Consumer:消费者,消息的接收方;

消费者连接到RabbitMQ服务器,订阅到队列上。当消费者消费一条消息时,只是消费消息的消息体而已。在消息路由的过程中,消息的标签会丢弃,存入到队列中的消息只有消息体,消费者也只会消费到消息体,不会知道消息的生产者是谁。

  • Broker:消息中间件的服务节点,相当于消息的中转站。

RabbitMQ的服务节点或者是RabbitMQ的服务实例。

  • Queue:队列,RabbitMQ的内部对象,用于存储消息。

RabbitMQ的消息只能存储在队列中,最终将消息投递到队列中,消费者可以从队列中获取消息来消费;

多个消费者可以订阅同一个队列,这时队列中的消息会被平摊(轮询)分给多个消费者进行处理,不是每个消费者都收到所有的消息进行消费。

  • Exchange:交换器。

生产者将消息发送到Exchange,由交换器将消息路由到队列中,若是路由不到,对消息进行丢弃或者是返回给生产者。

fanout交换器:把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中;

direct交换器:把消息路由到那些BindingKey和RoutingKey完全匹配的队列中;

topic交换器:按照匹配规则进行消息传递,基本也是按照BindingKey和RoutingKey进行匹配的,但是可以规定匹配的规则:

    • RoutingKey:生产者将消息发给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则,而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才生效;

    • BindingKey:绑定。通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确将消息路由到队列了。

    • RoutingKey为一个“.”分隔的字符串,如“com.rabbitmq.client”,被“.”分隔开的每一段独立的字符串称为一个单词;

    • BindingKey同上,也是“.”分隔的字符串;但是BindingKey可以存在两种特殊字符串"*"和"#",用于做模糊匹配。"*"用来匹配一个单词,"#"用来匹配多规格单词(允许匹配0个)。

RabbitMQ-入门_第7张图片

  • 配置路由键为“com.rabbitmq.client”的消息会路由到Queue1和Queue2中;

  • 路由键“com.hidden.client”消息只会路由到Queue2中;

  • 路由键“com.hidden.demo”的消息只会路由到Queue2中;

  • 路由键“java.rabbitmq.demo”的消息只会路由到Queue1中;

  • 路由键为“java.util.concurrent”消息会丢失或者是返回给生产者。

headers交换器:不依赖于路由键的匹配规则来路由消息,是根据发送的消息内容中的headers属性进行匹配。heaers类型的交换器性能会很差,不实用,基本不会使用。

工作模式

  • simple模式【简单的收发模式】

    RabbitMQ-入门_第8张图片

    1. 生产者将消息放入队列中,消费者进行监听(while)队列,若有消息则将其消费掉,队列中会自动清除消息体;

    2. 这种模式很容易消息丢失,可以设置手动ack,设置后,在消息处理完毕后需要及时发送ack消息给队列,否则会内存溢出。

  • work工作模式【资源竞争】

    RabbitMQ-入门_第9张图片

    1. 生产者将消息放入队列中,可以有多个消费者,这些消费者同时监听同一个队列,这些消费者是竞争关系,谁先拿到谁就负责消费消息。

    2. 在 高并发情况下,默认会产生一个消息被多个消费者共同使用,可以设置一个开关Syncronize,保证一条消息只能被一个消费者使用。

  • publish/subscribe发布订阅模式【共享资源】

    RabbitMQ-入门_第10张图片

    1. 生产者将消息发给Broker,由交换机将消息转发到绑定在该交换器的每个队列中,所有绑定这个交换器的队列都会接收到消息。消费者可以有多个,它们可以根据自己的要求监听所需的队列。

  • routing路由模式

    RabbitMQ-入门_第11张图片

    1. 消息生产者将消息发送给交换机,按照路由匹配对应的消息队列。这个路由为字符串,当前产生的消息携带路由字符(对象的方法)。交换机根据路由的key匹配对应的消息队列,当消费者监听对应的队列才能消费此消息;

    2. 路由字符串根据业务功能定义;

    3. 从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

  • topic主题模式(路由模式的一种)

    RabbitMQ-入门_第12张图片

    1. *和#代表通配符,*表示多个单词,#表示一个单词;

    2. 路由功能添加模糊匹配;

    3. 消息生产者产生消息,把消息发送给交换机;

    4. 交换机根据key的规则进行模糊匹配到对应的队列,由队列的监听消费者接收消息并消费。

举个例子【topic主题模式】

利用topic主题模式,实现消息的发送和匹配接收。

yaml配置

spring:
  rabbitmq:
    host: 192.168.209.200
    port: 5672
    username: guest
    password: guest
    #连接超时,单位毫秒,0表示无穷大,不超时
    connection-timeout: 10000s 
    # 是否启用【发布返回】
    publisher-returns: true 
    # correlated(发布确认模式),simple(使用waitForConfirms()或waitForConfirmsOrDie()发布时),none(发布非确认模式)
    publisher-confirm-type: correlated 
    #虚拟主机,用于不同的工作组
    virtual-host: / 
server:
  port: 8083

配置类

@Configuration
public class TopicRabbitConfig {
    /**
     * 队列msg
     */
    @Bean("msg")
    public Queue getQueueMsg() {
        return new Queue("msg.queue", true, false, false);
    }
    /**
     * 队列notes
     */
    @Bean("notes")
    public Queue getQueueNotes() {
        return new Queue("notes.queue", true, false, false);
    }
    /**
     * 交换机
     */
    @Bean
    public TopicExchange getTopicExchange() {
        return new TopicExchange("topic-exchange", true, false);
    }
    /**
     * 绑定队列msg到交换器
     * 绑定键:#.topic.* 
     * 表示:能匹配到topic前面出现的所有单词,能匹配到topic后面出现的一个单词
     *
     */
    @Bean
    public Binding bindingMsg() {
        return BindingBuilder.bind(getQueueMsg()).to(getTopicExchange()).with("#.topic.*");
    }
    /**
     * 绑定队列notes到交换器
     * 绑定键为:#.topic.#   
     * 表示能匹配到topic前面的所有出现的单词,也能匹配到topic后面出现的所有单词
     */
    @Bean
    public Binding bindingNotes() {
        return BindingBuilder.bind(getQueueNotes()).to(getTopicExchange()).with("#.topic.#");
    }
}

生产者

@Component
@Slf4j
public class TopicProducer {
    @Resource
    private AmqpTemplate amqpTemplate;
    public void send(String msg) {
        log.info("发送的消息为:" + msg);
        // 将消息发送到tpic-exchange交换器,根据路由键:rabbit.test.topic.msg.notes进行转发[该路由键决定着把消息存入哪些队列中]
        amqpTemplate.convertAndSend("topic-exchange", "rabbit.test.topic.msg.notes", msg);
    }
}

消费者​​​​​

@Component
@Slf4j
public class TopicConsumer {
    /**
     * msg.queue队列监听
     */
    @RabbitListener(queues = "msg.queue")
    public void receiveMsg(String msg) {
        log.info("监听msg.queue队列中的消息.....收到的消息为:{}", msg);
    }
    /**
     * notes.queue队列监听
     */
    @RabbitListener(queues = "notes.queue")
    public void receiveNotes(String msg) {
        log.info("监听notes.queue队列中的消息.....收到的消息为:{}", msg);
    }
}

测试

@SpringBootTest
class RabbitTopicDemoApplicationTests {
    @Autowired
    TopicProducer topicProducer;
    @Test
    void contextLoads() {
        topicProducer.send("test---topic交换器");
    }
}

结果:只有notes.queue队列中有消息,该队列在绑定交换器的时候按照#.topic.#进行匹配,可以成功匹配到路由键rabbit.test.topic.msg.notes

2022-01-12 15:47:36.083  INFO 48252 --- [ntContainer#0-1] com.xlh.topic.consumer.TopicConsumer     : 监听notes.queue队列中的消息.....收到的消息为:test---topic交换器

若是把路由键改为:rabbit.test.topic.msg,两个队列都可以收到消息​​​​​​​

2022-01-12 16:42:52.539  INFO 19280 --- [ntContainer#1-1] com.xlh.topic.consumer.TopicConsumer     : 监听notes.queue队列中的消息.....收到的消息为:test---topic交换器
2022-01-12 16:42:52.540  INFO 19280 --- [ntContainer#0-1] com.xlh.topic.consumer.TopicConsumer     : 监听msg.queue队列中的消息.....收到的消息为:test---topic交换器

总结

这些都是RabbitMQ的基础内容,还有好多好多知识,关于进阶的下次再进行学习了。

请关注公众号:喵喵伸懒腰

专注java学习,共同成长!

RabbitMQ-入门_第13张图片

你可能感兴趣的:(rabbitmq,java,分布式)