Spring消息之AMQP.

一、AMQP 概述

AMQP(Advanced Message Queuing Protocol),高级消息队列协议。

    简单回忆一下JMS的消息模型,可能会有助于理解AMQP的消息模型。在JMS中,有三个主要的参与者:消息的生产者、消息的消费者以及在生产者和消费者之间传递消息的通道(队列或主题)。在JMS中,通道有助于解耦消息的生产者和消费者,但是这两者依然会与通道相耦合。与之不同的是,AMQP的生产者并不会直接将消息发布到队列中。AMQP在消息的生产者以及传递信息的队列之间引入了一种间接的机制:Exchange。如下图:

    哈哈,笔主从今天开始也要学着自己画图了。

    来看看 AMQP 消息的通信过程。首先,生产者把消息发给 Exchange,并带有一个 routing key。其次,Exchange 和 队列 之间 通过 binging 通信,binging 上也有 一个 routing key,AMQP定义了四种不同类型的Exchange,每一种都有不同的路由算法,根据Exchange的算法不同,它可能会使用消息的routing key或参数,并与 binding 的routing key或参数进行对比,来决定是否要将信息放到队列中。然后,消费者从每个队列中取出消息。

    Exchange 的路由算法:

  • Direct:如果 消息的routing key 与 binding的routing key 直接匹配的话,消息将会路由到该队列上;
  • Topic:如果 消息的routing key 与 binding的routing key 符合通配符匹配的话,消息将会路由到该队列上;
  • Headers:如果 消息参数表中的头信息和值 都与 bingding参数表中 相匹配,消息将会路由到该队列上;
  • Fanout:不管消息的routing key和参数表的头信息/值是什么,消息将会路由到所有队列上。

    AMQP 与 JMS 的区别:

1、AMQP为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。JMS的API协议能够确保所有的实现都能通过通用的API来使用,但是并不能保证某个JMS实现所发送的消息能够被另外不同的JMS实现所使用。而AMQP的线路层协议规范了消息的格式,消息在生产者和消费者间传送的时候会遵循这个格式。这样AMQP在互相协作方面就要优于JMS——它不仅能跨不同的AMQP实现,还能跨语言和平台。

2、JMS 支持TextMessage、MapMessage 等复杂的消息类型;而AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送),个人认为这也是它能够跨平台和跨语言使用的原因之一。

3、由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。 

二、Spring 集成 RabbitMQ

    RabbitMQ是一个流行的开源消息代理,它实现了AMQP。Spring AMQP为RabbitMQ提供了支持,包括RabbitMQ连接工厂、模板以及Spring配置命名空间。

    首先,需要安装 RabbitMQ,我们可以在 http://www.rabbitmq.com/download.html 上找到安装指南,具体怎么安装,不是这篇博文的重点,请笔友们自行解决。

    接下来,让我们一起来看看,Spring 和 RabbitMQ 的集成:

1、pom 依赖

    <dependency>
      <groupId>org.springframework.amqp</groupId>
      <artifactId>spring-rabbit</artifactId>
      <version>2.0.3.RELEASE</version>
    </dependency>

2、连接工厂 和 admin

    <rabbit:connection-factory id="connectionFactory"
                               host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.userName}"
                               password="${rabbitmq.password}"/>
    
    <rabbit:admin connection-factory="connectionFactory"/>

admin 元素会自动创建一个RabbitMQ管理组件,它会自动创建队列、Exchange以及binding

3、声明队列、Exchange以及binding

声明队列:

    <rabbit:queue name="queue1"/>
    <rabbit:queue name="queue2"/>
    <rabbit:queue name="queue3"/>
    <rabbit:queue name="queue4"/>
    <rabbit:queue name="queue5"/>
    <rabbit:queue name="queue6"/>

声明 Exchange 以及 binding:

direct-exchange:

    <rabbit:direct-exchange name="directExchange">
        <rabbit:bindings>
            <rabbit:binding key="queue1" queue="queue1"/>
            <rabbit:binding key="queue2" queue="queue2"/>
            <rabbit:binding key="queue3" queue="queue3"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>

        如果消息的routing key 与 routing key 直接匹配的话,消息将会路由到该队列上。

topic-exchange

    <rabbit:topic-exchange name="topicExchange">
        <rabbit:bindings>
            <rabbit:binding pattern="routing.*" queue="queue2"/>
            <rabbit:binding pattern="routing.*" queue="queue3"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

消息的 routing key 与 binding的routing key 符合通配符匹配的话,消息将会路由到该队列上。

这个通配符匹配特别坑,贼坑!我本来写了个 "routing*" ,自以为能匹配 "routingrrr" 这样的字符,不行!然后我又写了个"routing?"、"rounting.",预想着能不能匹配单个任意字符,不行!

终于我得出了一个结论,只能使用 "*"(匹配 0 个或任意多个)通配符,并且,并且!"*" 前面一定要有 个 "."  ! 太可怕了,不知道我总结的对不对哈!

headers-exchange

    <rabbit:headers-exchange name="headersExchange">
        <rabbit:bindings>
            <rabbit:binding queue="queue4" key="betty" value="rubble"   />
            <rabbit:binding queue="queue5" key="barney" value="rubble"   />
        </rabbit:bindings>
    </rabbit:headers-exchange>

消息参数表中的头信息和值都与bingding参数表中相匹配,消息将会路由到该队列上。

这个用法比较少用,也比较难用,原因是因为它仅支持 发送 byte[] 的消息类型。

fanout-exchange

    <rabbit:fanout-exchange name="fanoutExchange">
        <rabbit:bindings>
            <rabbit:binding queue="queue5"/>
            <rabbit:binding queue="queue6"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

