介绍
首先来段官方文档
In the previous tutorial we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we’ll do something completely different – we’ll deliver a message to multiple consumers. This pattern is known as “publish/subscribe”.
The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue. Actually, quite often the producer doesn’t even know if a message will be delivered to any queue at all.
Instead, the producer can only send messages to an exchange. An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.
简言之,之前的模式都是一个生产者,一个队列,一个或者多个消费者。然而发布订阅模式引入了交换机的概念。有了交换机,生产者不会将消息直接发到队列中,而是发送到交换机,由交换机来决定将消息传送到具体哪一个或者多个队列中。其模型如下:
生产者实现
其基本实现逻辑和第一节讲的无异,关键是多了交换机和交换机与队列之间的联系。因此在代码上,仅仅添加了声明交换机,和绑定队列。注意,这里声明了两个队列,绑定时也是绑定了两个队列。
声明交换机代码如下:
//声明一个交换机
//参数:String exchange, String type
/**
* 参数明细:
* exchange:交换机名称
* type:交换机类型
* fanout:对应额rabbitmq工作模式是publish/subscribe
* direct:对应额rabbitmq工作模式是routing路由模式
* topic:对应额rabbitmq工作模式是topics通配符
* headers:对应额rabbitmq工作模式是headers转发器
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
绑定队列代码如下:
//进行交换机和队列绑定
//参数:String queue, String exchange, String routingKey
/**
* 参数明细:
* queue:队列名称
* exchange:交换机名称
* routingKey:路由key,作用是交换机根据路由key将消息发布到指定队列中。在发布订阅模式设为空串
*/
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
完整代码如下:
public class Producer01 {
private static final String QUEUENAME01 = "queue01";
private static final String QUEUENAME02 = "queue02";
private static final String EXCHANGE_FANOUT_INFO = "exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机,一个mq的服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq。
connectionFactory.setVirtualHost("/");
//建立新连接
Connection connection = null;
connection = connectionFactory.newConnection();
//创建会话通道,生产者和mq服务所有的通信都在channel里完成。
Channel channel = connection.createChannel();
//声明队列:如果队列在mq中没有,则创建
//参数String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
/**
* 参数明细:
* queue:队列名称
* durable:持久化,如果持久化,重启mq服务队列还在
* exclusive:是否独占连接,队列只允许在该连接中访问,如果连接关闭则队列删除,设置为true,可用于临时队列。
* autoDelete:自动删除,设置为true,连接不使用则删除队列
* arguments:指定一些扩展参数。例如存活时间等等
*/
channel.queueDeclare(QUEUENAME01,true,false,false,null);
channel.queueDeclare(QUEUENAME02,true,false,false,null);
//声明一个交换机
//参数:String exchange, String type
/**
* 参数明细:
* exchange:交换机名称
* type:交换机类型
* fanout:对应额rabbitmq工作模式是publish/subscribe
* direct:对应额rabbitmq工作模式是routing路由模式
* topic:对应额rabbitmq工作模式是topics通配符
* headers:对应额rabbitmq工作模式是headers转发器
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
//进行交换机和队列绑定
//参数:String queue, String exchange, String routingKey
/**
* 参数明细:
* queue:队列名称
* exchange:交换机名称
* routingKey:路由key,作用是交换机根据路由key将消息发布到指定队列中。在发布订阅模式设为空串
*/
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
channel.queueBind(QUEUENAME02,EXCHANGE_FANOUT_INFO,"");
//发送消息
//参数:String exchange, String routingKey, BasicProperties props, byte[] body
/**
* 参数明细:
* exchange:交换机,不指定的话为默认交换机
* routingKey:交换机根据路由key,将消息转发到指定队列,如果使用默认交换机,routingKey设置为队列的名称
* props:消息的属性
* body:消息内容,字节数组形式
*/
channel.basicPublish(EXCHANGE_FANOUT_INFO,"",null,"现在是发布订阅模式".getBytes());
System.out.println("send to mq");
//关闭通道
channel.close();
//关闭连接
connection.close();
}
}
消费者实现
消费者实现同样也要声明交换机和绑定队列,这次只需要绑定一次(对应的)队列即可。
public class Consumer01 {
private static final String QUEUENAME01 = "queue01";
private static final String EXCHANGE_FANOUT_INFO = "exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机,一个mq的服务可以设置多个虚拟机,每个mq相当于一个独立的mq。
connectionFactory.setVirtualHost("/");
//建立新连接
Connection connection = null;
connection = connectionFactory.newConnection();
//创建会话通道,生产者和mq服务所有的通信都在channel里完成。
Channel channel = connection.createChannel();
//声明队列:如果队列在mq中没有,则创建
channel.queueDeclare(QUEUENAME01,true,false,false,null);
channel.exchangeDeclare(EXCHANGE_FANOUT_INFO, BuiltinExchangeType.FANOUT);
channel.queueBind(QUEUENAME01,EXCHANGE_FANOUT_INFO,"");
//实现消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 当接收到消息后,此方法执行
* @param consumerTag 消费者标签,用来标识消费者,在监听队列时也可设置channel.basicConsume
* @param envelope 信封,可用信封获取一些信息,例如交换机、消息id等
* @param properties 消息属性,发送消息时设置的消息属性可在这里获取
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//super.handleDelivery(consumerTag, envelope, properties, body);
//拿到交换机
String exchange = envelope.getExchange();
//消息id,mq在channel中用来表示消息的id,可用于确认消息已接收。
long deliveryTag = envelope.getDeliveryTag();
String message = new String(body,"UTF-8");
System.out.println("交换机:"+exchange+" 消息id"+deliveryTag+" 消息内容"+message);
}
};
//监听队列
//参数 String queue, boolean autoAck, Consumer callback
/**
* 参数明细:
* queue:队列名称
* autoAck:自动回复,消费者接受到消息会自动告诉mq服务消息已接收此参数设置为true
* 会自动回复mq,如果设置为false要通过编程回复,不回复的话消息一直在队列里。
* callback:回调方法,当消费者接收到消息要执行的方法。
*/
channel.basicConsume(QUEUENAME01,true,defaultConsumer);
}
}