rabbitMQ消息队列原理

  1. 云栖社区>
  2. 博客>
  3. 正文

rabbitMQ消息队列原理

技术小阿哥 2017-10-30 17:06:00 浏览1127
展开阅读全文

MQ:Message Queue,消息队列,是一种应用程序对应用程序的通信方法;应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。

 

1      rabbitMQ入门及原理

rabbitMQ官网:http://www.rabbitmq.com/

Erlang官网:http://www.erlang.org/

1.1  rabbitMQ概述

RabbitMQ是一个由Erlang开发的AMQPAdvancedMessage Queue )的开源实现,支持多种客户端,如:PythonRuby.NETJavaJMSCPHPActionScriptXMPPSTOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

借用网络中一个rabbitMQ的系统架构图:

d77e4cfd3d9dc6bf328c7db4b60fbc95.jpg

49e36a7d3c1523e781dac3fa753337d0.png

 

1.1.1     AMQP简介

AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有 RabbitMQ等。--百度百科

Message BrokerAMQP简介

Message Broker是一种消息验证、传输、路由的架构模式,其设计目标主要应用于下面这些场景:

  • 消息路由到一个或多个目的地

  • 消息转化为其他的表现方式

  • 执行消息的聚集、消息的分解,并将结果发送到他们的目的地,然后重新组合相应返回给消息用户

  • 调用Web服务来检索数据

  • 响应事件或错误

  • 使用发布-订阅模式来提供内容或基于主题的消息路由

AMQP是Advanced Message QueuingProtocol的简称,它是一个面向消息中间件的开放式标准应用层协议。AMQP定义了这些特性:

  • 消息方向

  • 消息队列

  • 消息路由(包括:点到点和发布-订阅模式)

  • 可靠性

  • 安全性

RabbitMQ就是以AMQP协议实现的一种中间件产品,它可以支持多种操作系统,多种编程语言,几乎可以覆盖所有主流的企业级技术平台。

1.1.1.1             AMQP理论

AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

简单介绍AMQP的协议栈,AMQP协议本身包含三层,如下:

79c3f2ad461558298780c4cbe3bfdda6.png

Model Layer,位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以通过这些命令实现自己的业务逻辑,例如,客户端可以通过queue declare声明一个队列,利用consume命令获取队列的消息。

Session Layer,主要负责将客户端命令发送给服务器,在将服务器端的应答返回给客户端,主要为客户端与服务器之间通信提供可靠性、同步机制和错误处理。

Transport Layer,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示。

这种分层架构类似于OSI网络协议,可替换各层实现而不影响与其它层的交互。AMQP定义了合适的服务器端域模型,用于规范服务器的行为(AMQP服务器端可称为broker)。在这里Model层决定这些基本域模型所产生的行为,这种行为在AMQP中用command表示。Session层定义客户端与broker之间的通信(通信双方都是一个peer,可互称做partner),为command的可靠传输提供保障。Transport层专注于数据传送,并与Session保持交互,接受上层的数据,组装成二进制流,传送到receiver后再解析数据,交付给Session层。Session层需要Transport层完成网络异常情况的汇报,顺序传送command等工作。

接下来了解下AMQP当中的一些概念。

BrokerServer):接受客户端连接,实现AMQP消息队列和路由功能的进程。

Virtual Host:其实是一个虚拟概念,类似于权限控制组,一个Virtual Host里面可以有若干个ExchangeQueue,但是权限控制的最小粒度是Virtual Host

Exchange:接受生产者发送的消息,并根据Binding规则将消息路由给服务器中的队列。ExchangeType决定了Exchange路由消息的行为,例如,在RabbitMQ中,ExchangeTypedirectFanoutTopic三种,不同类型的Exchange路由的行为是不一样的。

Message Queue:消息队列,用于存储还未被消费者消费的消息。

Message:由HeaderBody组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、由哪个Message Queue接受、优先级是多少等。而Body是真正需要传输的APP数据。

