消息中间件(八)——RabbitMQ的工作模式(Routing)

1、概述

Routing是路由模式,它有如下特点:

1、每个消费者监听自己的队列,并且设置routingkey(路由key)。

2、生产者将消息发给交换机,交换机根据routingkey来转发消息到指定的队列。

该图中,指明了Routing模式的工作原理,X表示的交换机,交换机类型要设置为direct,error,info等都是routingKey。

2、代码实现

2.1 生产者代码

注意:声明exchange_routing_inform交换机;声明两个队列并且绑定到此交换机,绑定时需要指定routingkey;发送消息时需要指定routingkey 。

package com.zdw.prodcure;

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.text.SimpleDateFormat;
import java.util.Date;

/**
 * Create By zdw on 2019/9/17
 * 这是Routing模式的生成者
 */
public class RoutingProdcure {
    private static final String EXCHANGE_DIRECT_NAME="exchange_direct_inform";//定义交换机名称
    private static final String QUEUE_MSG="queue_msg_inform";//短信队列名称
    private static final String QUEUE_EMAIL="queue_email_inform";//邮件队列名称

    private static final String ROUTING_MSG="routing_msg";//短信队列的routingKey
    private static final String ROUTING_EMAIL="routing_email";//邮件队列的routingKey
    private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey

    public static void main(String[] args) {
        Connection connection = null;//定义连接对象
        Channel channel = null;//定义通道对象

        try {
            //创建连接工厂对象
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务

            connection = connectionFactory.newConnection();//得到与RabbitMQ服务的TCP连接

            channel = connection.createChannel();//从连接中得到与交换机的通道,一个连接可以创建多个通道,一个通道代表一个会话任务

            /**
             * 声明两个队列,String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
             * 参数明细:
             *  1、String queue:队列名称
             *  2、boolean durable:是否持久化,如果持久化,mq重启后队列还在
             *  3、exclusive :是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
             *  4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
             *  5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
             */
            channel.queueDeclare(QUEUE_MSG,true,false,false,null);
            channel.queueDeclare(QUEUE_EMAIL,true,false,false,null);

            /**
             * 声明交换机:
             * 参数:String exchange, BuiltinExchangeType type
             * 1、exchange:交换机名称
             * 2、type:交换机类型,主要有以下几种类型:
             *      fanout:对应的是发布订阅模式
             *      direct:对应的是routing模式
             *      topic:对应的是ropic模式
             *      headers 对应的是headers工作模式
             */
            channel.exchangeDeclare(EXCHANGE_DIRECT_NAME, BuiltinExchangeType.DIRECT);

            /**
             * 队列绑定到交换机,并且指定路由key
             * 参数:String queue, String exchange, String routingKey
             * 1、队列名称 2、交换机名称  3、路由key,作用是交换机根据路由key的值将消息转发到指定的队列中
             */
            channel.queueBind(QUEUE_MSG,EXCHANGE_DIRECT_NAME,ROUTING_MSG);
            channel.queueBind(QUEUE_EMAIL,EXCHANGE_DIRECT_NAME,ROUTING_EMAIL);
            //绑定到队列,指定路由为共同的路由key
            channel.queueBind(QUEUE_MSG,EXCHANGE_DIRECT_NAME,ROUTING_COMMON);
            channel.queueBind(QUEUE_EMAIL,EXCHANGE_DIRECT_NAME,ROUTING_COMMON);

            /**
             * 发送消息:String exchange, String routingKey, BasicProperties props, byte[] body
             * 参数明细:
             * 1、exchange 交换机 如果不指定将使用mq的默认交换机(设置为"")
             * 2、routingKey 路由key 交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
             * 3、props 消息的属性
             * 4、body  消息主体
             * 这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定那个默认的交换机,但是不能显示绑定或解除绑定
             * 默认的交换机,routingKey等于队列名称
             */
            for(int i=0;i<5;i++) {
                //定义发送的消息内容
                String message = "send inform msg to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
                //发送消息到短信队列,这里指定了路由key是ROUTING_MSG,所以只有短信队列可以接收该消息
                channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_MSG, null, message.getBytes());
                System.out.println("Send message to msg MQ:" + message);
            }

            for(int i=0;i<4;i++) {
                //定义发送的消息内容
                String message = "send inform email to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
                //发送消息到短信队列,这里指定了路由key是ROUTING_EMAIL,所以只有邮件队列可以接收该消息
                channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_EMAIL, null, message.getBytes());
                System.out.println("Send message to email MQ:" + message);
            }
            //发送消息时指定共同的路由key,那么两个队列都能接收到消息
            for(int i=0;i<3;i++) {
                //定义发送的消息内容
                String message = "send inform email and msg to user:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS").format(new Date());
                //发送消息到短信队列,这里指定了路由key是ROUTING_COMMON,所以短信和邮件队列都可以接收该消息
                channel.basicPublish(EXCHANGE_DIRECT_NAME, ROUTING_COMMON, null, message.getBytes());
                System.out.println("Send message to email and msg MQ:" + message);
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(channel!=null){
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

生产者代码中,声明了一个交换机,两个队列,三个路由key,两个队列都绑定到了交换机并且指定了各自特定的路由key和共同的路由key。发送消息的时候,第一个循环中,发送消息的时候指定了路由key是ROUTING_MSG,因为短信队列QUEUE_MSG绑定到交换机的时候指定了路由key是ROUTING_MSG,所以消息只会发送到QUEUE_MSG队列中;第二个循环中,发送的消息的时候指定了路由key是ROUTING_EMAIL,因为邮件队列QUEUE_EMAIL绑定到交换机的时候指定了路由ROUTING_EMAIL,所以消息只会发送到QUEUE_EMAIL队列中;第三个循环中,发送消息的时候指定了路由key是ROUTING_COMMON,因为短信队列QUEUE_MSG和邮件队列QUEUE_EMAIL都绑定到交换机的时候指定了路由ROUTING_COMMON,所以消息会发送到两个队列中。

2.2 消费者代码

2.2.1 短信消费者

package com.zdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * Create By zdw on 2019/9/18
 * 短信消费者
 */
public class RoutingConsumerMsg {
    private static final String EXCHANGE_NAME="exchange_direct_inform";//定义交换机名称,一定要与生产者中相同
    private static final String QUEUE_MSG="queue_msg_inform";//短信队列名称,一定要与生产者中相同

    private static final String ROUTING_MSG="routing_msg";//短信队列的routingKey
    private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey

    public static void main(String[] args) {
        Connection connection = null;//定义连接对象
        Channel channel = null;//定义通道对象

        try {
            //创建连接工厂对象
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务

            connection = connectionFactory.newConnection();//获取与RabbitMQ的tcp连接

            channel = connection.createChannel();//得到连接通道,一个连接可以创建多个通道,每个通道相当于一个会话任务

            channel.queueDeclare(QUEUE_MSG,true,false,false,null);//声明队列

            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//声明交换机

            channel.queueBind(QUEUE_MSG,EXCHANGE_NAME,ROUTING_MSG);//绑定队列到交换机,指定路由key
            channel.queueBind(QUEUE_MSG,EXCHANGE_NAME,ROUTING_COMMON);//绑定队列到交换机,指定路由key

            //定义消费消息的方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 消费者接收消息后执行该方法
                 * @param consumerTag  消费者标签,用来标识消费者的,在监听队列时(channel.basicConsume)设置
                 * @param envelope     信封,通过envelope  消息包的内容,可从中获取消息id,消息routingkey,交换机,
                 *                     消息和重传标志(收到消息失败后是否需要重新发送)
                 * @param properties   消息属性
                 * @param body         消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String exchange = envelope.getExchange();
                    String routingKey = envelope.getRoutingKey();
                    long msgId = envelope.getDeliveryTag();//消息id
                    String msg = new String(body,"utf-8");
                    System.out.println(exchange+"//"+routingKey+"//"+msgId+"//"+msg);
                }
            };

            //监听队列
            /** 参数:String queue, boolean autoAck, Consumer callback
             *  1、queue 队列名称
             *  2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
             *  3、callback,消费方法,当消费者接收到消息要执行的方法
             */
            channel.basicConsume(QUEUE_MSG,true,defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2.2.2 邮件消费者

package com.zdw.consumer;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * Create By zdw on 2019/9/18
 * 邮件消费者
 */
public class RoutingConsumerEmail {
    private static final String EXCHANGE_NAME="exchange_direct_inform";//定义交换机名称,一定要与生产者中相同
    private static final String QUEUE_EMAIL="queue_email_inform";//邮件队列名称,一定要与生产者中相同

    private static final String ROUTING_EMAIL="routing_email";//邮件队列的routingKey
    private static final String ROUTING_COMMON="routing_common";//短信队列和邮件队列共同的routingKey

    public static void main(String[] args) {
        Connection connection = null;//定义连接对象
        Channel channel = null;//定义通道对象

        try {
            //创建连接工厂对象
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            connectionFactory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务

            connection = connectionFactory.newConnection();//获取与RabbitMQ的tcp连接

            channel = connection.createChannel();//得到连接通道,一个连接可以创建多个通道,每个通道相当于一个会话任务

            channel.queueDeclare(QUEUE_EMAIL,true,false,false,null);//声明队列

            channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//声明交换机

            channel.queueBind(QUEUE_EMAIL,EXCHANGE_NAME,ROUTING_EMAIL);//绑定队列到交换机,指定路由key
            channel.queueBind(QUEUE_EMAIL,EXCHANGE_NAME,ROUTING_COMMON);//绑定队列到交换机,指定路由key

            //定义消费消息的方法
            DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                /**
                 * 消费者接收消息后执行该方法
                 * @param consumerTag  消费者标签,用来标识消费者的,在监听队列时(channel.basicConsume)设置
                 * @param envelope     信封,通过envelope  消息包的内容,可从中获取消息id,消息routingkey,交换机,
                 *                     消息和重传标志(收到消息失败后是否需要重新发送)
                 * @param properties   消息属性
                 * @param body         消息内容
                 * @throws IOException
                 */
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String exchange = envelope.getExchange();
                    String routingKey = envelope.getRoutingKey();
                    long msgId = envelope.getDeliveryTag();//消息id
                    String msg = new String(body,"utf-8");
                    System.out.println(exchange+"//"+routingKey+"//"+msgId+"//"+msg);
                }
            };

            //监听队列
            /** 参数:String queue, boolean autoAck, Consumer callback
             *  1、queue 队列名称
             *  2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
             *  3、callback,消费方法,当消费者接收到消息要执行的方法
             */
            channel.basicConsume(QUEUE_EMAIL,true,defaultConsumer);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

使用生产者发送若干条消息,交换机根据routingkey转发消息到指定的队列。

我们发现生产者只把消息转发到指定路由key的队列中,消费者也只能消费指定路由key的消息

 

注意:上面的生产者和消费者都声明了队列,声明了交换机,进行了队列绑定到交换机的操作。这样做是为了无论先启动的生产者还是先启动消费者,都不会报错。其实,只要有一方进行声明就行了的。

 

代码下载:https://download.csdn.net/download/zengdongwen/11784368

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值