
由图我们可以看出发布订阅的工作模式
1、一个生产者将消息发送给交换机
2、与交换机绑定的有多个队列,每个消费者监听自己的队列
3、生产者将消息发送给交换机,由交换机把消息转发到绑定次交换机的每个队列,每个绑定到交换机的队列都能接收到消息
下面来模仿现实生活中的一个案例
案例:用户通知,当用户充值成功或转账完成系统通知用户,通知方式有短信、邮件多种方法 。
1、首先创建生产者
public class produce {
private static final String QUEUE_INFORM_EMAIL = "queue_inform_email";
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_FANOUT_INFORM = "exchange_fanout_inform";
public static void main(String[] args) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
connection = factory.newConnection();
channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//声明交换机
/**
*参数 String exchange, BuiltinExchangeType type
* 1、交换机名称
* 2、交换机类型
* 1)fanout 发布订阅模式(Publish/Subscribe)
* 2)direct 路由模式(Routing)
* 3)topic 通配符模式(topic)
* 4) headers 对应headers
*/
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
//交换机和队列绑定
/**
* 参数 定String queue, String exchange, String routingKey
* 1、队列
* 2、交换机
* 3、路由key 交换机根据路由key把消息发送到指定队列中在发布订阅模式中设置 ""
*/
channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_FANOUT_INFORM, "");
channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_FANOUT_INFORM, "");
//发送消息
for (int i = 0; i < 5; i++) {
String message = "测试发布订阅";
channel.basicPublish(EXCHANGE_FANOUT_INFORM, "", null, message.getBytes());
System.out.print(message);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//先关闭通道 再关连接
try {
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
2、创建短信消费者
public class ConsumerSendSms {
private static final String QUEUE_INFORM_SMS = "queue_inform_sms";
private static final String EXCHANGE_FANOUT_INFORM = "inform_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
factory.setVirtualHost("/");
//创建一个连接
Connection connection = factory.newConnection();
//创建与交换机的通道,每个通道代表一个会话
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_INFORM_SMS, true, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
//队列与交换机绑定
channel.queueBind(QUEUE_INFORM_SMS, EXCHANGE_FANOUT_INFORM, "");
/**
* 消费者接收消息调用此方法
*/
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
*
* @param consumerTag 消费者标签标签 可以不设 可以在监听队列时设置
* @param envelope 信封
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
envelope.getExchange();
//消息id mq在通道中(channel)标识消息id 可用于消息已接收
long deliveryTag = envelope.getDeliveryTag();
String message = new String(body);
System.out.print(message);
}
};
//监听队列
//声明队列
/**
*参数
* 1、string queue
* 2、boolean autoAck
* 3、Consumer callback
*/
channel.basicConsume(QUEUE_INFORM_SMS, true, defaultConsumer);
}
}
3、Email消费者
public class ConsumerSendEmail {
private static final String QUEUE_INFORM_EMAIL = "inform_queue_email";
private static final String EXCHANGE_FANOUT_INFORM = "inform_exchange_fanout";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
factory.setVirtualHost("/");
//创建一个连接
Connection connection = factory.newConnection();
//创建与交换机的通道,每个通道代表一个会话
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_INFORM_EMAIL, true, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_FANOUT_INFORM, BuiltinExchangeType.FANOUT);
//队列与交换机绑定
channel.queueBind(QUEUE_INFORM_EMAIL, EXCHANGE_FANOUT_INFORM, "");
/**
* 消费者接收消息调用此方法
*/
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
*
* @param consumerTag 消费者标签标签 可以不设 可以在监听队列时设置
* @param envelope 信封
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//交换机
envelope.getExchange();
//消息id mq在通道中(channel)标识消息id 可用于消息已接收
long deliveryTag = envelope.getDeliveryTag();
String message = new String(body);
System.out.print(message);
}
};
//监听队列
//声明队列
/**
*参数
* 1、string queue
* 2、boolean autoAck
* 3、Consumer callback
*/
channel.basicConsume(QUEUE_INFORM_EMAIL, true, defaultConsumer);
}
}
测试:
打开RabbitMQ的管理界面,观察交换机绑定情况:
当生产者服务启动后可以看到交换机里面绑定了两个队列

而队列里面可以看到待发送10条 总共10条

当把两个消费者服务起来后可以看到连接里面有两个通道

而每个通道可以看到生产者和消费者具体信息


工作队列模式和发布订阅模式的区别
1)work queues不用定义交换机,而publish/subscribe需要定义交换机
2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认 交换机)。
3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑 定到默认的交换机 。
相同点: 两者实现的发布/接收的效果是一样的,多个消费端监听同一个队列不会重复消费消息。

c1相当于短信消费者 c2,c3相当于email消费者 当短信消费者启动一个服务 而email启动多个服务时 可以看到队列没有变化 而监听的成员是三个


4301

被折叠的 条评论
为什么被折叠?



