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

 

 

原创粉丝点击