一、消息概述

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

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

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

    • 队列(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. java poi excel导入模板设置下拉框

    import org.apache.poi.hssf.usermodel.DVConstraint; import org.apache.poi.hssf.usermodel.HSSFCell; im ...

  2. MyEclipse,Eclipse注释规范模板

    注释的作用:myeclipse中java文件头注释格式设置,最终可用于javadoc.exe生成API文档,同时提高了代码的可读性 设置方法: windows->preferences-> ...

  3. ramfs、rootfs和initramfs【转】

    ramfs, rootfs and initramfs October 17, 2005 Rob Landley <rob@landley.net> =================== ...

  4. Dagger2----一个最简单的Dagger2依赖的实现

    Dagger2是首个使用生成代码实现完整依赖注入的框架,极大降低了使用者的编码负担.Dagger2分析全部依赖并生成代码将这些依赖组织在一起,关于很多其它的Dagger2理论介绍请移步具体解释Dagg ...

  5. 记一次ORA-600[13011]

    SunOS 5.10 Oracle 10.2.0.2.0 开发环境某一数据库出现ora-600报错. alert.log中的报错信息: Thu Nov 13 15:11:43 2014 Errors ...

  6. Centos7 防火墙firewalld配置

    开启80端口 firewall-cmd --zone=public --add-port=80/tcp --permanent  出现success表明添加成功 移除某个端口 firewall-cmd ...

  7. flash builder 4.6 下载完成后安装不成功

    从网上下载了一下flash builder 4.6 下载完成后安装不成功,说是有一个安装被挂起,不成安装成功结果从注册表中删除了pendingobject,还是不行,没有办法,从网上搜了一下,发现了大 ...

  8. 【145】◀▶ .NET Framework类库索引

    C#编程基础: A1 ………… 基础A2 ………… using 关键字A3 ………… as 关键字A4 ………… is 关键字A5 ………… switch 关键字A6 ………… return 语句关键 ...

  9. http-2.4

    http-2.4 1)新特性 (1)MPM 支持运行为DSO 机制:以模块形式按需加载 (2)event MPM 生产环境可用 (3)异步读写机制 (4)支持每模块及每目录的单独日志级别定义 (5)每 ...

  10. IFRAME动态加载触发onload事件(转)

    原文地址:http://blog.ops.cc/webtech/javascript/f5nhm.html <body> <script>var iframe = docume ...