rabbitMQ学习记录

一.由多篇博客整理所得,记录学习

 

RabbitMQ的应用场景以及基本原理介绍

https://blog.csdn.net/whoamiyang/article/details/54954780 

windows10环境下的RabbitMQ安装步骤(图文)

https://blog.csdn.net/weixin_39735923/article/details/79288578

RabbitMQ入门(一)——RabbitMQ的安装以及使用(Windows环境下)

https://blog.csdn.net/qq_31634461/article/details/79377256

RabbitMQ基础知识

https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html

中间件系列三 RabbitMQ之交换机的四种类型和属性

rabbitmq channel参数详解

https://www.cnblogs.com/piaolingzxh/p/5448927.html

rabbitmq-channel方法介绍

https://www.cnblogs.com/xuwenjin/p/8970481.html

RabbitMQ 运转流程

http://www.cnblogs.com/saryli/p/9734740.html

消息队列 RabbitMQ 与 Spring 整合使用

https://www.cnblogs.com/libra0920/p/6230421.html

二.背景

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue)的开源实现。

三.场景

1异步处理
场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种

1.串行的方式;

2.并行的方式 
(1)串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西. 

(2)并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。 
 
假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并性已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,英爱是写入数据库后就返回. 
(3)消息队列 
引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理 

由此可以看出,引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。

2 应用解耦
场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口. 
 
这种做法有一个缺点:

当库存系统出现故障时,订单就会失败。
订单系统和库存系统高耦合. 
引入消息队列 


订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。

库存系统:订阅下单的消息,获取下单消息,进行库操作。 
就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失
流量削峰
流量削峰一般在秒杀活动中应用广泛 
场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。 
作用: 
1.可以控制活动人数,超过此一定阀值的订单直接丢弃
2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单) 
 
用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面. 
秒杀业务根据消息队列中的请求信息,再做后续处理.


四.windows安装

下载地址:http://www.rabbitmq.com/download.html

下载安装包.但要先有Erlang环境

rabbitmq-plugins.bat" enable rabbitmq_management

具体过程参见https://blog.csdn.net/weixin_39735923/article/details/79288578

五.基础知识

     讲解基础概念的前面,我们先来整体构造一个结构图,这样会方便们更好地去理解RabbitMQ的基本原理。