BindingBinding联系了ExchangeMessageQueueExchange在与多个MessageQueue发生Binding后会生成一张路由表,路由表中存储着Message Queue所需消息的限制条件即Binding Key。当Exchange收到Message时会解析其Header得到Routing KeyExchange根据Routing KeyExchangeTypeMessage路由到MessageQueueBinding KeyConsumerBinding ExchangeMessageQueue时指定,而Routing KeyProducer发送Message时指定,两者的匹配方式由ExchangeType决定。

Connection:连接,对于RabbitMQ而言,其实就是一个位于客户端和Broker之间的TCP连接。

Channel:信道,仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的。需要为每一个Connection创建ChannelAMQP协议规定只有通过Channel才能执行AMQP的命令。一个Connection可以包含多个Channel。之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。RabbitMQ建议客户端线程之间不要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection

CommandAMQP的命令,客户端通过Command完成与AMQP服务器的交互来实现自身的逻辑。例如在RabbitMQ中,客户端可以通过publish命令发送消息,txSelect开启一个事务,txCommit提交一个事务。

消息中间件的主要功能是消息的路由(Routing)和缓存(Buffering)。在AMQP中提供类似功能的两种域模型:Exchange  Messagequeue

c9b25308f76d038e2de2bee39b2d0762.jpg

Exchange接收消息生产者(MessageProducer)发送的消息根据不同的路由算法将消息发送往Message queueMessagequeue会在消息不能被正常消费时缓存这些消息,具体的缓存策略由实现者决定,当message queue与消息消费者(Messageconsumer)之间的连接通畅时,Message queue有将消息转发到consumer的责任。

一个Message的处理流程类似于下图:

a789e83e1de4d4dfa2233f9d87158ceb.jpg

Message是当前模型中所操纵的基本单位,它由Producer产生,经过BrokerConsumer所消费。它的基本结构有两部分: HeaderBodyHeader是由Producer添加上的各种属性的集合,这些属性有控制Message是否可被缓存,接收的queue是哪个,优先级是多少等。Body是真正需要传送的数据,它是对Broker不可见的二进制数据流,在传输过程中不应该受到影响。

一个broker中会存在多个Message queueExchange怎样知道它要把消息发送到哪个Message queue中去呢这就是上图中所展示Binding的作用。Messagequeue的创建是由client application控制的,在创建Message queue后需要确定它来接收并保存哪个Exchange路由的结果。Binding是用来关联ExchangeMessage queue的域模型。Clientapplication控制Exchange与某个特定Messagequeue关联,并将这个queue接受哪种消息的条件绑定到Exchange,这个条件也叫Binding key或是 Criteria

在与多个Messagequeue关联后,Exchange中就会存在一个路由表,这个表中存储着每个Message queue所需要消息的限制条件。Exchange就会检查它接受到的每个MessageHeaderBody信息,来决定将Message路由到哪个queue中去。MessageHeader中应该有个属性叫Routing Key,它由Message发送者产生,提供给Exchange路由这条Message的标准。Exchange根据不同路由算法有不同有ExchangeType。比如有Direct类似,需要Bindingkey等于Routing key;也有BindingkeyRouting key符合一个模式关系;也有根据Message包含的某些属性来判断。一些基础的路由算法由AMQP所提供,clientapplication也可以自定义各种自己的扩展路由算法。

AMQP中,Client application想要与Broker沟通,就需要建立起与Brokerconnection,这种connection其实是与Virtual Host相关联的,也就是说,connection是建立在clientVirtual Host之间。可以在一个connection上并发运行多个channel,每个channel执行与Broker的通信,我们前面提供的session就是依附于channel上的。

这里的Session可以有多种定义,既可以表示AMQP内部提供的command分发机制,也可以说是在宏观上区别与域模型的接口。正常理解就是我们平时所说的交互context,主要作用就是在网络上可靠地传递每一个command。在AMQP的设计中,应当是借鉴了TCP的各种设计,用于保证这种可靠性。

Session层,为上层所需要交互的每个command分配一个惟一标识符(可以是一个UUID),是为了在传输过程中可以对command做校验和重传。Command发送端也需要记录每个发送出去的commandReplayBuffer,以期得到接收方的回馈,保证这个command被接收方明确地接收或是已执行这个command。对于超时没有收到反馈的command,发送方再次重传。如果接收方已明确地回馈信息想要告知command发送方但这条信息在中途丢失或是其它问题发送方没有收到,那么发送方不断重传会对接收方产生影响,为了降低这种影响,command接收方设置一个过滤器IdempotencyBarrier,来拦截那些已接收过的command关于这种重传及确认机制,可以参考下TCP的相关设计。

 

