Rabbit MQ - tutorial 3 publish/subscribe, sending messages to many consumers at once

前面的两个例子,我们直接使用queue,实际上是通过默认的exchange来发送消息的。本例将演示使用exchange发布消息,并实现订阅。

基本概念

  • queue: A queue is a buffer that stores messages.
  • exchange
  • binding: A relationship between an exchange and a queue

exchange分类

  • direct exchange: a message goes to the queues whose binding key exactly matches the routing key of the message.Direct exchange由固定路由(通常是队列的名称)绑定。
  • topic exchange: 支持使用路由pattern(*和#通配符)绑定。
  • headers exchange:
  • fanout exchange: broadcasts all the messages it receives to all the queues it knows(Fanout exchange发布给所有绑定到它的队列,而不考虑任何路由密钥。)

注意:AMQP规范还要求所有broker都提供没有名称的“默认”direct exchange。 所有声明的队列将被绑定到这个默认的Exchange,并将其名称作为路由密钥。

Tutorial 3 - log订阅

(1) Producer

producer发布log(messsage),通过名为”logs”的exchange发布消息;然后consumer通过exchange订阅这些消息。exchange知道该把消息放到哪个queue里面,而这个又是通过binding把exchange和queue关联起来的。具体实现的步骤全部在下面代码注释中。

//EmitLog.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.Random;

//producer发送消息给exchange,exchange决定把消息放到哪个queue里面
public class EmitLog implements Runnable {
    //message index,用来查看多个consumer同时从broker取message时,消息是怎么被分派的
    private static Integer index = 0;

    //define name of the exchange
    private static final String EXCHANGE_NAME = "logs";
    //connection to the server(broker)
    private Connection rbtMqConn;
    //channel
    private Channel rbtMqChnl;

    //控制停止线程
    private boolean isStop = false;
    public void setIsStop(boolean stop){
        this.isStop = stop;
    }

    @Override
    public void run() {
        try{
            //1.创建一个connection链接到RabbitMQ服务器(connection为我们处理socket/协议/认证授权等)
            ConnectionFactory factory = new ConnectionFactory();
            //本例使用本机作为服务器;Consumer也从这个broker接收消息,也可以使用其它主机,比如172.16.21.111
            factory.setHost("localhost");
            rbtMqConn = factory.newConnection();

            //2.创建一个channel
            rbtMqChnl = rbtMqConn.createChannel();

            //3.声明exchange,名字为EXCHANGE_NAME,类型为fanout
            //这一步必须,因为不能发布到不存在的exchange是不允许的
            //通过rabbitmqctl list_exchanges -p <vhost>,查看vhost上的exchange
            rbtMqChnl.exchangeDeclare(EXCHANGE_NAME, "fanout");

            //如果没有queue绑定到exchange上,这些消息将会丢失,但这对本例来说没问题;
            //如果没有consumer正在监听,我们可以放心地丢弃消息。

            //send message per 3s
            while (!isStop){
                //message to send
                String message = getMessage(index++);
                //4.通过指定的exchange发布消息:
                rbtMqChnl.basicPublish(EXCHANGE_NAME, ""/*queue name*/, null, message.getBytes("UTF-8"));
                System.out.println(" [Producer] Sent '" + message + "'");
                Thread.sleep(3000);
            }

            //5.最后,使用完之后,关闭channel和connection;
            rbtMqChnl.close();
            rbtMqConn.close();
        }catch(Exception ex){
            System.out.println(ex.getMessage());
        }

        System.out.println(" [Producer] Send task stop");
    }

    /**
     * 生成随机消息用于发送给RabbitMQ服务器
     * */
    private static String getMessage(Integer index) {
        StringBuilder dotMsg = new StringBuilder("");
        dotMsg.append(String.format("[%d]", index));

        Random rand = new Random();
        Integer num = rand.nextInt(5) + 1;//[1, 5]
        for(Integer i=1; i <= num; ++i){
            dotMsg.append(".").append(i.toString());
        }

        return dotMsg.toString();
    }
}
//MyProducer.java
public class MyProducer {
    public static void main(String[] argv) throws Exception {
        EmitLog producer = new EmitLog();
        Thread thread = new Thread(producer);
        thread.start();
        //let the thread run 60 seconds
        Thread.sleep(60000);
        producer.setIsStop(true);
    }
}

(2) Consumer

具体实现的步骤,见下面代码注释。

//ConsumerReceiveLogs.java
import com.rabbitmq.client.*;
import java.io.IOException;

public class ConsumerReceiveLogs
{
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {
        //1.创建connection链接到RabbitMQ Server
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");//使用本机的RabbitMQ Server
        Connection connection = factory.newConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);

        //4.使用随机生成的消息队列
        //注意,当consumer退出连接时,该随机队列会自动删除
        String queueName = channel.queueDeclare().getQueue();
        System.out.println(" [Consumer] declare to get random queue: " + queueName);
        //5.queue绑定到exchange
        //使用rabbitmqctl list_bindings命令,查看绑定
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        //6.设置消息处理函数
        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(" [Consumer] Received '" + message + "'");
            }
        };

        //7.监听消息
        channel.basicConsume(queueName, true, consumer);
    }
}

(3) 运行结果

同时运行两个consumer,你会看到这两个consumer分别生成了自己的queue并且都绑定到了“logs”这个exchange。如果先运行producer,然后再运行consumer,那么在consumer运行起来之前由producer产生的message将丢失。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/hetoby/article/details/79871054
今日推荐