RabbitMQ之Exchange(五)

Exchange总共有4种类型:direct, topic, fanout and headers,这里主要介绍前三种。

direct转发规则是根据routing key(由消费者来绑定,可以简单理解为消费者感兴趣的事件,如新闻中的:娱乐,社会,汽车,国际),下图中routing key为info,waring,error

生产者发送消息的时候,会指定消息对应的routing key(也就是指定消息的类型,如新闻中的:娱乐,那么所有关注娱乐新闻的人都能看到)

 

topic也是根据binding key,但是多了" *,# "两个通配符(*匹配单个.号分开的单词,#匹配多个)

发送quick.orange.rabbit或者lazy.orange.elephant,C1,C2均能收到

发送quick.orange.fox只有C1能收到,发送lazy.brown.fox只有C2能收到

lazy.pink.rabbit只会向C2发送一次

quick.brown.fox不被匹配,所以直接丢弃。

fanout转发规则是广播,即转发直连到这个exchange上的queue

 

 

Exchange-direct

Producer

 1 package direct;
 2 
 3 import java.io.IOException;
 4 
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.Connection;
 7 import com.rabbitmq.client.ConnectionFactory;
 8 
 9 //direct为exchange的一种模式,主要是按照binding key来决定将消息放到哪些队列里
10 //相对于广播模式fanout就是多了一层routingKey的匹配,向匹配的queue广播
11 //大致流程时 producer -> exchange -> queue -> consumer
12 //producer可以选择像哪个exchange中发送数据,同时可以指定routingKey,也可以显示声明queue
13 //exchange则根据当前的模式决定将消息放到其关联的哪些queue里,广播就是全部,direct就是根据routingKey匹配,routingKey是exchange和queue之间的识别符
14 //queue就是用来具体的存放数据的队列,其内的同一条消息只会发送给一个consumer(假设多个人绑定同一个queue),可以用来负载均衡
15 public class Send {
16     private static final String EXCHANGE_NAME = "direct_logs";
17     public static void main(String[] args) {
18         // TODO Auto-generated method stub
19         foo();
20     }
21     
22     private static void foo() {
23         try {
24             ConnectionFactory factory = new ConnectionFactory();
25             factory.setHost("localhost");
26             Connection connection = factory.newConnection();
27             Channel channel = connection.createChannel();
28             //声明一个直连的exchange
29             channel.exchangeDeclare(EXCHANGE_NAME, "direct");
30             for (int i = 0; i < 3; i++) {
31                 //设定了routing key为info1,info2,info3
32                 String serverity = "info"+i;
33                 String message = "info test"+i;
34                 //在名为direct_logs的Exchange上绑定了三个routing key,并发送消息
35                 channel.basicPublish(EXCHANGE_NAME, serverity, null, message.getBytes());
36                 System.out.println("Send:"+message);
37             }
38             channel.close();
39             connection.close();
40         } catch (Exception e) {
41             // TODO Auto-generated catch block
42             e.printStackTrace();
43         }
44         
45     }
46 
47 }

 

Consumer

package direct;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;

public class Recv {
    private static final String EXCHANGE_NAME = "direct_logs";
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        args = new String[]{"info1","info2"};
        foo(args);
    }

    private static void foo(String[] args) {
        try {
            ConnectionFactory factory = new ConnectionFactory();
            factory.setHost("localhost");
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            //声明一个直连的exchange,direct的意思是根据routing key进行转发
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            String queueName = channel.queueDeclare().getQueue();
            
            //这里将同一个queue绑定了info1,info2两个routing key(binding key),所以可以收到info1,info2的消息
            for(String severity : args) {
                channel.queueBind(queueName, EXCHANGE_NAME, severity);
                System.out.println("routingKey:"+severity);
            }
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                        byte[] body) throws IOException {
                    String message = new String(body,"UTF-8");
                    System.out.println(queueName+" Recv "+message);
                }
            };
            channel.basicConsume(queueName, true, consumer);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}

运行结果

Procuder

Send:info test0
Send:info test1
Send:info test2

Consumer

routingKey:info1
routingKey:info2
amq.gen-tMcTNq9K_Zp3tNLCoNFqEg Recv info test1
amq.gen-tMcTNq9K_Zp3tNLCoNFqEg Recv info test2