图一

   通过上面这张应用相结合的结构图既能够清晰的看清楚整体的send Message到Receive Message的一个大致的流程。当然上面有很多名词都相比还没有介绍到,不要着急接下来我们就开始对其进行详细的讲解。

 Queue

   Queue(队列)RabbitMQ的作用是存储消息,队列的特性是先进先出。上图可以清晰地看到Client A和Client B是生产者,生产者生产消息最终被送到RabbitMQ的内部对象Queue中去,而消费者则是从Queue队列中取出数据。可以简化成表示为:

    生产者Send Message “A”被传送到Queue中,消费者发现消息队列Queue中有订阅的消息,就会将这条消息A读取出来进行一些列的业务操作。这里只是一个消费正对应一个队列Queue,也可以多个消费者订阅同一个队列Queue,当然这里就会将Queue里面的消息平分给其他的消费者,但是会存在一个一个问题就是如果每个消息的处理时间不同,就会导致某些消费者一直在忙碌中,而有的消费者处理完了消息后一直处于空闲状态,因为前面已经提及到了Queue会平分这些消息给相应的消费者。这里我们就可以使用prefetchCount来限制每次发送给消费者消息的个数。详情见下图所示:

    这里的prefetchCount=1是指每次从Queue中发送一条消息来。等消费者处理完这条消息后Queue会再发送一条消息给消费者。

    Exchange

    我们在开篇的时候就留了一个坑,就是那个应用结构图里面,消费者Client A和消费者Client B是如何知道我发送的消息是给Queue1还是给Queue2,有没有过这个问题,那么我们就来解开这个面纱,看看到底是个什么构造。首先明确一点就是生产者产生的消息并不是直接发送给消息队列Queue的,而是要经过Exchange(交换器),由Exchange再将消息路由到一个或多个Queue,当然这里还会对不符合路由规则的消息进行丢弃掉,这里指的是后续要谈到的Exchange Type。那么Exchange是怎样将消息准确的推送到对应的Queue的呢?那么这里的功劳最大的当属Binding,RabbitMQ是通过Binding将Exchange和Queue链接在一起,这样Exchange就知道如何将消息准确的推送到Queue中去。简单示意图如下所示:

     在绑定(Binding)Exchange和Queue的同时,一般会指定一个Binding Key,生产者将消息发送给Exchange的时候,一般会产生一个Routing Key,当Routing Key和Binding Key对应上的时候,消息就会发送到对应的Queue中去。那么Exchange有四种类型,不同的类型有着不同的策略。也就是表明不同的类型将决定绑定的Queue不同,换言之就是说生产者发送了一个消息,Routing Key的规则是A,那么生产者会将Routing Key=A的消息推送到Exchange中,这时候Exchange中会有自己的规则,对应的规则去筛选生产者发来的消息,如果能够对应上Exchange的内部规则就将消息推送到对应的Queue中去。那么接下来就来详细讲解下Exchange里面类型。

     Exchange Type

    我来用表格来描述下类型以及类型之间的区别。

  • fanout

        fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。

    上图所示,生产者(P)生产消息1将消息1推送到Exchange,由于Exchange Type=fanout这时候会遵循fanout的规则将消息推送到所有与它绑定Queue,也就是图上的两个Queue最后两个消费者消费。

  • direct

        direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中

     当生产者(P)发送消息时Rotuing key=booking时,这时候将消息传送给Exchange,Exchange获取到生产者发送过来消息后,会根据自身的规则进行与匹配相应的Queue,这时发现Queue1和Queue2都符合,就会将消息传送给这两个队列,如果我们以Rotuing key=create和Rotuing key=confirm发送消息时,这时消息只会被推送到Queue2队列中,其他Routing Key的消息将会被丢弃。

  • topic

      前面提到的direct规则是严格意义上的匹配,换言之Routing Key必须与Binding Key相匹配的时候才将消息传送给Queue,那么topic这个规则就是模糊匹配,可以通过通配符满足一部分规则就可以传送。它的约定是:

  1. routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  2. binding key与routing key一样也是句点号“. ”分隔的字符串
  3. binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

  当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue中,如果Routing Key=A.C.E这时候会被同是路由到Queue1和Queue2中,如果Routing Key=A.F.B时,这里只会发送一条消息到Queue2中。

  • headers

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
该类型的Exchange没有用到过(不过也应该很有用武之地),所以不做介绍。

这里在对其进行简要的表格整理:

Exchange规则
类型名称类型描述
fanout把所有发送到该Exchange的消息路由到所有与它绑定的Queue中
directRouting Key==Binding Key
topic我这里自己总结的简称模糊匹配
headersExchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

  补充说明:

  ConnectionFactory、Connection、Channel

  ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。ConnectionFactory为Connection的制造工厂。
  Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。

  Connection就是建立一个TCP连接,生产者和消费者的都是通过TCP的连接到RabbitMQ Server中的,这个后续会再程序中体现出来。

  Channel虚拟连接,建立在上面TCP连接的基础上,数据流动都是通过Channel来进行的。为什么不是直接建立在TCP的基础上进行数据流动呢?如果建立在TCP的基础上进行数据流动,建立和关闭TCP连接有代价。频繁的建立关闭TCP连接对于系统的性能有很大的影响,而且TCP的连接数也有限制,这也限制了系统处理高并发的能力。但是,在TCP连接中建立Channel是没有上述代价的。

六.交换机

交换机的类型:
交换机主要包括如下4种类型:

Direct exchange(直连交换机)
Fanout exchange(扇型交换机)
Topic exchange(主题交换机)
Headers exchange(头交换机)
另外RabbitMQ默认定义一些交换机:

默认交换机
amq.* exchanges
还有一类特殊的交换机:Dead Letter Exchange(死信交换机)

Direct exchange(直连交换机)
直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的,步骤如下:

将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
当一个携带着路由值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。
Fanout exchange(扇型交换机)
扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列。不同于直连交换机,路由键在此类型上不启任务作用。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的发送给这所有的N个队列

Topic exchange(主题交换机)
主题交换机(topic exchanges)中,队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列。

扇型交换机和主题交换机异同:

对于扇型交换机路由键是没有意义的,只要有消息,它都发送到它绑定的所有队列上
对于主题交换机,路由规则由路由键决定,只有满足路由键的规则,消息才可以路由到对应的队列上
Headers exchange(头交换机)
类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。 
此交换机有个重要参数:”x-match”

当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件
当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功
RabbitMQ默认定义一些交换机
在RabbitMQ默认定义一些交换机,主要如下:

默认交换机 
默认交换机(default exchange)实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange)。它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。

如:当你声明了一个名为”hello”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为”hello”。因此,当携带着名为”hello”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello”的队列中。即默认交换机看起来貌似能够直接将消息投递给队列,如同我们之前文章里看到一例子。

类似amq.*的名称的交换机

这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403 (ACCESS_REFUSED)错误

Dead Letter Exchange(死信交换机)
在默认情况,如果消息在投递到交换机时,交换机发现此消息没有匹配的队列,则这个消息将被悄悄丢弃。为了解决这个问题,RabbitMQ中有一种交换机叫死信交换机。当消费者不能处理接收到的消息时,将这个消息重新发布到另外一个队列中,等待重试或者人工干预。这个过程中的exchange和queue就是所谓的”Dead Letter Exchange 和 Queue”

交换机的属性
除交换机类型外,在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是:

Name:交换机名称
Durability:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
Auto-delete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
Arguments:扩展参数


七.代码实例

默认交换机 生产者发送消息

 

ConnectionFactory factory = new ConnectionFactory();
      factory.setHost("localhost");
      factory.setUsername("jianpeng3");
      factory.setPassword("123456");
      factory.setPort(5672);
      factory.setVirtualHost("/");


      Connection connection = factory.newConnection();
      Channel channel = connection.createChannel();

       /*接下创建channel(信道),这是绝大多数API都能用到的。为了发送消息,你必须要声明一个消息消息队列,然后向该队列里推送消息*/
      channel.queueDeclare(QUEUE_NAME1, false, false, true, null);

//    channel.exchangeDeclare(QUEUE_NAME,"direct");
      String message = "Hello World!";
      channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
      System.out.println(" [x] Sent '" + message + "'");

       /*声明一个幂等的队列(只有在该队列不存在时,才会被创建)。消息的上下文是一个
       字节数组,你可以指定它的编码。*/
      channel.close();
      connection.close();

如果报错 多半是连接的参数不对或者服务没有起来或者是权限不够

channel.queueDeclare(QUEUE_NAME1, false, false, true, null);

// 声明一个队列 -// queue 队列名称(第一个参数 , 下边类推,最后一个参数没什么用)
// durable 为true时server重启队列不会消失 (是否持久化)
// exclusive 队列是否是独占的,如果为true只能被一个connection使用,其他连接建立时会抛出异常 
// autoDelete 当没有任何消费者使用时,自动删除该队列

channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8")); 
* 向server发布一条消息 
* 参数1:exchange名字,若为空则使用默认的exchange 
* 参数2:routing key 
* 参数3:其他的属性 
* 参数4:消息体 
* RabbitMQ默认有一个exchange,叫default exchange,它用一个空字符串表示,它是direct exchange类型, 
* 任何发往这个exchange的消息都会被路由到routing key的名字对应的队列上,如果没有对应的队列,则消息会被丢弃 
这个例子用的默认交换机,所以不用写交换机名字.

消费者接收消息

       // 创建连接工厂
      ConnectionFactory factory = new ConnectionFactory();
      //设置RabbitMQ地址
      factory.setHost("localhost");
      factory.setUsername("jianpeng3");
      factory.setPassword("123456");
      factory.setPort(5672);
      factory.setVirtualHost("/");
      //创建一个新的连接
      Connection connection = factory.newConnection();
      //创建一个通道
      Channel channel = connection.createChannel();
      //声明要关注的队列
      channel.queueDeclare(QUEUE_NAME1, false, false, true, null);
//    channel.queueBind(channel.queueDeclare().getQueue(), QUEUE_NAME1, "");//对队列进行绑定

      System.out.println("连接成功,开始监听"+QUEUE_NAME1+"   queue队列");
      //DefaultConsumer类实现了Consumer接口,通过传入一个频道,
      // 告诉服务器我们需要那个频道的消息,如果频道中有消息,就会执行回调函数handleDelivery
      Consumer consumer = new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag, Envelope envelope,
                              AMQP.BasicProperties properties, byte[] body)
               throws IOException {
            String message = new String(body, "UTF-8");
            System.out.println("Customer Received '" + message + "'");
         }
      };
      //自动回复队列应答 -- RabbitMQ中的消息确认机制
//    channel.basicConsume(QUEUE_NAME, false, consumer);
      channel.basicConsume(QUEUE_NAME, true, consumer);