1.1.2     Erlang简介

 

Erlang([':l])是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。Erlang问世于1987年,经过十年的发展,于1998年发布开源版本。Erlang是运行于虚拟机的解释性语言,但是现在也包含有乌普萨拉大学高性能Erlang计划(HiPE)开发的本地代码编译器,自R11B-4版本开始,Erlang也开始支持脚本式解释器。在编程范型上,Erlang属于多重范型编程语言,涵盖函数式、并发式及分布式。顺序执行的Erlang是一个及早求值单次赋值和动态类型的函数式编程语言。

Erlang是一个结构化,动态类型编程语言,内建并行计算支持。最初是由爱立信专门为通信应用设计的,比如控制交换机或者变换协议等,因此非常适合于构建分布式,实时软并行计算系统。使用Erlang编写出的应用运行时通常由成千上万个轻量级进程组成,并通过消息传递相互通讯。进程间上下文切换对于Erlang来说仅仅只是一两个环节,比起C程序的线程切换要高效得多得多了。

使用Erlang来编写分布式应用要简单的多,因为它的分布式机制是透明的:对于程序来说并不知道自己是在分布式运行。Erlang运行时环境是一个虚拟机,有点像Java虚拟机,这样代码一经编译,同样可以随处运行。它的运行时系统甚至允许代码在不被中断的情况下更新。另外如果需要更高效的话,字节代码也可以编译成本地代码运行。

 

来自:百度百科

 

其他MQ

 f97ab5699444eadf310e07173f50f83f.png

1.1.3     下载rabbitMQErlang

 ec3645273847c31033ff96f5c4d49d4f.png

首页,下拉至:download下载,Tutorials教程

 5e996097280f3fbe68b2156b1eff7f64.png

下载windows版本:

rabbitmq-server-3.6.12.exe

 

教程RabbitMQ Tutorials

http://www.erlang.org/访问比较慢,建议大家也可以网上找资源下载。

0d2d1abea7347d9af2481204d39e4426.png

 

1.1.4     rabbitMQ基本概念

spring-boot-rabbitMQ项目源码https://git.oschina.net/wyait/springboot1.5.4.git

 

config配置类:

@Autowired

private ConnectionFactoryconnectionFactory;

@Autowired

private Queue3Listenerqueue3Listener;

 

@Bean

@Primary

public RabbitTemplaterabbitTemplate() {

    RabbitTemplate rabbitTemplate = newRabbitTemplate(connectionFactory);

    rabbitTemplate.setMessageConverter(newJackson2JsonMessageConverter());

    return rabbitTemplate;

}

 

@Bean

@Primary

publicSimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory() {

    SimpleRabbitListenerContainerFactorysimpleRabbitListenerContainerFactory = newSimpleRabbitListenerContainerFactory();

   simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory);

    simpleRabbitListenerContainerFactory.setMessageConverter(newJackson2JsonMessageConverter());

    returnsimpleRabbitListenerContainerFactory;

}

 

@Bean

publicSimpleMessageListenerContainer simpleMessageListenerContainer() {

    SimpleMessageListenerContainer container =new SimpleMessageListenerContainer(connectionFactory);

    container.setQueues(queue3());

   container.setMessageListener(queue3Listener);

    return container;

}

 

@Bean

public DirectExchangedirectExchange() {

    return new DirectExchange(EX_CHANGE_NAME1);

}

 

@Bean

public Queue queue1() {

    return new Queue(QUEUE1, true);

}

 

@Bean

public Queue queue2() {

    return new Queue(QUEUE2, true);

}

 

@Bean

public Queue queue3() {

    return new Queue(QUEUE3, true);

}

 

@Bean

public Binding binding1() {

    returnBindingBuilder.bind(queue1()).to(directExchange()).with(ROUTING_KEY1);

}

 

@Bean

public Binding binding2() {

    returnBindingBuilder.bind(queue2()).to(directExchange()).with(ROUTING_KEY2);

 

}

 

@Bean

public Binding binding3() {

    return BindingBuilder.bind(queue3()).to(directExchange()).with(ROUTING_KEY3);

}

 

基本概念:

  • ConnectionFactoryConnectionChannel

    connectionsocket连接的封装,connectionFqactoryconnection的生产工程,channel是通信的信道,实际进行数据交流的管道,因为建立connection的开销明显要比建立channel要大很多,所以数据传输真实发生在channel

  • Exchange,Queue

    exchange是可以理解成一条特殊的传输通道,他会把消息投递到绑定的消息池内。

    queue就是消息池了,使用前需要绑定exchange,以及自己的标志。

  • exchange_key,routing_key

    exchange_key决定了publisher的消息投递到哪条通道,routing_key决定了将消息放到哪个池子里

  • 绑定

    queue要接受消息必须与exchange进行绑定,并在绑定的时候给自己与exchange的绑定设置一个标记routing_key,以后用来匹配消息接收

    exchangequeue是一对多的关系,根据exchange不同类型,分别投递到不同的消息池

 

下面来看看exchange的类型

 

  1. 1.       fanout

    直接将消息发送到与该exchange绑定的所有queue

  1. 2.       direct

    routing_key进行严格匹配,当消息来到的时候,只有exchange与某queue绑定的routing_key完全匹配才将消息投递到该queue

  1. 3.       topic

    用特殊符号进行匹配,满足条件的queue都能收到消息,这里的routing_key"."分隔,*匹配一个单词,#匹配多个单词,如果满足多个条件也不会投递多次

  1. 4.       headers

    不依赖routing_key匹配,根据消息体内的headers属性匹配,绑定的时候可以制定键值对

    接下来来看看配置文件

          1.@Bean统一注入到容器中,我们声明了connectionfactory,他会自动根据application里面的属性进行组装,这个连接对于后面的容器都是要用到的,这里要注意converter的设置,因为我们要将pojo类型进行传输,一般程序外的传输都是建立在字节流的基础上,converter就会自动转换

          2.接下来我们声明queuetrue属性设置为持久型的池子,当连接断开时,消息会呗保留,然后声明exchange,这里我们使用的是directexchange,接下来将两者绑定起来

  1. 5.       声明SimpleMessageListenerContainerSimpleRabbitListenerContainerFactory注意这里声明两个是因为这是消息监听的两种方式

    首先讲讲SimpleMessageListenerContainer,这个需要设置确认方式,有较多属性克设置,有兴趣可自行设置,这里我只是简单的设置了一下,然后要设置listener

    listener需要实现ChannelAwareMessageListener里面有

    public void onMessage(Message message,Channel channel) 的重载方法需要实现,消息体在Messagebody内,相对来说信息比较完备

    接下来看看SimpleRabbitListenerContainerFactory,这个有几个注意点,需要再次设置converter因为,一个是发消息的时候解析成二进制,这个则是将二进制解析成具体的类,回调相对简单一点

 

    @Component

    @RabbitListener(queues =RabbitMQConfig.QUEUE1, containerFactory ="simpleRabbitListenerContainerFactory")

    public class Queue1Listener  {

     private static Logger logger =LoggerFactory.getLogger(Queue1Listener.class);

     @RabbitHandler

     public void receive(@Payload String s) {

 

     logger.info("listener1 info: " +s);

 

     }

    }

 

    记得需要containerFactory具体写出来

    在接收消息的方法上写@RabbitHandler,消息体打上@payload久可以接受消息了。

    其实还有个方法就是指定一个MessageAdapter,然后在container里面就可以指定接收的方法名,不是很推荐,明文反射总感觉容易出问题

    当然publisher也是有消息的回调的

    RabbitTemplate下有ConfirmCallback实现confirm方法就好了

 

1.2  rabbitMQ入门

1.2.1     安装rabbitMQwindows

安装步骤:

1.    安装Erland,通过官方下载页面http://www.erlang.org/downloads获取exe安装包,直接打开并完成安装。

2.    安装RabbitMQ,通过官方下载页面https://www.rabbitmq.com/download.html获取exe安装包。

3.    下载完成后,可以直接运行安装程序,或配置环境变量后运行rabbitMQ-server安装程序。

4.    RabbitMQ Server安装完成之后,会自动的注册为服务,并以默认配置启动起来。

 

安装过程请百度

 

安装成功:访问:http://127.0.0.1:15672   用户密码:guest/guest

19f5a378734d5d5164c22f58228dee5f.png

我们可以看到一些基本概念,比如:ConnectionsChannelsExchangesQueue等。第一次使用,可以都点开看看都有些什么内容,熟悉一下RabbitMQ Server的服务端。

 

点击Admin标签,在这里可以进行用户的管理。

4cb5295ff0954532b14efa40468bc720.png

点击admin,添加用户:wyait/wyait并授权。

9ecaf7b7d635a42a1442d6a7f7828590.png

点击all users表单中的用户名“wyait”进行授权:

 1ae05b2095255154c1e6dd23f9e33b4f.png 

1.2.1.1             Virtual Hosts设置界面:

160f8f3478f8a7e0a8237bbad7acb3d0.png

程序中和rabbitMQ交互的端口是:5672AMQP协议端口

 

1.2.2     创建spring-boot-MQ工程

项目源码,

码云地址:https://git.oschina.net/wyait/springboot1.5.4.git

github地址:https://github.com/wyait/spring-boot-1.5.4.git

spring boot整合rabbitMQ项目博客链接:spring boot 1.5.4 整合rabbitMQ(十七)

 

1.2.3     消息队列

官网:rabbitMQTutorials

04d7e56a4dd5d8b50c30711d96b72f2b.png

前提,rabbitMQ服务已经启动;测试过程:

1spring Boot项目先启动,监听队列;

2,启动测试类发送消息到队列中;、

3,消费者消费消息。

 

1.2.3.1             hello world

The simplest thingthat does something 简单的消息队列:

ae97423ac713c3dac0e57e861f95bd2b.png

P:消息的生产者;

C:消息的消费者;

红色框:消息队列;

 

demo参考1.2.2章节。

 

1.2.3.2             Work Queues

Distributing tasksamong workers (the competing consumers pattern)

9ef016c90da8e91d24f217825d6dff74.png

一个生产者对应一个消息队列MQMQ可以对应多个消费者,但是同一个消息只能被一个客户端生产者所获取;

 

同一个消息只能被一个客户端所获取。但是对于不同的消费者,接受消息,处理的效率不同,所以会有不合理的地方。

 

RabbitMqConfig中定义一个队列workQueues

@Bean

   public Queue workQueue() {

      return new Queue("workQueues");

   }

 

消息生产者WorkSender:

@Component

public class WorkSender {

   @Autowired

   private AmqpTemplate rabbitMQTemplate;

 

   /**

    *

    * @描述:work模式

    * @创建人:wyait

    * @创建时间:2017914下午5:51:20

    */

   public void workSend(String msg) {

      String context = msg + new Date();

      System.out.println("workSender : " + context);

      this.rabbitMQTemplate.convertAndSend("workQueues",context);

   }

}

 

消息消费者1 WorkReceiver:

@Component

@RabbitListener(queues ="workQueues")

public class WorkReceiver {

 

   @RabbitHandler

   // handler注解来指定对消息的处理方法

   public void process(String hello) {

      System.out.println("workReceiver:" + hello);

   }

}

 

消息消费者2 WorkReceiverTwo:

@Component

@RabbitListener(queues ="workQueues")

public class WorkReceiverTwo {

 

   @RabbitHandler

   // handler注解来指定对消息的处理方法

   public void process(String hello) {

      System.out.println("workReceiverTwo:" + hello);

   }

}

 

测试消费消息结果:
503a2221941bc7043dad04eb1bcc8e89.png

平均分配消息原则(你一条,我一条)。

可通过更改channel设置,改变分配策略。

 

1.2.3.3             Publish/Subscribe订阅模式

Sending messagesto many consumers at once.

一个生产者将同一条消息message发送到交换机exchange,通过exchange发送到多个队列中,而对应的消费者都能获取到该消息。

30edb6a834b95aabead6a9659c5caceb.png

注意:

问题1:消息是发送到交换机而不是队列?答:消息可以发送到队列,也可以发送到交换机。

问题2:消费者的消息来源只能是队列;

问题3:如果将消息发送到没有绑定队列的交换机上,消息会去哪?答:消息丢失。

总结:消息只能存放于队列,不能存放在交换机;交换机只能用于消息的传递,消息通道。

 

Fanout Exchange:

46015481703c70a380c08c87f758e852.png

不处理路由键(routingKey)。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。

 

Fanout 就是我们熟悉的广播模式或者订阅模式,给Fanout转发器发送消息,绑定了这个转发器的所有队列都收到这个消息。

 

这里使用三个队列来测试(也就是在Application类中创建和绑定的fanout.Afanout.Bfanout.C)这三个队列都和Application中创建的fanoutExchange转发器绑定。

 

新增subscribe订阅模式配置:

// ******************subscribe订阅模式**********Start****************

   /**

    *

    * @描述:subscribe订阅模式的队列A;

    * @创建人:wyait

    * @创建时间:2017915下午3:24:31

    * @return

    */

   @Bean

   public Queue subscribeQueueA() {

      return new Queue("fanout.A");

   }

 

   /**

    *

    * @描述:subscribe订阅模式的队列B;

    * @创建人:wyait

    * @创建时间:2017915下午3:24:31

    * @return

    */

   @Bean

   public Queue subscribeQueueB() {

      return new Queue("fanout.B");

   }

 

   /**

    *

    * @描述:subscribe订阅模式的队列C;

    * @创建人:wyait

    * @创建时间:2017915下午3:24:31

    * @return

    */

   @Bean

   public Queue subscribeQueueC() {

      return new Queue("fanout.C");

   }

 

   /**

    *

    * @描述:fanoutExchange交换机

    * @创建人:wyait

    * @创建时间:2017915下午3:34:41

    * @return

    */

   @Bean

   public FanoutExchange fanoutExchange() {

      return new FanoutExchange("fanoutExchange");

   }

 

   /**

    *

    * @描述:subscribeQueue绑定fanoutExchange交换机

    * @创建人:wyait

    * @创建时间:2017915下午3:41:10

    * @param subscribeQueue

    * @param fanoutExchange

    * @return

    */

   @Bean

   Binding bindingExchangeA(Queue subscribeQueueA,

        FanoutExchange fanoutExchange) {

      // 绑定队列AfanoutExchange交换机,也可以使用:bind(subscribeQueueA())进行绑定;

      return BindingBuilder.bind(subscribeQueueA).to(fanoutExchange);

   }

 

   @Bean

   Binding bindingExchangeB(Queue subscribeQueueB,

        FanoutExchange fanoutExchange) {

      return BindingBuilder.bind(subscribeQueueB).to(fanoutExchange);

   }

 

   @Bean

   Binding bindingExchangeC(Queue subscribeQueueC,

        FanoutExchange fanoutExchange) {

      return BindingBuilder.bind(subscribeQueueC).to(fanoutExchange);

   }

 

   // ******************subscribe订阅模式**********End****************

 

消息生产者SubscribeSender指定交换机:

@Component

public class SubscribeSender {

   @Autowired

   private AmqpTemplate rabbitTemplate;

 

   public void send(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---SubscribeSender : " +sendMsg);

      // convertAndSend(String exchange, String routingKey, Objectmessage)

      this.rabbitTemplate.convertAndSend("fanoutExchange","aaa", sendMsg);

   }

 

}

 

消息消费者SubscribeReveicerABC监听队列fanout.A/B/C:

@Component

@RabbitListener(queues ="fanout.A")

public class SubscribeReceiver{

   @RabbitHandler

   public void precess(String msg) {

      System.out.println("SubscribeReceiverA  : " + msg);

   }

}

 

测试test类:

@Autowired

   private SubscribeSender subSend;

 

   @Test

   public void subscribeTest() {

      System.out.println("==========subscribe发送消息!");

      for (int i = 0; i < 50; i++) {

        String msg = "==========msg_" + i;

        subSend.send(msg);

        try {

           Thread.sleep(100);

        } catch (InterruptedException e) {

           e.printStackTrace();

        }

      }

   }

 

三个消费者都接收到了每一条信息。

851514323debff33e0affe3d7a010e66.png

注意:subscribe订阅模式和work模式的区别。

1、  work模式将消息发送到队列

2、  订阅模式将消息发送到交换机

3、  work模式是1个队列N个消费者,订阅模式是N个队列N个消费者(N>0)

 

1.2.3.4             Routing路由模式

e1eec04aaba9c5ee0ec7c4c151e98eb4.png

路由模式:基于订阅模式,

可以在队列绑定到交换机时指定一个规则,根据不同的消息规则,选择是否接受该消息。

0f3bcce82aacc2088184af47292c1a86.png

处理路由键(routingKey)。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键routingKey完全匹配。

 

基于Subscribe订阅模式,配置类中添加队列、DirectExchange交换机并进行绑定:

/**

    *

    * @描述:routing路由模式的队列A;

    * @创建人:wyait

    * @创建时间:2017915下午3:24:31

    * @return

    */

   @Bean

   public Queue routingQueueA() {

      return new Queue("routing.A");

   }

 

   /**

    *

    * @描述:routing路由模式的队列B;

    * @创建人:wyait

    * @创建时间:2017915下午3:24:31

    * @return

    */

   @Bean

   public Queue routingQueueB() {

      return new Queue("routing.B");

   }

 

   /**

    *

    * @描述:DirectExchange交换机

    * @创建人:wyait

    * @创建时间:2017915下午3:34:41

    * @return

    */

   @Bean

   public DirectExchange directExchange() {

      return new DirectExchange("directExchange");

   }

 

   /**

    *

    * @描述:routingQueue绑定directExchange交换机

    * @创建人:wyait

    * @创建时间:2017915下午3:41:10

    * @param routingQueue

    * @param directExchange

    * @return

    */

   @Bean

   Binding bindingDirectExchangeA(Queue routingQueueA,

        DirectExchange directExchange) {

      // 绑定routing队列AdirectExchange交换机,并指定routing路由规则;

      return BindingBuilder.bind(routingQueueA()).to(directExchange())

           .with("info");

   }

 

   @Bean

   Binding bindingDirectExchangeB(Queue routingQueueB,

        DirectExchange directExchange) {

      // 绑定routing队列AdirectExchange交换机,并指定routing路由规则;

      returnBindingBuilder.bind(routingQueueB()).to(directExchange())

           .with("error");

   }

 

消息生产者:

@Component

public class RoutingSender {

   @Autowired

   private AmqpTemplate rabbitTemplate;

 

   public void send(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---RoutingSender : " + sendMsg);

      this.rabbitTemplate.convertAndSend("directExchange","info", sendMsg);

   }

 

   public void sendTwo(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---RoutingSender TWO: " +sendMsg);

      this.rabbitTemplate

           .convertAndSend("directExchange", "infoTwo",sendMsg);

   }

 

   public void sendError(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---RoutingSender Error: " +sendMsg);

      this.rabbitTemplate.convertAndSend("directExchange","error", sendMsg);

   }

 

   public void sendErrorTwo(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---RoutingSender ErrorTwo: " +sendMsg);

      this.rabbitTemplate.convertAndSend("directExchange","errorTwo",

           sendMsg);

   }

 

}

 

消息消费者A

@Component

@RabbitListener(queues ="routing.A")

public class RoutingReceiver {

   @RabbitHandler

   public void precess(String msg) {

      System.out.println("RoutingReceiverA === : " + msg);

   }

 

}

 

测试类:

@Autowired

   private RoutingSender routSend;

 

   @Test

   public void routingTest() {

      System.out.println("==========routing发送消息!");

      routSend.send("==========msg_info ");

      routSend.sendTwo("==========msg_infoTwo ");

      routSend.sendError("==========msg_error ");

      routSend.sendErrorTwo("==========msg_ErrorTwo ");

 

      System.out.println("==========routing发送消息   结束!");

   }

 

运行:

e1ea13567a2e8d26b56be095dc8fe33e.png

MqApplication控制台:

33e294ec1cc02f99e7e6b190bd609584.png

由此可以看出,routingKey符合规则的消息,会被消费方接收并消费。

 

1.2.3.5             Topics通配符模式

Receiving messagesbased on a pattern (topics)

9186caf294234d3acd57b894fc72a1ea.png

基于路由模式,使用通配符匹配队列,发送消息

4ea0164e18715288b5c2314834987a09.png

将路由键和某模式进行匹配。

 

任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue

 

1. 这种模式需要RouteKey,要提前绑定ExchangeQueue

 

2. 如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息。

 

3. 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)

 