备注:这里需要先运行Consumer,然后运行Producer。因为Producer没有绑定Queue,所以如果Producer先运行,那么没有queue来存放数据,导致数据直接丢失。

如下图:

 

 

 

运行结果图解:(与上述代码操作上有些许差别)

下图构造同上述代码,图中圆代表生产者,六边形代表Exchange,长方形代表queue,锯齿形代表消费者(下图中的两个是同一个消费者,队列也是同一个队列,当然可以是不同的队列,这里是为了符合代码)

下面的info1,info2,info3正好代表三个routing key(其中info3没有绑定queue,这里是为了画图方便)

连续发送两条routing key为info1的消息就是下图这种效果

  

Exchange-topic

 

Producer

 1 package topic;
 2 
 3 import java.io.IOException;
 4 
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.Connection;
 7 import com.rabbitmq.client.ConnectionFactory;
 8 
 9 //topic与direct的区别是,topic绑定的routingKey支持通配符
10 public class Send {
11     private static final String EXCHANGE_NAME = "topic_logs";
12     public static void main(String[] args) {
13         // TODO Auto-generated method stub
14         foo();
15     }
16     
17     private static void foo() {
18         try {
19             ConnectionFactory factory = new ConnectionFactory();
20             factory.setHost("localhost");
21             Connection connection = factory.newConnection();
22             Channel channel = connection.createChannel();
23             channel.exchangeDeclare(EXCHANGE_NAME, "topic");
24             for (int i = 0; i < 3; i++) {
25                 String serverity = "info.warn"+i;
26                 String message = "info.warn"+i;
27                 channel.basicPublish(EXCHANGE_NAME, serverity, null, message.getBytes());
28                 System.out.println("Send:"+message);
29             }
30             channel.close();
31             connection.close();
32         } catch (Exception e) {
33             // TODO Auto-generated catch block
34             e.printStackTrace();
35         }
36         
37     }
38 
39 }

 

Consumer

 1 package topic;
 2 
 3 import java.io.IOException;
 4 
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.Connection;
 7 import com.rabbitmq.client.ConnectionFactory;
 8 import com.rabbitmq.client.Consumer;
 9 import com.rabbitmq.client.DefaultConsumer;
10 import com.rabbitmq.client.Envelope;
11 import com.rabbitmq.client.AMQP.BasicProperties;
12 
13 public class Recv {
14     private static final String EXCHANGE_NAME = "topic_logs";
15     public static void main(String[] args) {
16         // TODO Auto-generated method stub
17         //args = new String[]{"#"};     //可以用来匹配所有的topic,#是指匹配0个或多个关键字
18         //args = new String[]{"*"};     //只能匹配一个一个关键字,这里的关键字指的的以.分割的,如:send端topic为"info warn1"可以匹配,为"info.warn1"无法匹配
19         args = new String[]{"info.*"};
20         foo(args);
21     }
22 
23     private static void foo(String[] args) {
24         try {
25             ConnectionFactory factory = new ConnectionFactory();
26             factory.setHost("localhost");
27             Connection connection = factory.newConnection();
28             Channel channel = connection.createChannel();
29             channel.exchangeDeclare(EXCHANGE_NAME, "topic");
30             String queueName = channel.queueDeclare().getQueue();
31             
32             for(String severity : args) {
33                 channel.queueBind(queueName, EXCHANGE_NAME, severity);
34                 System.out.println("routingKey:"+severity);
35             }
36             Consumer consumer = new DefaultConsumer(channel) {
37                 @Override
38                 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
39                         byte[] body) throws IOException {
40                     String message = new String(body,"UTF-8");
41                     System.out.println(queueName+" Recv: "+message);
42                 }
43             };
44             channel.basicConsume(queueName, true, consumer);
45         } catch (Exception e) {
46             // TODO Auto-generated catch block
47             e.printStackTrace();
48         }
49         
50     }
51 }

运行结果:

  Procuder

Send:info.warn0
Send:info.warn1
Send:info.warn2

Consumer

routingKey:info.*
amq.gen-n5I6CxBz00azD2KPnq7GcA Recv: info.warn0
amq.gen-n5I6CxBz00azD2KPnq7GcA Recv: info.warn1
amq.gen-n5I6CxBz00azD2KPnq7GcA Recv: info.warn2

