一、消息概述

  • 在大多数应用中,可以通过消息服务中间件来提升系统的异步通信扩展解耦流量削峰等能力。

  • 当消息发送者发送消息后,将由消息代理接管,消息代理保证消息传递到指定目的地

  • 消息队列主要有两种形式的目的地:

    • 队列(queue):点对点消息通信(point-to-point):消息发送者发送消息,消息代理将其送入一个队列中,消息接收者从队列中获取消息,消息被读取后被移出队列。注意:每一个消息只能从一个发送者发送到达唯一一个接收者。
    • 主题(topic):发布(publish)/订阅(subscribe)消息通信:发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么接收者(订阅者)就会在消息到达时同时收到消息。
  • JMS(Java Message Service):JAVA 消息服务--基于 JVM 消息代理的规范。ActiveMQ、HornetMQ 是 JMS 的实现。

  • AMQP(Advanced Message Queuing Protocol):高级消息队列协议,也是一个消息代理的规范,兼容 JMS,RabbitMQ是 AMQP 的实现。

二、RabbitMQ

  • 简介:RabbitMQ 是一个由 erlang 开发的 AMQP 的开源实现。

  • 核心概念:

    • Message:消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
    • Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
    • Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列
    • Queue:消息队列,用来保存消息直到发送给消费者一个消息可投入一个或多个队列。
    • Binding:绑定,用于关联消息队列和交换器。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 之间的绑定可以多对多的。
    • Connection:网络连接,比如一个 TCP 连接。
    • Channel:信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的 TCP 连接内的虚拟连接,AMQP 的命令都是通过信道发送出去的。因为建立和销毁一条 TCP 连接开销太大,所以使用信道来复用 TCP 连接。
    • Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
    • Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是/。
    • Broker:消息队列服务器实体。

Exchange 类型:Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:directfanouttopic、headers(性能较差,几乎不用)。

1、docker 安装 RabbitMQ

拉取镜像(阿里云服务器上):

[root@izwz9d74k4cznxtxjeeur9z ~]# docker pull rabbitmq:3-management

镜像实例化为容器:

[root@izwz9d74k4cznxtxjeeur9z ~]# docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq a7f2574d507f

# 第一个端口映射为 rabbitmq 客户端服务,第二个端口映射为 web 管理界面

测试访问(IP + 端口):

输入账号:guest 密码:guest

2、测试 RabbitMQ

以上图中的 3 种交换器、4 个队列为例,测试 RabbitMQ:

  1. 添加一个名为 exchange.direct ,类型为 direct 交换器;添加一个名为 exchange.fanout,类型为 fanout 的交换器;添加一个名为 exchange.topic,类型为 topic 的交换器。

  2. 添加 4 个队列,分别名为 atguigu、atguigu.news、atguigu.emps 和 gulixueyuan.news。

  1. 添加交换器 和 队列之间的绑定(Routing Key):①为 exchange.direct 与 4 个队列之间添加 Bindings,Routing Key 分别为队列名 atguigu、atguigu.news、atguigu.emps 和 gulixueyuan.news。(direct 类型交换器精确匹配路由键)

    ②为 exchange.fanout 与 4 个队列之间添加 Bindings,Routing Key 分别为队列名 atguigu、atguigu.news、atguigu.emps 和 gulixueyuan.news。(fanout 类型交换器与路由键无关,它是广播的)

    ③为 exchange.topic 与 4 个队列之间添加如示意图所示的 Bindings。绑定结果如下:

4.发布消息:①向 exchange.direct 发送 4 条消息,消息的 Routing key 分别为 atguigu、atguigu.news、atguigu.emps 和 gulixueyuan.news。

预测 4 个队列各自有一条消息,消息发送到队列根据路由键来的。

依次读取消息并从队列中移除:

​ ②向 exchange.fanout 中 发送一条消息,路由键随意,由于它是广播的,所以 4 个队列都会得到该消息。