channel.basicConsume(QUEUE_NAME, true, consumer);

/*消息消费完成确认
* autoAck 是否自动确认 true自动确认 false手动确认(第二个参数)
* 模式1:自动确认 只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。 
* 模式2:手动确认
* 消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么 该消息将一直处于不可用状态。
* 如果选用自动确认,在消费者拿走消息执行过程中出现宕机时,消息可能就会丢失!!
*/

autoAck:是否自动ack,如果不自动ack,需要使用channel.ack、channel.nack、channel.basicReject 进行消息应答

这个过程中channel.queueDeclare(QUEUE_NAME1, false, false, true, null);只在生产者声明就可以了.若是在消费者再声明也不会有问题,

直连交换机

消费者

ConnectionFactory factory = new ConnectionFactory();
factory.setPort(5672);
factory.setHost("localhost");
factory.setUsername("jianpeng3");
factory.setPassword("123456");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"direct");//注意是direct


//发送信息
for (String routingKey:routingKeys){
   String message = "RoutingSendDirect Send the message level:" + routingKey;
   //basicPublish第一个参数为交换机名称、第二个参数为队列映射的路由key、第三个参数为消息的其他属性、第四个参数为发送信息的主体
   channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes());
   System.out.println("RoutingSendDirect Send"+routingKey +"':'" + message);
}
channel.close();
connection.close();
channel.exchangeDeclare(EXCHANGE_NAME,"direct");//注意是direct  声明交换机,这个方法有五个参数,此例子是重载两个参数的方法

 

第一个参数是自己定义的交换机名字
第二个参数 type:有direct、fanout、topic三种
第三个参数 durable:true、false true:服务器重启会保留下来Exchange。警告:仅设置此选项,不代表消息持久化。即不保证重启后消息还在。原文:true if we are declaring a durable exchange (the exchange will survive a server restart)
第四个参数 autoDelete:true、false.true:当已经没有消费者时,服务器是否可以删除该Exchange。原文1:true if the server should delete the exchange when it is no longer in use。
最后一个参数没有用

 

声明了交换机后,channel.basicPublish()的第一个参数就要填写交换机的名字

消费者接收消息

ConnectionFactory factory = new ConnectionFactory();
      factory.setHost("localhost");
      factory.setUsername("jianpeng3");
      factory.setPassword("123456");;
      factory.setPort(5672);
      Connection connection = factory.newConnection();
      Channel channel = connection.createChannel();
      //声明交换器
      channel.exchangeDeclare(EXCHANGE_NAME, "direct");
      //获取匿名队列名称
      String queueName=channel.queueDeclare().getQueue();

      //根据路由关键字进行绑定
      for (String routingKey:routingKeys){
         channel.queueBind(queueName,EXCHANGE_NAME,routingKey);
//       System.out.println("ReceiveLogsDirect1 exchange:"+EXCHANGE_NAME+"," +
//             " queue:"+queueName+", BindRoutingKey:" + routingKey);
      }
      System.out.println("ReceiveLogsDirect1  Waiting for messages");
      Consumer consumer = new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag, Envelope envelope,
                              AMQP.BasicProperties properties, byte[] body) throws IOException {
            String message = new String(body, "UTF-8");
            System.out.println("ReceiveLogsDirect1 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
         }
      };
      channel.basicConsume(queueName, true, consumer);

比较默认交换机,直连交换机消费者代码只多了channel.queueBind(queueName,EXCHANGE_NAME,routingKey);

将队列跟交换器进行绑定,参数1 队列名称  参数2 交换机名称,  参数3  队列跟交换机绑定的键值

广播交换机

这个比较简单

发送消息时候

channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());第二个参数不要填就ok了,因为是广播模式,所有消费者都能接收到消息

消费者接收消息

channel.queueBind(queueName, EXCHANGE_NAME, "");//对队列进行绑定 不要填第3个参数,因为没有键

匹配交换机

生产者与其他类似

消费者

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("jianpeng3");
factory.setPassword("123456");;
factory.setPort(5672);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();

//声明一个匹配模式的交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
//路由关键字
String[] routingKeys = new String[]{"*.orange.*"};
//绑定路由
for (String routingKey : routingKeys) {
   channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
   System.out.println("ReceiveLogsTopic1 exchange:" + EXCHANGE_NAME + ", queue:" + queueName + ", BindRoutingKey:" + routingKey);
}
System.out.println("ReceiveLogsTopic1 Waiting for messages");