4. #”表示0个或若干个关键字,“*”表示一个关键字。如“log.*”能与“log.warn”匹配,无法与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。

通配符#*的区别;

#:代表匹配一个或多个词;

*:代表只匹配一个词.

 

配置类中新增队列绑定TopicExchange交换机,并指定routingKey和匹配模式:

@Bean

   public Queue topicQueueA() {

      return new Queue("topic.queueA", true); // true表示持久化该队列

   }

 

   @Bean

   public Queue topicQueueB() {

      return new Queue("topic.queueB", true);

   }

 

   // 声明交互器

   @Bean

   TopicExchange topicExchange() {

      return new TopicExchange("topicExchange");

   }

 

   // 绑定

   @Bean

   public Binding bindingA() {

      return BindingBuilder.bind(topicQueueA()).to(topicExchange())

           .with("topic.message");

   }

 

   @Bean

   public Binding bindingB() {

      return BindingBuilder.bind(topicQueueB()).to(topicExchange())

           .with("topic.#");

   }

 

消息生产者:

@Component

public class TopicSender {

   @Autowired

   private AmqpTemplate rabbitTemplate;

 

   public void send(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---TopicSender : " + sendMsg);

      this.rabbitTemplate.convertAndSend("topicExchange","topic.message",

           sendMsg);

   }

 

