一、工作原理
组成部分说明如下: Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。 Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。 Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。 Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
消息发布接收流程:
-----发送消息-----
1、生产者和Broker建立TCP连接。
2、生产者和Broker建立通道。
3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。
4、Exchange将消息转发到指定的Queue(队列)
----接收消息-----
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。
二、工作模式
work queue
1.两个或多个消费者消费同一个队列中的消息
2.生产者多次发送消息,消费端采用轮询的方式接收
3.消费者在处理完某条消息之后,才会收到下一条消息
代码步骤:
1.创建通道
2.声明队列
无需使用交换机
Publish/Subscribe
1.每个消费者监听自己的队列
2.生产者发送消息,由交换机将消息转发到绑定此交换机的每个队列,每个绑定到交换机的队列都能接收消息
代码步骤
1.创建通道
2.声明交换机
3.声明队列(交换机类型为fanout)
4.队列绑定到交换机(绑定的时候routingkey设为"“即可)
5.发送消息(发送消息时,routingkey设为”")
Routing
1.每个消费者监听自己的队列,并且设置routingkey
2.生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列
代码步骤:
1.创建通道
2.声明交换机
3.声明队列(交换机类型为direct)
4.队列绑定到交换机上(绑定的时候设置routingkey的值)
5.发送消息(发送消息的时候,指定要发送的routingkey)
Topic
1.每个消费者监听自己的队列,并且设置带有通配符的routingkey
2.生产者将消息发送给broker(exchange+queue),由交换机根绝routingkey来转发到指定的队列
案例:
根据用户的通知设置去通知用户,设置接收Email的用户只接收Email,设置接收sms的用户只接收sms,设置两种 通知类型都接收的则两种通知都有效。
代码步骤:
1.创建通道
2.声明交换机(指定topic类型)
3.声明队列
4.生产端无需交换机绑定队列,发送消息的时候指定routingkey
channel.basicPublish(EXCHANGR_TOPIC_INFORM, "sms.email", null, message.getBytes());
5.消费端队列绑定到交换机上,使用通配符
注意问题
1.在队列绑定交换机的时候,可使用
channel.queueBind(String queue,String exchange,String routingKey);
也可以使用
channel.exchangeBind(String exchange,String queue,String routingKey);
参数顺序要注意!!
2.工作模式还有Header、RPC两种模式
总结
Work queue 相当于多个消费者共同监听一个个队列,消息轮询消费
Publish/Describe多个消费者有监听各自的队列,消息发送到各个队列,一个消息被多个消费者消费
Routing多个消费者监听各自的队列,交换机根据路由转发到指定的某个队列
Topic多个消费者监听各自的队列,交换机根据路由转发到指定的某些队列
/**队列绑定交换机指定通配符: 统配符规则: 中间以“.”分隔。 符号#可以匹配多个词,
符号*可以匹配一个词语
*/
channel.exchangeBind(QUEUE_INFORM_EMAIL, EXCHANGR_TOPIC_INFORM, "inform.#.email.#");
//伪代码
--生产者
/*1.定义队列名、交换机名*/
/*2.通过连接工厂建立Connection,创建Channel(即一个会话)*/
/*3.通过通道声明交换机*/
/**
* 参数明细
* 1.交换机名称
* 2.交换机类型 fanout、topic、direct、headers
*/
channel.exchangeDeclare(String exchange, BuiltinExchangeType type);
/*4.通过通道声明队列*/
/**
* 参数明细
* 1.队列名称
* 2.是否持久化
* 3.是否独占此队列
* 4.队列是否自动删除
* 5.参数
*/
channel.queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments);
/*5.交换机和队列进行绑定*/
/**
* 参数明细
* 1.队列名称
* 2.交换机名称
* 3.routingKey
*/
channel.queueBind(String queue, String exchange, String routingKey);
/*6.发送消息*/
/**
* 参数明细
* 1.交换机名称
* 2.routingKey根据key名称将消息转发到具体的队列,表示消息将发到此队列
* 3.消息属性
* 4.消息体
*/
channel.basicPublish(String exchange, String routingKey,
BasicProperties props,byte[] body);
---消费者
/*1.定义队列名、交换机名*/
/*2.通过连接工厂建立Connection,创建Channel(即一个会话)*/
/*3.通过通道声明交换机*/
/**
* 参数明细
* 1.交换机名称
* 2.交换机类型 fanout、topic、direct、headers
*/
channel.exchangeDeclare(String exchange, BuiltinExchangeType type);
/*4.通过通道声明队列*/
/**
* 参数明细
* 1.队列名称
* 2.是否持久化
* 3.是否独占此队列
* 4.队列是否自动删除
* 5.参数
*/
channel.queueDeclare(String queue, boolean durable, boolean exclusive,
boolean autoDelete, Map<String, Object> arguments);
/*5.交换机和队列进行绑定*/
/**
* 参数明细
* 1.队列名称
* 2.交换机名称
* 3.routingKey
*/
channel.queueBind(String queue, String exchange, String routingKey);
/*6.定义消费消息的方法*/
//定义消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
long deliveryTag = envelope.getDeliveryTag();
String exchange = envelope.getExchange();
//接收到的消息内容
String message = new String(body,"utf-8");
System.out.println(message);
}
};
/*7.监听队列,接收消息*/
/** 参数明细
* 1.监听的队列名称
* 2.是否自动回复,设置为true为表示消息接收到自动向mq回复接收到了,
* mq接收到回复会删除消息,设置 为false则需要手动回复
* 3.消费者接收到消息后调用的方法
*/
channel.basicConsume(String queue, boolean autoAck,Consumer callback)
//消息生产者
public class Producer01 {
//队列名称
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = null;
Channel channel = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");//rabbitmq默认虚拟机名称为“/”,虚拟机相当于一个独立的mq服务器
//创建与rabbitmq服务的tcp连接
connection = factory.newConnection();
//创建Exchange通道,每个连接可以创建多个通道,每个通道代表一个会话
channel = connection.createChannel();
/**
* 声明队列,如果rabbitmq中没有此队列将自动创建
* param1:队列名称
* param2:是否持久化
* param3:队列是否独占此连接
* param4:队列不再使用时是否自动删除此队列
* param5:队列参数
*/
channel.queueDeclare(QUEUE, true, false, false, null);
String message = "helloworld小明" + System.currentTimeMillis();
/**
* 消息发布
* param1:Exchange名称,如果没有指定交换机,消息将会发给默认的交换机,每个队列也会绑定那个默认的交换机,
* 但是不能显示绑定或者解除绑定
* param2:routingKey,消息的路由key,用于Exchange将消息转发到指定的队列
* 使用默认交换机时,routingKey使用
* param3:消息包含的属性
* param4:消息体
*/
channel.basicPublish("", QUEUE, null, message.getBytes());
System.out.println("send Message is '" + message +"'");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
}
}
}
//消费者
public class Consumer01 {
//队列名称
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE, true, false, false, null);
//自定义消费方法 继承DefaultConsumer
DefaultConsumer consumer = new DefaultConsumer(channel) {
/**
* 消费者接受消息调用此方法
* @param consumerTag 消费者的标签,在channel.basicConsume()去指定
* @param envelope 消息包的内容,可从中获取消息id,消息routingKey,交换机,消息和
* 重传表示(收到消息失败后是否需要重新发送)
* @param properties
* @param body 消息体
*/
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
//交换机
String exchange = envelope.getExchange();
//路由key
String routingKey = envelope.getRoutingKey();
//消息id
long deliveryTag = envelope.getDeliveryTag();
//消息内容
String msg = new String(body,"utf-8");
System.out.println("receive message.."+ msg);
}
};
/**
* 监听队列String queue,boolean autoAsk,Consumer callback
* 参数明细
* 1.队列名称
* 2.是否自动回复,设置为true表示消息接收到自动向mq回复接收到了,mq接收到回复消息会删除消息,
* 设置为false则需要手动回复
* 3.消费者接收消息后调用此方法
*/
channel.basicConsume(QUEUE, true,consumer);
}
}
先启动消息的生产者,发送一条消息到mq中,然后再启动消费者,监听并拿到这条消息,此时消费者仍会持续监听,再使用生产者发送一条消息,消费者会立马接收到