RabbitMQ总结
来源:互联网 发布:拍拍微店官网软件 编辑:程序博客网 时间:2024/06/06 05:19
一、环境搭建
第一步:软件安装
1、 如果安装RabbitMQ首先安装基于erlang语言支持的OTP软件
2、 安装完成OTP之后再安装RabbitMQ
OTP下载地址:http://www.erlang.org/downloads
RabbitMQ下载地址:http://www.rabbitmq.com/news.html
第二步:环境变量配置
以上步骤都完成之后配置环境变量
首先配置ERLANG_HOME如下图(变量值就是你按照otp软件的路径)
然后在配置RABBITMQ_SERVER如下图(变量值是rabbitMQ的安装路径)
最后进行path的配置如下图(path的值为;%ERLANG_HOME%\bin;%RABBITMQ_SERVER%\sbin;注意是追加)
第三步:启动监控管理器
找到你安装rabbitMQ的路径,然后切换到sbin的文件夹,输入rabbitmq-plugins enable rabbitmq_management命令来启动监控管理器,然后在浏览器输入http:localhost:15672 用户名和密码默认都为guest。
二、RabbitMQ常用的命令
启动监控管理器:rabbitmq-plugins enable rabbitmq_management
关闭监控管理器:rabbitmq-plugins disable rabbitmq_management
启动rabbitmq:rabbitmq-service start
关闭rabbitmq:rabbitmq-service stop
查看所有的队列:rabbitmqctl list_queues
清除所有的队列:rabbitmqctl reset
关闭应用:rabbitmqctl stop_app
启动应用:rabbitmqctl start_app
用户和权限设置
添加用户:rabbitmqctl add_user username password
分配角色:rabbitmqctl set_user_tags username administrator
新增虚拟主机:rabbitmqctl add_vhost vhost_name
将新虚拟主机授权给新用户:rabbitmqctl set_permissions -p vhost_name username '.*' '.*' '.*'
角色说明
none 最小权限角色
management 管理员角色
policymaker 决策者
monitoring 监控
administrator 超级管理员
三、RabbitMQ Java教程
1、简介
AMQP,即Advanced MessageQueuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
下面将重点介绍RabbitMQ中的一些基础概念,了解了这些概念,是使用好RabbitMQ的基础。
2、引入依赖架包
3、 DEMO
1."HelloWorld!"
发送端代码:
package com.my.demo1;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * 生产者 * <p> * Created by YJH on 2017/11/8 21:59. */public class Send { private final static String QUEUE_NAME = "hello"; public static void main(String[] args) throws IOException, TimeoutException, InterruptedException { //创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置RabbitMQ相关信息 factory.setHost("localhost"); //factory.setUsername(""); //factory.setPassword(""); //factory.setPort(8888); //创建一个新的连接 Connection connection = factory.newConnection(); //创建一个通道 Channel channel = connection.createChannel(); //声明一个队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); //发送消息到队列中 for (int i = 0; i < 10; i++) { String msg = "这是生产者发送的消息 ---> " + i; channel.basicPublish("", QUEUE_NAME, null, msg.getBytes()); System.out.println(" [x] 发送: '" + msg + "'"); Thread.sleep(5500); } //关闭通道和连接 channel.close(); connection.close(); }}
接收端代码:
package com.my.demo1;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * 消费者 * <p> * Created by YJH on 2017/11/8 22:18. */public class Recv { private final static String QUEUE_NAME = "hello"; public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂 ConnectionFactory factory = new ConnectionFactory(); //设置服务器地址 factory.setHost("localhost"); //创建一个新的连接 Connection connection = factory.newConnection(); //创建一个通道 Channel channel = connection.createChannel(); //声明要关注的队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); System.out.println(" [*] Waiting for messages..."); //DefaultConsumer类实现了Consumer接口,通过传入一个通道 //告诉服务器我们需要那个频道的消息,如果频道中有消息,就会执行回调函数handleDelivery Consumer consumer = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { super.handleDelivery(consumerTag, envelope, properties, body); String msg = new String(body, "UTF-8"); System.out.println(" [x] 接收: '" + msg + "'"); } }; //自动回复队列应答 -- RabbitMQ中的消息确认机制 channel.basicConsume(QUEUE_NAME, true, consumer); }}
注释:
ConnectionFactory、Connection、Channel都是RabbitMQ对外提供的API中最基本的对象。Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。ConnectionFactory为Connection的制造工厂。
Channel是我们与RabbitMQ打交道的最重要的一个接口,我们大部分的业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。
Queue(队列)是RabbitMQ的内部对象,用于存储消息,用下图表示。
RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。
queueDeclare()方法:
第一个参数表示队列名称、
第二个参数为是否持久化(true表示是,队列将在服务器重启时生存)、
第三个参数为是否是独占队列(创建者可以使用的私有队列,断开后自动删除)、
第四个参数为当所有消费者客户端连接断开时是否自动删除队列、
第五个参数为队列的其他参数
basicPublish()方法:
第一个参数为交换机名称、
第二个参数为队列映射的路由key、
第三个参数为消息的其他属性、
第四个参数为发送信息的主体
2. Work Queues
发送端代码:
package com.my.demo2;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.MessageProperties;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * Created by YJH on 2017/11/8 23:02. */public class NewTask { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //队列——队列的名称 //持久——如果我们声明一个持久队列(队列将在服务器重启后幸存) //互斥——如果我们声明一个排它队列(仅限于此连接),则为true。 //自动删除——如果我们说有一个自动删除”队列(服务器将删除它时,不再使用) //参数——队列的其他属性(构造参数) channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); for (int i = 1; i <= 12; i++) { String msg = "这是第-" + i + "-条消息."; //exchange - 将消息发布到哪个exchange //routingKey - 路由key(队列的名称) //props - 消息的其他属性 - 路由头等 //body - 消息体 channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8")); System.out.println(" [x] 发送 '" + msg + "'"); } channel.close(); connection.close(); }}
接收端代码一:
package com.my.demo2;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * Created by YJH on 2017/11/8 23:17. */public class Worker { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); final Connection connection = factory.newConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); System.out.println(" [*] Waiting for messages..."); //设置一次分发的条数 channel.basicQos(10); final 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(" [x] 接收: '" + message + "'"); try { doWork(message); } finally { System.out.println(" [x] 完成"); channel.basicAck(envelope.getDeliveryTag(), false); } } }; //autoAck是否自动回复,如果为true的话,每次生产者只要发送信息就会从内存中删除, //那么如果消费者程序异常退出,那么就无法获取数据,我们当然是不希望出现这样的情况,所以才去手动回复, //每当消费者收到并处理信息然后在通知生成者。最后从队列中删除这条信息。如果消费者异常退出,如果还有其他消费者, //那么就会把队列中的消息发送给其他消费者,如果没有,等消费者启动时候再次发送 boolean autoAck = false; channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer); } private static void doWork(String task) { for (char ch : task.toCharArray()) { if (ch == '.') { try { Thread.sleep(2000); } catch (InterruptedException _ignored) { Thread.currentThread().interrupt(); } } } }}
接收端代码二:
package com.my.demo2;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * Created by YJH on 2017/11/8 23:17. */public class Worker2 { private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); final Connection connection = factory.newConnection(); final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); System.out.println(" [*] Waiting for messages..."); //设置一次分发的条数 channel.basicQos(1); final 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(" [x] 接收 '" + message + "'"); try { doWork(message); } finally { System.out.println(" [x] 完成"); channel.basicAck(envelope.getDeliveryTag(), false); } } }; //消息消费完成确认 channel.basicConsume(TASK_QUEUE_NAME, false, consumer); } private static void doWork(String task) { for (char ch : task.toCharArray()) { if (ch == '.') { try { Thread.sleep(2000); } catch (InterruptedException _ignored) { Thread.currentThread().interrupt(); } } } }}
注释:
channel.basicQos(1);保证一次只分发一个 。autoAck是否自动回复,如果为true的话,每次生产者只要发送信息就会从内存中删除,那么如果消费者程序异常退出,那么就无法获取数据,我们当然是不希望出现这样的情况,所以才去手动回复,每当消费者收到并处理信息然后在通知生成者。最后从队列中删除这条信息。如果消费者异常退出,如果还有其他消费者,那么就会把队列中的消息发送给其他消费者,如果没有,等消费者启动时候再次发送。
Message durability
如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失事件的发生(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了),如果我们需要对这种小概率事件也要管理起来,那么我们要用到事务。由于这里仅为RabbitMQ的简单介绍,所以这里将不讲解RabbitMQ相关的事务。
Prefetch count
前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
3. Publish/Subscribe
发送端代码:
package com.my.demo3;import com.rabbitmq.client.BuiltinExchangeType;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * 群发所有人 * <p> * 发布者 * <p> * Created by YJH on 2017/11/9 16:12. */public class EmitLog { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true); String msg = "info: 这是EmitLog消息"; channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes("UTF-8")); System.out.println(" [x] 发送 '" + msg + "'"); channel.close(); connection.close(); }}
接收端代码一:
package com.my.demo3;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * 订阅者 * <p> * Created by YJH on 2017/11/9 16:21. */public class ReceiveLogs { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //BuiltinExchangeType.FANOUT标识分发,所有的消费者得到同样的队列信息 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true); //创建一个自动删除,非持久队列,并且获取它们名称 String queueName = channel.queueDeclare().getQueue(); //将队列绑定到Exchange,不需要额外的参数。 //queue —— 队列的名称 //exchange —— exchange的名称 //routingKey —— 路由主要用于绑定 channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] 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(" [x] 接收 '" + message + "' queueName = " + queueName); } }; channel.basicConsume(queueName, true, consumer); }}
接收端代码二:
package com.my.demo3;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;/** * 订阅者 * <p> * Created by YJH on 2017/11/9 16:21. */public class ReceiveLogs2 { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] args) throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT, true); //创建一个自动删除,非持久队列,并且获取它们名称 String queueName = channel.queueDeclare().getQueue(); //将队列绑定到Exchange,不需要额外的参数。 //queue —— 队列的名称 //exchange —— exchange的名称 //routingKey —— 路由主要用于绑定 channel.queueBind(queueName, EXCHANGE_NAME, ""); System.out.println(" [*] 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(" [x] 接收 '" + message + "' queueName = " + queueName); } }; channel.basicConsume(queueName, true, consumer); }}
注释:
在上两个demo的队列都指定了名称,但是现在我们不需要这么做,我们需要接收所有的信息,而不只是其中的一个。如果要做这样的队列,我们需要2件事,一个就是获取一个新的空的队列,这样我就需要创建一个随机名称的队列,最好让服务器帮我们做出选择,第二个就是我们断开用户的队列,应该自动进行删除。
Exchange
生产者将消息投递到Queue中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。
Binding
RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了
4. Routing
发送端代码:
package com.my.demo4;import com.rabbitmq.client.BuiltinExchangeType;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;/** * 对指定人群发 * <p> * 发布者(发送"info", "warning", "error") * <p> * Created by YJH on 2017/11/9 22:06. */public class EmitLogDirect { private static final String EXCHANGE_NAME = "direct_logs"; /** * 路由关键字 */ private static final String[] routingKeys = new String[]{"info", "warning", "error"}; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); for (String key : routingKeys) { String message = "你好!擎天柱"; channel.basicPublish(EXCHANGE_NAME, key, null, message.getBytes("UTF-8")); System.out.println(" [x] 发送 '" + key + "':'" + message + "'"); } channel.close(); connection.close(); }}
接收端代码一:
package com.my.demo4;import com.rabbitmq.client.*;import java.io.IOException;import java.util.Arrays;/** * 订阅者(接收"error") * * Created by YJH on 2017/11/9 22:07. */public class ReceiveLogsDirect { //交换机名称 private static final String EXCHANGE_NAME = "direct_logs"; //路由key private static final String[] routingKeys = new String[]{"info", "warning"}; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //声明交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //获取匿名队列名称 String queueName = channel.queueDeclare().getQueue(); System.out.println(" [*] 绑定:"+ Arrays.toString(routingKeys)); //根据路由key进行绑定 for (String key : routingKeys) { channel.queueBind(queueName, EXCHANGE_NAME, key); } System.out.println(" [*] 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(" [x] 接收 '" + envelope.getRoutingKey() + "':'" + message + "'"); } }; channel.basicConsume(queueName, true, consumer); }}
接收端代码二:
package com.my.demo4;import com.rabbitmq.client.*;import java.io.IOException;import java.util.Arrays;/** * 订阅者(接收"error") * <p> * Created by YJH on 2017/11/9 22:07. */public class ReceiveLogsDirect2 { private static final String EXCHANGE_NAME = "direct_logs"; /** * 路由关键字 */ private static final String[] routingKeys = new String[]{"error"}; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //声明交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //获取匿名队列名称 String queueName = channel.queueDeclare().getQueue(); System.out.println(" [*] 绑定:" + Arrays.toString(routingKeys)); //根据路由key进行绑定 for (String key : routingKeys) { channel.queueBind(queueName, EXCHANGE_NAME, key); } System.out.println(" [*] 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(" [x] 接收 '" + envelope.getRoutingKey() + "':'" + message + "'"); } }; channel.basicConsume(queueName, true, consumer); }}
注释:
采用路由的方式对不同的消息进行过滤,可以实现类似于群聊和单聊都可以
Binding key
在绑定(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 Types
RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种(AMQP规范里还提到两种ExchangeType,分别为system与自定义),下面分别进行介绍。
fanout
fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。
上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。(如demo3)
direct
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。
以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…);如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。
topic
前面讲到direct类型的Exchange路由规则是完全匹配binding key与routing key,但这种严格的匹配方式在很多情况下不能满足实际业务需求。topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
l routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
l binding key与routing key一样也是句点号“. ”分隔的字符串
l binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。
headers
headers类型的Exchange不依赖于routingkey与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
该类型的Exchange没有用到过(不过也应该很有用武之地),所以不做介绍。
5. Topics
发送端代码:
package com.my.demo5;import com.rabbitmq.client.BuiltinExchangeType;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import java.util.Arrays;/** * 提供者 * <p> * Created by YJH on 2017/11/9 22:56. */public class EmitLogTopic { private static final String EXCHANGE_NAME = "topic_logs"; public static void main(String[] argv) { Connection connection = null; Channel channel = null; try { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); //声明一个BuiltinExchangeType.TOPIC模式的交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String[] routingKey = getRouting(); //发送消息 for (String key : routingKey) { String msg = "这是使用TOPIC -- " + key; channel.basicPublish(EXCHANGE_NAME, key, null, msg.getBytes("UTF-8")); System.out.println(" [x] 发送 '" + key + "':'" + msg + "'"); } } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (Exception ignore) { ignore.printStackTrace(); } } } } private static String[] getRouting() { //待发送的消息 String[] routingKeys = new String[]{ "quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox", "lazy.brown.fox", "quick.brown.fox", "quick.orange.male.rabbit", "lazy.orange.male.rabbit" }; System.out.println("routingKeys = " + Arrays.toString(routingKeys)); return routingKeys; }}
接收端代码一:
package com.my.demo5;import com.rabbitmq.client.*;import java.io.IOException;import java.util.Arrays;/** * 消费者(只接收 *.orange.*) * * "*" :可以替代一个词 * "#":可以替代0或者更多的词 * <p> * Created by YJH on 2017/11/9 22:59. */public class ReceiveLogsTopic { private static final String EXCHANGE_NAME = "topic_logs"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //声明一个BuiltinExchangeType.TOPIC模式的交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String queueName = channel.queueDeclare().getQueue(); String[] bindingKey = getRouting(); //绑定路由key for (String key : bindingKey) { channel.queueBind(queueName, EXCHANGE_NAME, key); } System.out.println(" [*] 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(" [x] 接收 '" + envelope.getRoutingKey() + "':'" + message + "'"); } }; channel.basicConsume(queueName, true, consumer); } private static String[] getRouting() { //待发送的消息 String[] routingKeys = new String[]{"*.orange.*"}; System.out.println("routingKeys = " + Arrays.toString(routingKeys)); return routingKeys; }}
接收端代码二:
package com.my.demo5;import com.rabbitmq.client.*;import java.io.IOException;import java.util.Arrays;/** * 消费者(只接收 "*.*.rabbit", "lazy.#") * <p> * "*" :可以替代一个词 * "#":可以替代0或者更多的词 * <p> * Created by YJH on 2017/11/9 22:59. */public class ReceiveLogsTopic2 { private static final String EXCHANGE_NAME = "topic_logs"; public static void main(String[] argv) throws Exception { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); //声明一个BuiltinExchangeType.TOPIC模式的交换机 channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String queueName = channel.queueDeclare().getQueue(); String[] bindingKey = getRouting(); //绑定路由key for (String key : bindingKey) { channel.queueBind(queueName, EXCHANGE_NAME, key); } System.out.println(" [*] 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(" [x] 接收 '" + envelope.getRoutingKey() + "':'" + message + "'"); } }; channel.basicConsume(queueName, true, consumer); } private static String[] getRouting() { //待发送的消息 String[] routingKeys = new String[]{"*.*.rabbit", "lazy.#"}; System.out.println("routingKeys = " + Arrays.toString(routingKeys)); return routingKeys; }}
注释:
这种应该属于模糊匹配
* :可以替代一个词
#:可以替代0或者更多的词
6. Remote procedure call (RPC)
服务点代码:
public class RPCServer { private static final String RPC_QUEUE_NAME = "rpc_queue"; private static int fib(int n) { if (n == 0) { return 0; } if (n == 1) { return 1; } return fib(n - 1) + fib(n - 1); } public static void main(String[] args) throws IOException, InterruptedException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = connection.createChannel(); channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null); channel.basicQos(1); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(RPC_QUEUE_NAME, false, consumer); System.out.println("RPCServer Awating RPC request"); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); BasicProperties props = delivery.getProperties(); BasicProperties replyProps = new AMQP.BasicProperties.Builder(). correlationId(props.getCorrelationId()).build(); String message = new String(delivery.getBody(), "UTF-8"); int n = Integer.parseInt(message); System.out.println("RPCServer fib(" + message + ")"); String response = "" + fib(n); channel.basicPublish( "", props.getReplyTo(), replyProps, response.getBytes()); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } }}
客户端代码:
public class RPCClient { private Connection connection; private Channel channel; private String requestQueueName = "rpc_queue"; private String replyQueueName; private QueueingConsumer consumer; public RPCClient() throws IOException, TimeoutException { ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); connection = factory.newConnection(); channel = connection.createChannel(); replyQueueName = channel.queueDeclare().getQueue(); consumer = new QueueingConsumer(channel); channel.basicConsume(replyQueueName, true, consumer); } public String call(String message) throws IOException, InterruptedException { String response; String corrID = UUID.randomUUID().toString(); AMQP.BasicProperties props = new AMQP.BasicProperties().builder() .correlationId(corrID).replyTo(replyQueueName).build(); channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8")); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); if (delivery.getProperties().getCorrelationId().equals(corrID)) { response = new String(delivery.getBody(), "UTF-8"); break; } } return response; } public void close() throws Exception { connection.close(); } public static void main(String[] args) throws Exception { RPCClient rpcClient = null; String response; try { rpcClient = new RPCClient(); System.out.println("RPCClient Requesting fib(20)"); response = rpcClient.call("20"); System.out.println("RPCClient Got '" + response + "'"); } catch (Exception e) { e.printStackTrace(); } finally { if (rpcClient != null) { rpcClient.close(); } } }}
注释:
如果需要远程计算机上运行一个函数,等待结果。这就是一个不同的故事了,这种模式通常被称为远程过程调用或者RPC。
服务器代码解读
1:建立连接,通道,队列
2:我们可能运行多个服务器进程,为了分散负载服务器压力,我们设置channel.basicQos(1);
3:我们用basicconsume访问队列。然后进入循环,在其中我们等待请求消息并处理消息然后发送响应。
客户端代码解读
1:建立一个连接和通道,并声明了一个唯一的“回调”队列的答复
2:我们订阅回调队列,这样就可以得到RPC的响应
3:定义一个call方法用于发送当前的回调请求
4:生成一个唯一的correlationid,然后通过while循环来捕获合适的回应
5:我们请求信息,发送2个属性,replyTo 和correlationId
6:然后就是等待直到有合适的回应到达
7:while循环是做一个非常简单的工作,对于每一个响应消息,它检查是否有correlationid然后进行匹配。然后是就进行响应。
8:最后把响应返回到客户端。
Message属性:
AMQP协议一共预定义了14个属性,但是大多数属性很少使用,下面几个可能用的比较多
deliveryMode:有2个值,一个是持久,另一个表示短暂(第二篇说过)
contentType:内容类型:用来描述编码的MIME类型。例如,经常使用JSON编码是将此属性设置为一个很好的做法:application/json。
replyTo:经常使用的是回调队列的名字
correlationid:RPC响应请求的相关应用
Correlation Id
在队列上接收到一个响应,但它并不清楚响应属于哪一个,当我们使用CorrelationId属性的时候,我们就可以将它设置为每个请求的唯一值,稍后当我们在回调队列中接收消息的时候,我们会看到这个属性,如果我们看到一个未知的CorrelationId,我们就可以安全地忽略信息-它不属于我们的请求。为什么我们应该忽略未知的消息在回调队列中,而不是失败的错误?这是由于服务器端的一个竞争条件的可能性。比如还未发送了一个确认信息给请求,但是此时RPC服务器挂了。如果这种情况发生,将再次重启RPC服务器处理请求。这就是为什么在客户端必须处理重复的反应。
rpc工作方式:
1:当客户端启动时,它创建一个匿名的独占回调队列。
2:对于rpc请求,客户端发送2个属性,一个是replyTo设置回调队列,另一是correlationId为每个队列设置唯一值
3:请求被发送到一个rpc_queue队列中
4:rpc服务器是等待队列的请求,当收到一个请求的时候,他就把消息返回的结果返回给客户端,使请求结束。
5:客户端等待回调队列上的数据,当消息出现的时候,他检查correlationId,如果它和从请求返回的值匹配,就进行响应。
四、HTML页面与RabbitMQ进行交互
1、安装插件
在控制台内输入:
启动stomp有关的一系列插件
rabbitmq-plugins enable rabbitmq_management rabbitmq_web_stomp rabbitmq_stomp rabbitmq_web_stomp_examples
查看已启动了哪些RabbitMQ插件
rabbitmq-plugins list
重启rabbitmq
services restart rabbitmq
可以在15670端口访问web-stomp-examples,RabbitMQ运行在15672端口,stomp服务运行在15674端口。
2、引入js文件
3、HTML代码
queue_1.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>queue_1.html</title> <style> .box { width: 440px; float: left; margin: 0 20px 0 20px; } .box div, .box input { border: 1px solid; -moz-border-radius: 4px; border-radius: 4px; width: 100%; padding: 5px; margin: 3px 0 10px 0; } .box div { border-color: grey; height: 300px; overflow: auto; } div code { display: block; } #first div code { -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #eee; margin-bottom: 5px; } #second div { font-size: 0.8em; } body { font-family: "Arial"; color: #444; } h1, h2 { color: #f60; font-weight: normal; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; margin: 0; } a { color: #f60; border: 1px solid #fda; background: #fff0e0; border-radius: 3px; -moz-border-radius: 3px; padding: 2px; text-decoration: none; /* font-weight: bold; */ } ul.menu { list-style-type: none; padding: 0; margin: 0; } ul.menu li { padding: 5px 0; } </style></head><body><div id="first" class="box"> <h2>接收</h2> <div></div> <form><input autocomplete="off" value="" placeholder="输入要发送的消息"/></form></div><div id="second" class="box"> <h2>日志</h2> <div></div></div><script type="text/javascript" src="../js/jquery-1.8.3.js"></script><script type="text/javascript" src="../js/sockjs-0.3.min.js"></script><script type="text/javascript" src="../js/stomp.js"></script><script type="text/javascript"> $(document).ready(function () { var has_had_focus = false; var pipe = function (el_name, send) { var div = $(el_name + ' div'); var inp = $(el_name + ' input'); var form = $(el_name + ' form'); var print = function (m, p) { p = (p === undefined) ? '' : JSON.stringify(p); div.append($("<code>").text(m + ' ' + p)); div.scrollTop(div.scrollTop() + 10000); }; if (send) { form.submit(function () { send(inp.val()); inp.val(''); return false; }); } return print; }; // Stomp.js boilerplate var client = Stomp.client('ws://192.168.1.191:15674/ws'); client.debug = pipe('#second'); var print_first = pipe('#first', function (data) { client.send('/exchange/ex_test/Q.3', {}, data); }); var on_connect = function (x) { console.log(x.toString()); var id = client.subscribe("/exchange/ex_test/Q.*", function (d) { print_first(d.body); }); console.log("\n" + id.toString()); }; var on_error = function (c) { alert("error" + c.toString()); }; client.connect('admin', 'admin', on_connect, on_error, '/'); $('#first input').focus(function () { if (!has_had_focus) { has_had_focus = true; $(this).val(""); } }); });</script></body></html>
queue_2.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>queue_2.html</title> <style> .box { width: 440px; float: left; margin: 0 20px 0 20px; } .box div, .box input { border: 1px solid; -moz-border-radius: 4px; border-radius: 4px; width: 100%; padding: 5px; margin: 3px 0 10px 0; } .box div { border-color: grey; height: 300px; overflow: auto; } div code { display: block; } #first div code { -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #eee; margin-bottom: 5px; } #second div { font-size: 0.8em; } body { font-family: "Arial"; color: #444; } h1, h2 { color: #f60; font-weight: normal; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; margin: 0; } a { color: #f60; border: 1px solid #fda; background: #fff0e0; border-radius: 3px; -moz-border-radius: 3px; padding: 2px; text-decoration: none; /* font-weight: bold; */ } ul.menu { list-style-type: none; padding: 0; margin: 0; } ul.menu li { padding: 5px 0; } </style></head><body><div id="first" class="box"> <h2>接收</h2> <div></div> <form><input autocomplete="off" value="" placeholder="输入要发送的消息"/></form></div><div id="second" class="box"> <h2>日志</h2> <div></div></div><script type="text/javascript" src="../js/jquery-1.8.3.js"></script><script type="text/javascript" src="../js/sockjs-0.3.min.js"></script><script type="text/javascript" src="../js/stomp.js"></script><script type="text/javascript"> $(document).ready(function () { var has_had_focus = false; var pipe = function (el_name, send) { var div = $(el_name + ' div'); var inp = $(el_name + ' input'); var form = $(el_name + ' form'); var print = function (m, p) { p = (p === undefined) ? '' : JSON.stringify(p); div.append($("<code>").text(m + ' ' + p)); div.scrollTop(div.scrollTop() + 10000); }; if (send) { form.submit(function () { send(inp.val()); inp.val(''); return false; }); } return print; }; // Stomp.js boilerplate var client = Stomp.client('ws://192.168.1.191:15674/ws'); client.debug = pipe('#second'); var h1 = {'userId': 'queue_2'}; var print_first = pipe('#first', function (data) { client.send('/exchange/ex_test/P2', {}, data); }); var on_connect = function (x) { console.log(x.toString()); var id = client.subscribe("/exchange/ex_test/P2", function (d) { print_first(d.body); }); console.log("\n" + id.toString()); }; var on_error = function (c) { alert("error" + c.toString()); }; client.connect('admin', 'admin', on_connect, on_error, '/'); $('#first input').focus(function () { if (!has_had_focus) { has_had_focus = true; $(this).val(""); } }); });</script></body></html>
queue_3.html
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>queue_3.html</title> <style> .box { width: 440px; float: left; margin: 0 20px 0 20px; } .box div, .box input { border: 1px solid; -moz-border-radius: 4px; border-radius: 4px; width: 100%; padding: 5px; margin: 3px 0 10px 0; } .box div { border-color: grey; height: 300px; overflow: auto; } div code { display: block; } #first div code { -moz-border-radius: 2px; border-radius: 2px; border: 1px solid #eee; margin-bottom: 5px; } #second div { font-size: 0.8em; } body { font-family: "Arial"; color: #444; } h1, h2 { color: #f60; font-weight: normal; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; margin: 0; } a { color: #f60; border: 1px solid #fda; background: #fff0e0; border-radius: 3px; -moz-border-radius: 3px; padding: 2px; text-decoration: none; /* font-weight: bold; */ } ul.menu { list-style-type: none; padding: 0; margin: 0; } ul.menu li { padding: 5px 0; } </style></head><body><div id="first" class="box"> <h2>接收</h2> <div></div> <form><input autocomplete="off" value="" placeholder="输入要发送的消息"/></form></div><div id="second" class="box"> <h2>日志</h2> <div></div></div><script type="text/javascript" src="../js/jquery-1.8.3.js"></script><script type="text/javascript" src="../js/sockjs-0.3.min.js"></script><script type="text/javascript" src="../js/stomp.js"></script><script type="text/javascript"> $(document).ready(function () { var has_had_focus = false; var pipe = function (el_name, send) { var div = $(el_name + ' div'); var inp = $(el_name + ' input'); var form = $(el_name + ' form'); var print = function (m, p) { p = (p === undefined) ? '' : JSON.stringify(p); div.append($("<code>").text(m + ' ' + p)); div.scrollTop(div.scrollTop() + 10000); }; if (send) { form.submit(function () { send(inp.val()); inp.val(''); return false; }); } return print; }; // Stomp.js boilerplate var client = Stomp.client('ws://192.168.1.191:15674/ws'); client.debug = pipe('#second'); var h1 = {'userId': 'queue_3'}; var print_first = pipe('#first', function (data) { client.send('/exchange/ex_test/Q.1', {}, data); }); var on_connect = function (x) { console.log(x.toString()); var id = client.subscribe("/exchange/ex_test/Q.*", function (d) { print_first(d.body); }); console.log("\n" + id.toString()); }; var on_error = function (c) { alert("error" + c.toString()); }; client.connect('admin', 'admin', on_connect, on_error, '/'); $('#first input').focus(function () { if (!has_had_focus) { has_had_focus = true; $(this).val(""); } }); });</script></body></html>
注释
queue_1和queue_2私聊,queue_3收不到消息
js订阅/发送总结
/queue/queuename:使用默认转发器订阅/发布消息,默认由stomp自动创建一个持久化队列
/amq/queue/queuename:与/queue/queuename的区别在于队列不由stomp自动进行创建,队列不存在失败
/topic/routing_key:通过amq.topic转发器订阅/发布消息,订阅时默认创建一个临时队列,通过routing_key与topic进行绑定
/temp-queue/xxx:创建一个临时队列(只能在headers中的属性reply-to中使用),可用于发送消息后通过临时队列接收回复消息,接收通过client.onreceive
/exchange/exchangename/[routing_key]:通过转发器订阅/发布消息,转发器需要手动创建
client.subscribe(destination,callback,headers):订阅消息
client.send(destination,headers,body):发布消息
client.unsubscribe(id):取消订阅,id为订阅时返回的编号
client.onreceive:默认接收回调从临时队列获取消息
五、参考内容
官网:http://www.rabbitmq.com/
教程:http://www.rabbitmq.com/getstarted.html
一些好的中文教程:
RabbitMQ安装和各种模式介绍:http://www.cnblogs.com/LipeiNet/p/5973061.html
js连接RabbitMQ达到实时消息推送:https://www.cnblogs.com/puyangsky/p/6666624.html
RabbitMQ基础概念介绍:http://blog.csdn.net/whycold/article/details/41119807
RabbitMQ使用场景练习:STOMP plugin:http://blog.csdn.net/azhegps/article/details/53815283
通过WebSocket STOMP(js连接RabbitMQ)英文文档:http://jmesnil.net/stomp-websocket/doc/
中文翻译:https://segmentfault.com/a/1190000006617344
RabbitMQ STOMP Adapter(js连接RabbitMQ):http://www.rabbitmq.com/stomp.html
- RabbitMQ总结
- RabbitMQ总结
- rabbitmq-server 总结
- RabbitMQ的Heartbeat总结
- RabbitMQ使用总结
- rabbitmq学习总结
- rabbitmq安装总结
- rabbitmq实战总结
- RabbitMQ spring 使用总结
- rabbitmq 学习总结
- RabbitMQ学习总结
- RabbitMQ的学习总结
- RabbitMQ的使用总结
- RabbitMQ 知识总结
- 编写RabbitMQ总结
- RabbitMQ spring 使用总结
- RabbitMQ教程总结
- RabbitMQ集群相关理论总结
- Android-TextView好用第三方库记录
- prometheus监控方案
- AutoEncoder详解
- 选择结构程序设计
- 其中常见的有RAID0、RAID0+1、RAID1、RAID5这四种
- RabbitMQ总结
- Fork/Join框架
- Linux 安装Nginx详细图解教程
- Android 源码编译JDK的切换
- Rediis 指令操作说明文档地址
- 正则表达式测试工具
- Java8中流的性能
- 基于tensorflow的简单BP神经网络的结构搭建
- react-native地图应用,申请sdk key流程