   public void sendTwo(String msg) {

      String sendMsg = msg + new Date();

      System.out.println("---TopicSender messages: " +sendMsg);

      this.rabbitTemplate.convertAndSend("topicExchange","topic.messages",

           sendMsg);

   }

 

}

 

消息消费者:

@Component

@RabbitListener(queues ="topic.queueA")

public class TopicReceiver {

   @RabbitHandler

   public void precess(String msg) {

      System.out.println("TopicReceiverA  : " + msg);

   }

}

 

test测试类:

   @Autowired

   private TopicSender topicSend;

 

   @Test

   public void topicTest() {

      System.out.println("==========topic发送消息!");

      topicSend.send("==========msg_info ");

      topicSend.sendTwo("==========msg_infoTwo ");

 

      System.out.println("==========topic发送消息   结束!");

   }

 

重启MqApplication,运行test类:结果:

43a0b8c3dc037057b030a37d6838a34c.png

 92193865ac175fbe027e1cf8d04884fc.png

根据路由规则,接收不同生产者的消息。

1.2.3.6             交换机总结

RPC模式可以百度查资料去了解!

 

FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念 

        HeadersExchange :通过添加属性key-value匹配 

        DirectExchange:按照routingkey分发到指定队列 

        TopicExchange:多关键字匹配 

 

1.2.4     管理界面操作队列和交换机

 

进入Exchanges交换机界面,可以看到所有的AMQP默认的交换机和定义的Exchange:

f3e237c7577f5ebd94d7330a5bd15911.png

选择topicExchange:

5093d779e87925abfbf8fad052723d98.png

 

可以对队列进行添加和解绑操作!

 

1.2.5     队列的持久化

RabbitMQ的队列有2种,一种是内存队列,一种是持久化队列

1、  内存队列

  1. 优点:速度快,效率高

  2. 缺点:宕机,消息丢失

2、  持久化队列

  1. 优点:消息可以持久化保存,宕机或断电后消息不丢失

  2. 缺点:比内存存储速度慢,性能差

设置方法:

@Bean

   public Queue topicQueueA() {

      return new Queue("topic.queueA", true); // true表示持久化该队列

   }

 

管理界面查看是否持久化:

107a956b9ba6c55ce12636ecf6b0bfd7.png


本文转自 wyait 51CTO博客,原文链接:http://blog.51cto.com/wyait/1977544,如需转载请自行联系原作者

网友评论

登录后评论
0/500
评论
技术小阿哥
+ 关注