备注:这里需要先运行Consumer,然后运行Producer。因为Producer没有绑定Queue,所以如果Producer先运行,那么没有queue来存放数据,导致数据直接丢失。

图解:

这里由于工具问题,topic未能正常工作,所以使用广播模式绘制下图(导致看不到routing key,广播fanout不关心routing key),但是结果符合代码实际运行。

 

 

Exchange-topic

 Producer

 1 package PubSub;
 2 
 3 import com.rabbitmq.client.Channel;
 4 import com.rabbitmq.client.Connection;
 5 import com.rabbitmq.client.ConnectionFactory;
 6 
 7 public class Send1 {
 8     private static final String QUEUE_NAME = "workqueue";
 9     private static final String EXCHANGE_NAME = "ex_log";
10     public static void main(String[] args) {
11         // TODO Auto-generated method stub
12         foo();
13     }
14 
15     /**
16      *  测试Exchange的fanout类型:即广播模式,不关心queue和exchange的binding key。
17      *  本例测试场景是发送端发送日志消息到mq,消费者打印实时日志
18      *  所以这里没有为exchange绑定queue,这意味着如果生产者先发送数据,那么消息会丢失,消费者只能收到其起来之后生产者发送的消息
19      *  因为只有queue才能存数据,本例中只有消费者声明了queue(绑定的是随机queue,生命周期同消费者)
20      *  这样做的好处是,一个消费者一个queue,消费者挂了queue也会自动删除
21      */
22     private static void foo() {
23         try{
24             ConnectionFactory factory = new ConnectionFactory();
25             factory.setHost("localhost");
26             Connection connection = factory.newConnection();
27             Channel channel = connection.createChannel();
28             
29             channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
30             String message = "this is a log";
31             //下面第一个参数时exchange的名字,之前为空"",表示为匿名,只要对端也为空""即可
32             channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
33             System.out.println("Send:"+message);
34             channel.close();
35             connection.close();
36         } catch(Exception e) {
37             e.printStackTrace();
38         }
39     }
40 }

 

Consumer

 1 package PubSub;
 2 
 3 import java.io.IOException;
 4 
 5 import com.rabbitmq.client.Channel;
 6 import com.rabbitmq.client.Connection;
 7 import com.rabbitmq.client.ConnectionFactory;
 8 import com.rabbitmq.client.DefaultConsumer;
 9 import com.rabbitmq.client.Envelope;
10 import com.rabbitmq.client.AMQP.BasicProperties;
11 
12 public class Recv1 {
13     private static final String QUEUE_NAME = "workqueue";
14     private static final String EXCHANGE_NAME = "ex_log";
15     public static void main(String[] args) {
16         foo();
17     }
18 
19     private static void foo() {
20         try {
21             ConnectionFactory factory = new ConnectionFactory();
22             factory.setHost("localhost");
23             Connection connection = factory.newConnection();
24             Channel channel = connection.createChannel();
25             channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
26             //创建一个随机的queue(非持久化、独占的、随着client session断开而自动删除的)
27             String queueName = channel.queueDeclare().getQueue();
28             channel.queueBind(queueName, EXCHANGE_NAME, "");
29             DefaultConsumer consumer = new DefaultConsumer(channel){
30                 @Override
31                 public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
32                         byte[] body) throws IOException {
33                     String message = new String(body,"UTF-8");
34                     System.out.println("Recv:"+message);
35                 }
36             };
37             //只能接收到绑定道queue之后producer生产的数据,如果生产者生产数据时没有queue绑定到exchange,那么消息自动丢弃
38             channel.basicConsume(queueName, true, consumer);
39         } catch (Exception e) {
40             e.printStackTrace();
41         }
42     }
43 }

运行结果:

  Procuder

    Send:this is a log

  Consumer

    Recv:this is a log

   备注:先运行Consumer再运行Producer才能收到,因为这里Producer没有声明queue

运行图解

注意下面有个命名queue和随机queue的区别(个人理解命名,官方可能没有这两个概念)

  命名queue指的是Prodocer和Consumer都会去声明的Queue,即双方约定好的

  随机queue指Producer没有去声明这个queue,其也不知道这个queue的存在,由消费者动态声明,用以接收数据,好处是这个queue的生命周期同consumer(上述代码用的就是随机queue)

 

posted @ 2018-12-01 15:30  exact  阅读(189)  评论(0编辑  收藏  举报