​ 移除所有消息。

​ ③向 exchan.topic 中 发送一条消息,路由键为 atguigu.news,由于队列 atguigu 绑定的路由键为 atguigu.#,匹配成功,收到消息,队列 gulixueyuan.news 绑定的路由键为*.news,匹配成功收到消息。同理另外 2 个队列也会收到消息。若发送的消息键值为 abc.news, 则只有队列 gulixueyuan.news 和 guigu.news 收到消息。

3、Spring Boot 使用 RabbitMQ

  • RabbitAutoConfiguration 自动配置了连接工厂 rabbitConnectionFactory

    @Configuration
    @ConditionalOnClass({ RabbitTemplate.class, Channel.class })
    @EnableConfigurationProperties(RabbitProperties.class)
    @Import(RabbitAnnotationDrivenConfiguration.class)
    public class RabbitAutoConfiguration { @Configuration
    @ConditionalOnMissingBean(ConnectionFactory.class)
    protected static class RabbitConnectionFactoryCreator { @Bean
    public CachingConnectionFactory rabbitConnectionFactory(
    RabbitProperties properties,
    ObjectProvider<ConnectionNameStrategy> connectionNameStrategy)
    throws Exception {
    PropertyMapper map = PropertyMapper.get();
    CachingConnectionFactory factory = new CachingConnectionFactory(
    getRabbitConnectionFactoryBean(properties).getObject());
    map.from(properties::determineAddresses).to(factory::setAddresses);
    map.from(properties::isPublisherConfirms).to(factory::setPublisherConfirms);
  • RabbitProperties 封装了 RabbitMQ 的配置

    @ConfigurationProperties(prefix = "spring.rabbitmq")
    public class RabbitProperties { /**
    * RabbitMQ host.
    */
    private String host = "localhost"; /**
    * RabbitMQ port.
    */
    private int port = 5672; /**
    * Login user to authenticate to the broker.
    */
    private String username = "guest";
  • AmqpAdmin : RabbitMQ 系统管理功能组件,创建和删除 Queue,Exchange,Binding

        @Bean
    @ConditionalOnSingleCandidate(ConnectionFactory.class)
    @ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
    @ConditionalOnMissingBean
    public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
    } }
    @ManagedResource(description = "Admin Tasks")
    public class RabbitAdmin implements AmqpAdmin, ApplicationContextAware, ApplicationEventPublisherAware,
    BeanNameAware, InitializingBean {
    ... @Override
    public void declareExchange(final Exchange exchange) {
    try {
    this.rabbitTemplate.execute(channel -> {
    declareExchanges(channel, exchange);
    return null;
    });
    }
    catch (AmqpException e) {
    logOrRethrowDeclarationException(exchange, "exchange", e);
    }
    } @Override
    @ManagedOperation(description = "Delete an exchange from the broker")
    public boolean deleteExchange(final String exchangeName) {
    return this.rabbitTemplate.execute(channel -> { // NOSONAR never returns null
  • RabbitTemplate :给 RabbitMQ 发送和接受消息

    @Bean
    @ConditionalOnSingleCandidate(ConnectionFactory.class)
    @ConditionalOnMissingBean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
    PropertyMapper map = PropertyMapper.get();
    RabbitTemplate template = new RabbitTemplate(connectionFactory);
    MessageConverter messageConverter = this.messageConverter.getIfUnique();
    if (messageConverter != null) {
    template.setMessageConverter(messageConverter);
    }
    public class RabbitTemplate extends RabbitAccessor // NOSONAR type line count/comment density
    implements BeanFactoryAware, RabbitOperations, MessageListener,
    ListenerContainerAware, PublisherCallbackChannel.Listener, Lifecycle, BeanNameAware {
    ...
    @Override
    public void convertAndSend(Object object) throws AmqpException {
    convertAndSend(this.exchange, this.routingKey, object, (CorrelationData) null);
    }

1.使用 IDEA Spring Initializer 创建 Spring Boot 项目并选中 RabbitMQ 模块。

2.配置 RabbitMQ

application.properties:

spring.rabbitmq.host=xx.xx.xx.xx
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

3.测试

  • 使用 AmqpAdmin 创建 Exchange、队列和相应的 Bindings。

    package com.yunche;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.amqp.core.AmqpAdmin;
    import org.springframework.amqp.core.Binding;
    import org.springframework.amqp.core.DirectExchange;
    import org.springframework.amqp.core.Queue;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SpringRabbitmqApplicationTests { @Autowired
    AmqpAdmin amqpAdmin;
    @Test
    public void contextLoads() {
    amqpAdmin.declareExchange(new DirectExchange("test-direct-exchange"));
    System.out.println("创建了一个 direct 类型的 exchange");
    amqpAdmin.declareQueue(new Queue("test-queue"));
    System.out.println("创建了一个队列");
    amqpAdmin.declareBinding(new Binding("test-queue", Binding.DestinationType.QUEUE, "test-direct-exchange", "test-routingkey", null));
    System.out.println("添加了绑定,路由键为 test-routingkey");
    } }
  • 使用 RabbitTemplate 发送和接收消息:

    @Autowired
    RabbitTemplate rabbitTemplate;
    @Test
    public void sendMsg() {
    rabbitTemplate.convertAndSend("test-direct-exchange","test-routingkey", "hello world");
    }
    @Test
    public void receiveMsg() {
    String msg = (String)rabbitTemplate.receiveAndConvert("test-queue");
    System.out.println(msg);
    } /*Output:hello world*/
    @Test
    public void sendObject() { rabbitTemplate.convertAndSend("test-direct-exchange","test-routingkey", new Book("红楼梦", "曹雪芹"));
    } private static class Book implements Serializable {
    private String name;
    private String author; public String getName() {
    return name;
    } public void setName(String name) {
    this.name = name;
    } public Book(String name, String author) {
    this.name = name;
    this.author = author;
    } public String getAuthor() { return author;
    } public void setAuthor(String author) {
    this.author = author;
    }
    } @Test
    public void receiveObject() {
    Book b = (Book)rabbitTemplate.receiveAndConvert("test-queue");
    System.out.println(b.name);
    } /*Output: 红楼梦*/

4.监听消息

@RabbitListener 监听消息队列的内容;首先用@EnableRabbit 开启基于注解的 RabbitMQ 模式

package com.yunche;

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableRabbit
@SpringBootApplication
public class SpringRabbitmqApplication { public static void main(String[] args) {
SpringApplication.run(SpringRabbitmqApplication.class, args);
} }

首先运行单元测试中的 sendObject() 方法发送消息,然后控制台立即输出消息。

@RabbitListener(queues = "test-queue")
public void msgListener(Book book) {
System.out.println(book.getAuthor());
} /*Output:曹雪芹*/

三、参考资料

尚硅谷.Spring Boot 高级

Spring Boot 与消息的更多相关文章

  1. Spring Boot与消息

    一.概述 1. 大多应用中,可通过消息服务中间件来提升系统异步通信.扩展解耦能力 2. 消息服务中两个重要概念: 消息代理(message broker)和目的地(destination) 当消息发送 ...

  2. Spring Boot高级

    Spring Boot高级内容概要一.Spring Boot与缓存二.Spring Boot与消息三.Spring Boot与检索四.Spring Boot与任务五.Spring Boot与安全六.S ...

  3. Spring Boot 完整讲解

    SpringBoot学习笔记 文章写得比较详细,所以很长(105336 字数),可以参考目录 文章目录 SpringBoot学习笔记 @[toc] 一. Spring Boot 入门 预:必须掌握的技 ...

  4. Spring Boot使用Redis进行消息的发布订阅

    今天来学习如何利用Spring Data对Redis的支持来实现消息的发布订阅机制.发布订阅是一种典型的异步通信模型,可以让消息的发布者和订阅者充分解耦.在我们的例子中,我们将使用StringRedi ...

  5. spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的?

    spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的? 消息中间件在解决异步处理,模块间解耦和,和高流量场景的削峰,等情况下有着很广泛的应用 . 本文将跟大家一起 ...

  6. Sping Boot入门到实战之实战篇(一):实现自定义Spring Boot Starter——阿里云消息队列服务Starter

    在 Sping Boot入门到实战之入门篇(四):Spring Boot自动化配置 这篇中,我们知道Spring Boot自动化配置的实现,主要由如下几部分完成: @EnableAutoConfigu ...

  7. Spring Boot消息队列应用实践

    消息队列是大型复杂系统解耦利器.本文根据应用广泛的消息队列RabbitMQ,介绍Spring Boot应用程序中队列中间件的开发和应用. 一.RabbitMQ基础 1.RabbitMQ简介 Rabbi ...

  8. 在Spring Boot框架下使用WebSocket实现消息推送

    Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...

  9. Spring Kafka和Spring Boot整合实现消息发送与消费简单案例

    本文主要分享下Spring Boot和Spring Kafka如何配置整合,实现发送和接收来自Spring Kafka的消息. 先前我已经分享了Kafka的基本介绍与集群环境搭建方法.关于Kafka的 ...

随机推荐

  1. NDK开发,没有你想象的那么难

    NDK:Native Development Kit原生开发工具 NDK能干什么:NDK使得在android中,java能够调用C函数库. 为什么要用NDK:我们都知道.java是半解释型语言,非常e ...

  2. Codeforces Round #267 (Div. 2) C. George and Job(DP)补题

    Codeforces Round #267 (Div. 2) C. George and Job题目链接请点击~ The new ITone 6 has been released recently ...

  3. 训练深度学习网络时候,出现Nan是什么原因,怎么才能避免?——我自己是因为data有nan的坏数据,clear下解决

    from:https://www.zhihu.com/question/49346370   Harick     梯度爆炸了吧. 我的解决办法一般以下几条:1.数据归一化(减均值,除方差,或者加入n ...

  4. mipi差分信号原理

    差分信号,什么是差分信号 一个差分信号是用一个数值来表示两个物理量之间的差异.从严格意义上来讲,所有电压信号都是差分的,因为一个电压只能是相对于另一个电压而言的.在某些系统里,系统’地’被用作电压基准 ...

  5. EasyUI Datagrid 分页显示(客户端)

    转自:https://blog.csdn.net/metal1/article/details/17536185 EasyUI Datagrid 分页显示(客户端) By ZYZ 在使用JQuery ...

  6. 关于js-cookie使用出现兼容性问题以及js-cookie的如何使用

    最近使用vue开发的项目,开发过程引入了js-cookie插件,在PC端以及移动端网页调试都没出现问题,但是打包成APP在安卓手机调试发现使用js-cookie保存的数据失效了,然后只能使用local ...

  7. WebService基于soapheader的身份验证

    用WebService开发接口十分方便.但接口提供的数据不应是对所有人可见的,我们来利用SoapHeader写一个简单的身份验证Demo 目录 创建WebService项目(带SoapHeader) ...

  8. [SDOI2013]泉

    题目描述 作为光荣的济南泉历史研究小组中的一员,铭铭收集了历史上x个不同年份时不同泉区的水流指数,这个指数是一个小于. 2^30的非负整数.第i个年份时六个泉区的泉水流量指数分别为 A(i,l),A( ...

  9. sublime 自定义快捷键

    [ { "keys": ["alt+space"], "command": "auto_complete" }, // ...

  10. JavaScript(十二)事件

    Dom事件 1.DOM0级事件 on事件 只能 监听冒泡阶段 切只能绑定一个事件 dom.onclick = function(){}; 2.Dom2级事件 可以绑定多次事件    可以通过设置fla ...