目录
在此介绍一些学习RabbitMQ时 可以加深理解的几种消息模型Demo
前置准备
首先当然是开启rabbitMQ,然后登陆监控页面检查一下
之后在demo项目中写一个工具类(如果你不想每个小demo都写一次创建连接的参数的话)
public class RabbitMQUtils {
private static ConnectionFactory connectionFactory;
static{
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
}
//获取一个rabbitMQ连接
public static Connection getConnection() throws IOException, TimeoutException {
Connection connection = connectionFactory.newConnection();
return connection;
}
public static void close(Connection connection, Channel channel) throws IOException, TimeoutException {
//5.最后要关闭连接资源
if (null!=channel){
channel.close();
}
if (null!=connection){
connection.close();
}
}
}
1.工作模式
work queue(task queue),任务模型
就是少数的消息生产者,生产的消息由一个消费者消费的速度太慢,消费吃力时
另起一个消费者来分担压力。要求 协调并进,避免消息丢失!
生产者只给一个队列发消息,多个消费者绑定到这一个队列,共同消费队列中的消息
代码示范:先运行消费者挂机,然后运行生产者发布消息,观察消费者收取消息的情况
生产者:
public class proivder {
public static void main(String[] args) throws IOException, TimeoutException {
//1.获取连接,创建通道
Connection connection = RabbiMQUtils.getConnection();
Channel channel = connection.createChannel();
//2.声明一个队列 workqueue
channel.queueDeclare("workqueue",false,false,false,null);
//3.发30遍消息,用于测试多个消费者是否协同消费消息
for (int i = 0; i < 30; i++) {
channel.basicPublish("",
"workqueue",
null,
("This is workqueue mode!,num="+i).getBytes());
}
//关闭资源
RabbiMQUtils.close(connection,channel);
}
}
消费者:
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接和通道
Connection connection = RabbiMQUtils.getConnection();
Channel channel = connection.createChannel();
//2.声明队列(消费者这里需要和生产者声明队列的相同,参数一个不能错)
// 这是因为我们运行的顺序:先运行消费者等待消息,再运行生产者发布消息
// 那么如果不先声明一个队列,后面根据队列收取消息时会报错:找不到目标队列
channel.queueDeclare("workqueue",false,false,false,null);
//3.声明预取的消息数量,一次一个,若不这么做,
// 就会被一个消费者独揽这个队列里的所有的消息
channel.basicQos(1);
/**
* 这里的第二个参数:autoAck 表示是否自动确认
* 若如果是自动确认,会有消费者分散 导致的消息丢失风险!!
*
* 场景描述:
* 测试一个场景,当两个消费者,一个消费者消费的快,另一个消费的慢,
* 但是生产者生产的消息全部被这两个消费者确认了!
* 但此时消费慢的消费者却正在慢慢消费!
* 这就造成了消息的丢失
* (因为慢消费者没消费的消息,已经被确认了,导致快的消费者无法分担压力。
*/
channel.basicConsume("workqueue",false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1:"+new String(body));
/**
* 既然已经选择了非自动确认,那么就需要手动确认,防止重消费!
*
* 第一个参数表示投递过来的消息标签
(标签是mq标记的 1..2..3...)表示确认哪个消息
* 第二个参数表示 把累计消费的消息是否再全部确认一遍,
通常用 false,表示来一条确认一条
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
--------------------------------------------------
consumer2代码与consumer1一样,只有模拟处理时间的线程休眠时间不一样,不作演示
2.订阅模式
Fanout queue,也称广播模式,原理与广播发送消息类似
生产者发布消息到交换机,然后其他消费者的队列绑定到交换机,
交换机会公平分发消息给每个绑定到此的队列
代码示范:先运行一遍生产者,创建该有的交换机,然后消费者运行后挂机,等待生产者再次运行发布消息,
观察消费者收取消息的情况
生产者:
public class FanoutProvider {
public static final String EXCHANGE_NAME = "fanout-exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//1.获取连接和通道
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//2.声明一个Fanout类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
/* 3.消息发送到交换机
因为是fanout广播模式的特点是:
每个绑定到此交换机的队列,都将收到同样的消息,就像广播一样
所以不需要routingKey有值,但不能为NULL
*/
channel.basicPublish(EXCHANGE_NAME,
"",
null,
("this is fanout test!").getBytes());
RabbitMQUtils.close(connection,channel);
}
}
消费者:
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);
//使用一个临时的队列来做这次demo即可
String queueName = channel.queueDeclare().getQueue();
/*注意!
这里队列绑定到指定的交换机即可,无需routingKey的值,有也一样,不能为null
* */
channel.queueBind(queueName, FanoutProvider.EXCHANGE_NAME,"");
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println(new String(body, Charset.defaultCharset()));
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
3.直连模型
Direct queque,根据routingKey来指定某某队列收到消息的方式,精确匹配routingKey的模式
代码示范: 先运行一遍生产者,创建该有的交换机,然后消费者运行后挂机,等待生产者再次运行发布消息,
观察消费者收取消息的情况
public class DirectProvider {
public static final String EXCHANGE_NAME = "direct-exchange";
public static void main(String[] args) throws IOException, TimeoutException {
//1.获取连接和通道
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//2.声明一个Direct类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//3.发布消息,需要指定routingKey,交换机收到消息后,
// 将会根据routingKey匹配队列发送
channel.basicPublish(EXCHANGE_NAME,
"success",
null,
("this is direct test for success!").getBytes());
RabbitMQUtils.close(connection,channel);}}
消费者:
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);
String queueName = channel.queueDeclare().getQueue();
/*注意!
* 这里是queueBind,是将队列绑定到交换机,并设置routingKey,
这里的routingKey决定了能不能收到生产者发布的消息:
* 只有和生产者发布消息时设置的routingKey相同,才会收到消息
* */
channel.queueBind(queueName,"direct-exchange","success");
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println(new String(body, Charset.defaultCharset()));
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
其他类型
另外还有其他模型,在编写的时候的过程都差不多,不做举例