前面的两个例子,我们直接使用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将丢失。