这个是最简单粗暴的匹配规则,不管消息的routing key和参数表的头信息/值是什么,消息将会路由到所有队列上。

 4、发送和接收消息

    还是Spring的那一套,Spring 为我们提供了一个模板 bean(rabbitTemplate) 来发送和接收消息。其中,像前文提到的 jmsTemplate 那样,rabbitTemplate 也为我们 提供了 convertAndSend() 方法来自动转换和发送消息,提供了receiveAndConvret() 方法来接收和自动转换成对象(消息和对象之间默认的消息转换器是SimpleMessageConverter,它适用于String、Serializable实例以及字节数组)。另外,rabbitTemplate 也照常提供了 send() 和 receive() 方法来发送和接收消息,不过貌似仅支持发送字节数组...

配置 rabbitTemplate:

    <rabbit:template id="rabbitTemplate"
                     connection-factory="connectionFactory"
                     exchange="directExchange"
                     routing-key="queue1"/>

    下面仅演示 通配符路由方式 和 header 路由方式 发送和接收消息。其他具体详细的内容可参考我下面附上的源码:

通配符路由方式:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TopicExchange {

    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Test
    public void convertAndSend(){
        List<String> list = new ArrayList<>();
        list.add("java");
        list.add("python");
        list.add("c++");
        rabbitTemplate.convertAndSend("topicExchange","routing.123", list);
    }


    @Test
    public void receiveAndConvert(){
        List<String> queue2List =(List) rabbitTemplate.receiveAndConvert("queue2");
        printList(queue2List);

        System.out.println("----------------华丽的分隔符-----------------");

        List<String> queue3List =(List) rabbitTemplate.receiveAndConvert("queue3");
        printList(queue3List);

    }


    private <E> void printList(List<E> list){
        if (list != null && list.size() > 0){
            for (Object o : list){
                System.out.println("-----------------"+ o +"---------------");
            }
        }
    }
}

header 路由方式:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class HeadersExchangeTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void convertAndSend(){
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setHeader("betty", "rubble");
        messageProperties.setHeader("fred", "flintstone");
        messageProperties.setHeader("barney", "rubble");

        String str = new String("Hello RabbitMQ");
        Message message = new Message(str.getBytes(), messageProperties);
        rabbitTemplate.convertAndSend("headersExchange","",message);
    }

    @Test
    public void receiveAndConvert(){

        Message queue4 = rabbitTemplate.receive("queue4");
        System.out.println("第一个输出:" + new String(queue4.getBody()));
        Message queue5 = rabbitTemplate.receive("queue5");
        System.out.println("第三个输出:" + new String(queue5.getBody()));

    }

}

5、定义消息驱动的AMQP POJO

    用 receive()和 receiveAndConvert()方法都会立即返回,如果队列中没有等待的消息时,将会得到 null。Spring AMQP提供了消息驱动POJO的支持,也就是相当于一个监听器,监听某些队列,当消息到达指定队列的时候,可以立即调用方法处理该消息。

listener-container 配置:

    <rabbit:listener-container connection-factory="connectionFactory" concurrency="2" prefetch="3" type="direct">
        <rabbit:listener ref="handlerListener" method="handler" queue-names="queue5,queue6"/>
    </rabbit:listener-container>

其中,ref 指定Spring bean 的 id,method 指定 该bean中处理队列中消息的方法,queue-names 指定要监听哪些队列,队列之间用 "," 分隔。

三、结语

    祝大家五一节快乐!

    演示源码下载链接:https://github.com/JMCuixy/SpringMessageRabbitMQ

参考资料:《Spring 实战第四版》

Spring-amqp文档

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JavaNew

Spring Boot实战:数据库操作

39615
来自专栏芋道源码1024

源码级别解读 mybatis 插件

简介: ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以...

3408
来自专栏java学习

关于Spring 和 Spring MVC的43个问题【问题汇总】

通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。

541
来自专栏阿杜的世界

Spring Boot应用的测试——Mockito

Spring Boot可以和大部分流行的测试框架协同工作:通过Spring JUnit创建单元测试;生成测试数据初始化数据库用于测试;Spring Boot可以...

781
来自专栏Spark生态圈

[spark] Task成功执行的结果处理

在文章Task执行流程 中介绍了task是怎么被分配到executor上执行的,本文讲解task成功执行时将结果返回给driver的处理流程。

1204
来自专栏杂烩

一个综合的分布式项目之功能代码 原

    看过第一篇博客的应该都知道流程,虽然并不是一模一样,但大体是一样的,现在来确认具体方案。

1342
来自专栏哎_小羊

SpringMVC+jade实现高性能模板引擎(简单配置)

最近在研究一个前后端通用的高性能模板引擎,大概搜索了下资料,有很多类似的模板引擎,比如Jade,Mustache.js,Dust.js,Nunjucks,EJS...

2398
来自专栏ImportSource

像Spring Boot那样创建一个你自己的Starter

如果你所在的公司要开发一个共享的lib,或者如果你想要为开源世界做点贡献,你也许想要开发你自己的自定义的自动配置类以及你自己的starter pom。这些自动配...

3229
来自专栏小筱月

springboot 整合 MongoDB 实现登录注册,html 页面获取后台参数的方法

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而...

780
来自专栏强仔仔

如何用SpringBoot框架来接收multipart/form-data文件

今天遇到一个坑,这里给大家介绍一下。 现在很多文件上传类型都是multipart/form-data类型的,HTTP请求如下所示: ? 可是问题就在于如果用传统...

6307

扫码关注云+社区