RabbitMQ
来源:互联网 发布:信鸽软件鑫 编辑:程序博客网 时间:2024/06/03 17:15
参考:https://www.cnblogs.com/luxiaoxun/p/3918054.html
1.RabbitMQ简述
RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。作用是:异步,解耦,缓冲,消息分发
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。它是一种二进制协议。默认启动端口 5672。
主要分为三个部分:生产者,交换机和队列,消费者。
需要注意消息持久化,目的为了防止 RabbitMQ 宕机;考虑 ACK 机制,目的为了如果消费者对消息的处理失败了,那么后续要如何处理。
具体特点:
⑴可靠性(Reliability)
RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。
⑵灵活的路由(Flexible Routing)
在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。
⑶消息集群(Clustering)
多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。
⑷高可用(Highly Available Queues)
队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。
⑸多种协议(Multi-protocol)
RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。
⑹多语言客户端(Many Clients)
RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。
⑺管理界面(Management UI)
RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。
⑻跟踪机制(Tracing)
如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
⑼插件机制(Plugin System)
RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。
2.RabbitMQ概念模型
左侧 P 代表 生产者,也就是往 RabbitMQ 发消息的程序。
中间即是 RabbitMQ,其中包括了 交换机 和 队列。
右侧 C 代表 消费者,也就是往 RabbitMQ 拿消息的程序。
其中比较重要的概念有 4 个,分别为:虚拟主机,交换机,队列,和绑定。
虚拟主机:一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制。 因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。
交换机:Exchange 用于转发消息,但是它不会做存储 ,如果没有 Queue bind 到 Exchange 的话,它会直接丢弃掉 Producer 发送过来的消息。
这里有一个比较重要的概念:路由键 。消息到交换机的时候,交互机会转发到对应的队列中,那么究竟转发到哪个队列,就要根据该路由键。
绑定:也就是交换机需要和队列相绑定,这其中如上图所示,是多对多的关系。
3.exchange交换机机制
producer并不知道消息具体分发到哪个队列中去,当exchange收到message时,必须准确知道该如何分发。是append到一定规则的queue,还是append到多个queue中,还是被丢弃?这些规则都是通过exchange的4种type去定义的。
在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中,在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。
binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。
exchange和queue通过routing-key关联,这两者之间的关系是就是binding。如下图所示,X表示交换机,红色表示队列,交换机通过一个routing-key去binding一个queue,routing-key有什么作用呢?看Direct exchange类型交换机。
'
⑴Directed Exchange
路由键exchange,该交换机收到消息后会把消息发送到指定routing-key的queue中。那消息交换机是怎么知道的呢?其实,producer deliver消息的时候会把routing-key add到 message header中。routing-key只是一个messgae的attribute。
Default Exchange
这种是特殊的Direct Exchange,是rabbitmq内部默认的一个交换机。该交换机的name是空字符串,所有queue都默认binding 到该交换机上。所有binding到该交换机上的queue,routing-key都和queue的name一样。
⑵Topic Exchang
通配符交换机,exchange会把消息发送到一个或者多个满足通配符规则的routing-key的queue。其中*表号匹配一个word,#匹配多个word和路径,路径之间通过.隔开。如满足a.*.c的routing-key有a.hello.c;满足#.hello的routing-key有a.b.c.helo。
⑶Fanout Exchange
扇形交换机,该交换机会把消息发送到所有binding到该交换机上的queue。这种是publisher/subcribe模式。用来做广播最好。
所有该exchagne上指定的routing-key都会被ignore掉。
⑷Header Exchange
设置header attribute参数类型的交换机。
4.ACK机制
采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完 成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。这样仅能保证每次Consumer接到数据。
为了保证数据能被正确处理,我们不能采用no-ack。而应该是在处理完数据后发送ack。
5.代码分析
消费者1:
package test2;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeoutException;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.ConsumerCancelledException;import com.rabbitmq.client.QueueingConsumer;import com.rabbitmq.client.ShutdownSignalException;@SuppressWarnings("deprecation")public class Consumer { private static final String QUEUE_NAME = "log2"; public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException, InterruptedException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); String queueName = QUEUE_NAME; //producerTest.XT xt = producerTest.XT.FANOUT; // producerTest.XT xt = producerTest.XT.DIRECT; producerTest.XT xt = producerTest.XT.TOPIC; switch (xt) { /* case DEFAULT: //队列的相关参数需要与第一次定义该队列时相同,否则会出错,使用channel.queueDeclarePassive()可只被动绑定已有队列,而不创建 channel.queueDeclare(queueName, true, false, true, null); break;*/ case FANOUT: //接收端也声明一个fanout交换机 channel.exchangeDeclare(producerTest.XCHG_NAME, "fanout", true, true, null); //channel.exchangeDeclarePassive() 可以使用该函数使用一个已经建立的exchange //声明一个临时队列,该队列会在使用完比后自动销毁 queueName = channel.queueDeclare().getQueue(); //将队列绑定到交换机,参数3无意义此时 channel.queueBind(queueName, producerTest.XCHG_NAME, ""); break; case DIRECT: channel.exchangeDeclare(producerTest.XCHG_NAME, "direct", true, true, null); queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, producerTest.XCHG_NAME, "info"); //绑定一个routing key,可以绑定多个 channel.queueBind(queueName, producerTest.XCHG_NAME, "warning"); break; case TOPIC: channel.exchangeDeclare(producerTest.XCHG_NAME, "topic", true, true, null); queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, producerTest.XCHG_NAME, "warning.#"); //监听两种模式 #匹配一个或多个单词 *匹配一个单词 channel.queueBind(queueName, producerTest.XCHG_NAME, "*.blue"); break; } // 在同一时间不要给一个worker一个以上的消息。 // 不要将一个新的消息分发给worker知道它处理完了并且返回了前一个消息的通知标志(acknowledged) // 替代的,消息将会分发给下一个不忙的worker。 channel.basicQos(1); //server push消息时的队列长度 //用来缓存服务器推送过来的消息 QueueingConsumer consumer = new QueueingConsumer(channel); //为channel声明一个consumer,服务器会推送消息 //参数1:队列名称 //参数2:是否发送ack包,不发送ack消息会持续在服务端保存,直到收到ack。 可以通过channel.basicAck手动回复ack //参数3:消费者 channel.basicConsume(queueName, false, consumer); //channel.basicGet() //使用该函数主动去服务器检索是否有新消息,而不是等待服务器推送 while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); System.out.println("消费者1Received " + new String(delivery.getBody())); //回复ack包,如果不回复,消息不会在服务器删除 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); //channel.basicReject(); channel.basicNack(); //可以通过这两个函数拒绝消息,可以指定消息在服务器删除还是继续投递给其他消费者 } }}
消费者2:
package test2;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeoutException;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.ConsumerCancelledException;import com.rabbitmq.client.QueueingConsumer;import com.rabbitmq.client.ShutdownSignalException;public class Consumer2 { private static final String QUEUE_NAME = "log2"; public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException, InterruptedException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); String queueName = QUEUE_NAME; //producerTest.XT xt = producerTest.XT.FANOUT; // producerTest.XT xt = producerTest.XT.DIRECT; producerTest.XT xt = producerTest.XT.TOPIC; switch (xt) { /* case DEFAULT: //队列的相关参数需要与第一次定义该队列时相同,否则会出错,使用channel.queueDeclarePassive()可只被动绑定已有队列,而不创建 channel.queueDeclare(queueName, true, false, true, null); break;*/ case FANOUT: //接收端也声明一个fanout交换机 channel.exchangeDeclare(producerTest.XCHG_NAME, "fanout", true, true, null); //channel.exchangeDeclarePassive() 可以使用该函数使用一个已经建立的exchange //声明一个临时队列,该队列会在使用完比后自动销毁 queueName = channel.queueDeclare().getQueue(); //将队列绑定到交换机,参数3无意义此时 channel.queueBind(queueName, producerTest.XCHG_NAME, ""); break; case DIRECT: channel.exchangeDeclare(producerTest.XCHG_NAME, "direct", true, true, null); queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, producerTest.XCHG_NAME, "info"); //绑定一个routing key,可以绑定多个 channel.queueBind(queueName, producerTest.XCHG_NAME, "warning222"); break; case TOPIC: channel.exchangeDeclare(producerTest.XCHG_NAME, "topic", true, true, null); queueName = channel.queueDeclare().getQueue(); channel.queueBind(queueName, producerTest.XCHG_NAME, "warning.#"); //监听两种模式 #匹配一个或多个单词 *匹配一个单词 channel.queueBind(queueName, producerTest.XCHG_NAME, "*.blue"); break; } // 在同一时间不要给一个worker一个以上的消息。 // 不要将一个新的消息分发给worker知道它处理完了并且返回了前一个消息的通知标志(acknowledged) // 替代的,消息将会分发给下一个不忙的worker。 channel.basicQos(1); //server push消息时的队列长度 //用来缓存服务器推送过来的消息 QueueingConsumer consumer = new QueueingConsumer(channel); //为channel声明一个consumer,服务器会推送消息 //参数1:队列名称 //参数2:是否发送ack包,不发送ack消息会持续在服务端保存,直到收到ack。 可以通过channel.basicAck手动回复ack //参数3:消费者 channel.basicConsume(queueName, false, consumer); //channel.basicGet() //使用该函数主动去服务器检索是否有新消息,而不是等待服务器推送 while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); System.out.println("消费者2Received " + new String(delivery.getBody())); //回复ack包,如果不回复,消息不会在服务器删除 channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); //channel.basicReject(); channel.basicNack(); //可以通过这两个函数拒绝消息,可以指定消息在服务器删除还是继续投递给其他消费者 } }}生产者:
package test2;import java.io.IOException;import java.util.HashMap;import java.util.Map;import java.util.Scanner;import java.util.concurrent.TimeoutException;import javax.xml.parsers.FactoryConfigurationError;import com.rabbitmq.client.*;public class producerTest { //定义枚举,交换类型 public enum XT { DIRECT, TOPIC, HEADERS, FANOUT } private static final String QUEUE_NAME = "log2"; private static boolean GetInputString() { message = scanner.nextLine(); if (message.length() == 0) return false; return true; } private static Scanner scanner = new Scanner(System.in); private static String message = ""; public static String XCHG_NAME = "xchg6"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); //声明一个连接 Channel channel = connection.createChannel(); //声明消息通道 // XT xt = XT.DIRECT; XT xt = XT.TOPIC; //XT xt = XT.FANOUT; switch (xt) { case FANOUT: //广播给所有队列 接收方也必须通过fanout交换机获取消息,所有连接到该交换机的consumer均可获取消息 //如果producer在发布消息时没有consumer在监听,消息将被丢弃 //定义一个交换机 //参数1:交换机名称 //参数2:交换机类型 //参数3:交换机持久性,如果为true则服务器重启时不会丢失 //参数4:交换机在不被使用时是否删除 //参数5:交换机的其他属性 channel.exchangeDeclare(XCHG_NAME, "fanout", true, true, null); while (GetInputString()) { //向server发布一条消息 //参数1:exchange名字,若为空则使用默认的exchange //参数2:routing key //参数3:其他的属性 //参数4:消息体 //RabbitMQ默认有一个exchange,叫default exchange,它用一个空字符串表示,它是direct exchange类型, //任何发往这个exchange的消息都会被路由到routing key的名字对应的队列上,如果没有对应的队列,则消息会被丢弃 //发送一条广播消息,参数2此时无意义 channel.basicPublish(XCHG_NAME, "", null, message.getBytes()); System.out.println("Send " + message); } break; case DIRECT: //定义一个交换机 //参数1:交换机名称 //参数2:交换机类型 //参数3:交换机持久性,如果为true则服务器重启时不会丢失 //参数4:交换机在不被使用时是否删除 //参数5:交换机的其他属性 //向所有绑定了相应routing key的队列发送消息 //如果producer在发布消息时没有consumer在监听,消息将被丢弃 //如果有多个consumer监听了相同的routing key 则他们都会受到消息 channel.exchangeDeclare(XCHG_NAME, "direct", true, true, null); while (GetInputString()) { //input like : info message String[] temp = message.split(","); channel.basicPublish(XCHG_NAME, temp[0], null, temp[1].getBytes()); System.out.println("Send " + message); } break; case TOPIC: //与direct模式有类似之处,都使用routing key作为路由 //不同之处在于direct模式只能指定固定的字符串,而topic可以指定一个字符串模式 channel.exchangeDeclare(XCHG_NAME, "topic", true, true, null); while (GetInputString()) { //input like : topic message String[] temp = message.split(","); channel.basicPublish(XCHG_NAME, temp[0], null, temp[1].getBytes()); System.out.println("Send " + message); } break; } channel.close(); connection.close(); } }
1.扇形分发:生产者:23213Send 23213消费者1:消费者1Received 23213消费者2:消费者2Received 232132.direct 分发(记得改下交换机名字)生产者:info,heidouSend info,heidou消费者1:消费者1Received heidou消费者2:消费者2Received heidou生产者:warning,123Send warning,123消费者1:消费者1Received 123消费者2:没有匹配上所以无输出3.TOPIC分发:生产者:warning.heidou,123Send warning.heidou,123消费者1:消费者1Received 123消费者2:消费者2Received 123
- rabbitmq
- RabbitMQ
- RabbitMQ
- RabbitMQ
- rabbitmq
- RabbitMQ
- RabbitMQ
- rabbitmq
- RabbitMQ
- rabbitmq
- RabbitMQ
- rabbitmq
- RabbitMQ
- rabbitmq
- RabbitMQ
- RabbitMQ
- RabbitMQ
- rabbitmq
- Mac里的airdrop传输文件
- 程序人生
- hadoop2配置文件详解
- prim算法(未优化版)
- 根据开始日期和结束日期获取所有日期的方法
- RabbitMQ
- java基础 堆 和 栈序列1
- 综合变换
- [ArcGIS] 焦点统计应用 提取山顶点 地形起伏度
- bzoj3674 可持久化并查集加强版 (主席树 & 并查集)
- 自定义类型部分知识写一篇博客。 知识点: >结构体类型创建 >结构体初始化 >结构体内存对齐 >位段,位段计算机大小。 >枚举+联合。
- TensorFlow学习笔记1:Hello TensorFlow!
- 分布式锁之数据库锁
- LeetCode 300. Longest Increasing Subsequence