Consumer consumer = new DefaultConsumer(channel) {
   @Override
   public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
      String message = new String(body, "UTF-8");
      System.out.println("ReceiveLogsTopic1 Received '" + envelope.getRoutingKey() + "':'" + message + "'");
   }
};
channel.basicConsume(queueName, true, consumer);

与其他不同的是 channel.queueBind(queueName, EXCHANGE_NAME, routingKey);  第三个参数 这个键用正则表达式代替

*可以匹配一个标识符。

#可以匹配0个或多个标识符。

.是分隔

工作流程:

 在最初状态下,生产者发送消息的时候

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

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

(3) 生产者声明一个队列井设置相关属性,比如是否排他、是否持久化、是否自动删除等

( 4 ) 生产者通过路由键将交换器和队列绑定起来

( 5 ) 生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息

(6) 相应的交换器根据接收到的路由键查找相匹配的队列。

( 7 ) 如果找到,则将从生产者发送过来的消息存入相应的队列中。

(8) 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者

(9) 关闭信道。

(1 0) 关闭连接。

消费者接收消息的过程:

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

(2) 消费者向RabbitMQ Broker 请求消费相应队列中的消息,声明要关注的队列,即bindkey

(3)等待RabbitMQ Broker 回应并投递相应队列中的消息, 消费者接收消息。

(4) 消费者确认( ack) 接收到的消息。

( 5) RabbitMQ 从队列中删除相应己经被确认的消息。

( 6) 关闭信道。

( 7) 关闭连接。

整合spring

maven依赖

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

连接参数

mq.username=jianpeng3
mq.password=123456
mq.host=127.0.0.1
mq.port=5672

在spring主配置文件中加入,

<import resource="classpath*:spring-mq.xml"/>

spring-mq.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:util="http://www.springframework.org/schema/util"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xmlns:rabbit="http://www.springframework.org/schema/rabbit"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/util
     http://www.springframework.org/schema/util/spring-util-3.0.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/rabbit
     http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">
     
    <!-- RabbitMQ start -->
    <!-- 连接配置 -->
    <rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}"
        password="${mq.password}" port="${mq.port}"  />
        
    <rabbit:admin connection-factory="connectionFactory"/>
    
    <!-- 消息队列客户端 -->
    <rabbit:template id="amqpTemplate" exchange="${mq.queue}_exchange" connection-factory="connectionFactory"  />
    
    <!-- queue 队列声明 -->
    <!-- 
        durable 是否持久化 
        exclusive 仅创建者可以使用的私有队列,断开后自动删除 
        auto-delete 当所有消费端连接断开后,是否自动删除队列 -->
    <rabbit:queue id="test_queue" name="${mq.queue}_testQueue" durable="true" auto-delete="false" exclusive="false" />
    
    <!-- 交换机定义 -->
    <!-- 
        交换机:一个交换机可以绑定多个队列,一个队列也可以绑定到多个交换机上。
        如果没有队列绑定到交换机上,则发送到该交换机上的信息则会丢失。
        
        direct模式:消息与一个特定的路由器完全匹配,才会转发
        topic模式:按模式匹配
     -->
    <rabbit:topic-exchange name="${mq.queue}_exchange" durable="true" auto-delete="false">
        <rabbit:bindings>
            <!-- 设置消息Queue匹配的pattern (direct模式为key) -->
            <rabbit:binding queue="test_queue" pattern="${mq.queue}_patt"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    
    <bean name="rabbitmqService" class="com.enh.mq.RabbitmqService"></bean>
    
    <!-- 配置监听 消费者 -->
    <rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
        <!-- 
            queues 监听队列,多个用逗号分隔 
            ref 监听器 -->
        <rabbit:listener queues="test_queue" ref="rabbitmqService"/>
    </rabbit:listener-container>
</beans>

消费者

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;

public class RabbitmqService implements MessageListener {
    public void onMessage(Message message) {
        System.out.println("消息消费者 = " + message.toString());
    }
}

生产者

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Description: 消息队列发送者
 * @Author: 
 * @CreateTime: 
 */
@Service
public class Producer {

    @Autowired
    private AmqpTemplate amqpTemplate;
    
    public void sendQueue(String exchange_key, String queue_key, Object object) {
        // convertAndSend 将Java对象转换为消息发送至匹配key的交换机中Exchange
        amqpTemplate.convertAndSend(exchange_key, queue_key, object);
    }
}

 

 

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值