1.消息应答模式(手动、自动)
1.1应答模式
为了确保消息不会丢失,RabbitMQ支持消息应答。消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕了。RabbitMQ就可以删除它了。
如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。这样,你就可以确认即使消费者偶尔挂掉也不会丢失任何消息了。 没有任何消息超时限制;只有当消费者挂掉时,RabbitMQ才会重新投递。即使处理一条消息会花费很长的时间。
消息应答是默认打开的。我们通过显示的设置autoAsk=true关闭这种机制。现即自动应答开,一旦我们完成任务,消费者会自动发送应答。通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者
.1.2自动应答
// 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
channel.basicConsume(QUEUE_EMAIL, true, defaultConsumer);
生产者
public class Producer {
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接
Connection connection = ConnectionUtils.newConnection();
// 2.创建通道
Channel channel = connection.createChannel();
//3.绑定的交换机 参数1交互机名称 参数2 exchange类型
channel.exchangeDeclare(EXCHANGE, "fanout");
String msg = "测试生产消息" + new Date();
try {
// channel.txSelect();
// 4.生产消息
channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
// int i = 1 / 0;
System.out.println("生产者生产消息:" + msg);
// channel.txCommit();
} catch (IOException e) {
e.printStackTrace();
// channel.txRollback();
} finally {
channel.close();
connection.close();
}
}
}
短信消费者
public class SmsConsumer {
private static String QUEUE_SMS = "sms";
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
System.out.println("短信消费者");
//1.创建连接
Connection connection = ConnectionUtils.newConnection();
// 2.创建通道
final Channel channel = connection.createChannel();
// 3.声明队列
/**
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue:这没什么好说的,队列名
*
* durable:是否持久化,那么问题来了,这是什么意思?持久化,指的是队列持久化到数据库中。在之前的博文中也说过,如果RabbitMQ服务挂了怎么办,队列丢失了自然是不希望发生的。持久化设置为true的话,即使服务崩溃也不会丢失队列
* exclusive:是否排外,what? 这又是什么呢。设置了排外为true的队列只可以在本次的连接中被访问,也就是说在当前连接创建多少个channel访问都没有关系,但是如果是一个新的连接来访问,对不起,不可以,下面是我尝试访问了一个排外的queue报的错。还有一个需要说一下的是,排外的queue在当前连接被断开的时候会自动消失(清除)无论是否设置了持久化
* autoDelete:这个就很简单了,是否自动删除。也就是说queue会清理自己。但是是在最后一个connection断开的时候
* 设置队列的其他一些参数
*/
channel.queueDeclare(QUEUE_SMS, false, false, false, null);
/**
* String queue, String exchange, String routingKey
*/
channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msgString = new String(body, "UTF-8");
System.out.println("短信消费者开始获取消息.........." );
int i = 1 / 0;
// 手动应答消息
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("短信消费者结束获取消息:" + msgString);
}
};
// 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
}
}
邮件消费者
public class SmsConsumer {
private static String QUEUE_SMS = "sms";
private static String EXCHANGE = "sms_email";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
System.out.println("短信消费者");
//1.创建连接
Connection connection = ConnectionUtils.newConnection();
// 2.创建通道
final Channel channel = connection.createChannel();
// 3.声明队列
/**
* String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
* queue:这没什么好说的,队列名
*
* durable:是否持久化,那么问题来了,这是什么意思?持久化,指的是队列持久化到数据库中。在之前的博文中也说过,如果RabbitMQ服务挂了怎么办,队列丢失了自然是不希望发生的。持久化设置为true的话,即使服务崩溃也不会丢失队列
* exclusive:是否排外,what? 这又是什么呢。设置了排外为true的队列只可以在本次的连接中被访问,也就是说在当前连接创建多少个channel访问都没有关系,但是如果是一个新的连接来访问,对不起,不可以,下面是我尝试访问了一个排外的queue报的错。还有一个需要说一下的是,排外的queue在当前连接被断开的时候会自动消失(清除)无论是否设置了持久化
* autoDelete:这个就很简单了,是否自动删除。也就是说queue会清理自己。但是是在最后一个connection断开的时候
* 设置队列的其他一些参数
*/
channel.queueDeclare(QUEUE_SMS, false, false, false, null);
/**
* String queue, String exchange, String routingKey
*/
channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msgString = new String(body, "UTF-8");
System.out.println("短信消费者开始获取消息.........." );
int i = 1 / 0;
// 手动应答消息
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("短信消费者结束获取消息:" + msgString);
}
};
// 4.设置应答模式,true的时候为自动应答,false为手动应答,需要处理
channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
}
}
异常状态:
int i = 1 / 0,我们在邮件和短信消费者代码里面加入,在手动或者自动应答之前抛出异常,就会导致消费都没被消费,而如果在应答之后抛出异常,只能用事务来处理,使用的消息队列是sms和email.
看到消息都是出于等待被消费的状态
正常
注释掉int i = 1/0,
手动应答(队列sms):通过debug可以看到,执行完1,消费还没被确认
直到2执行完成后,才被确认
自动应答(队列email):执行完成后就被确认了
1.消息事务
事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
channel.txSelect()声明启动事务模式;
channel.txComment()提交事务;
channel.txRollback()回滚事务;
生产者回滚:消费队列中看不到任何消息
channel.txSelect();
// 4.生产消息
channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
int i = 1 / 0;
channel.txCommit();
System.out.println("生产者生产消息:" + msg);
} catch (IOException e) {
e.printStackTrace();
channel.txRollback();
}
消费者回滚:消息没有被消费,进行了回滚
try {
channel.txSelect();
System.out.println("短信消费者开始获取消息.........." );
// 手动应答消息
channel.basicAck(envelope.getDeliveryTag(), false);
int i = 1 / 0;
channel.txCommit();
System.out.println("短信消费者结束获取消息:" + msgString);
} catch (IOException e) {
channel.txRollback();
}
3.公平转发
目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。 为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。 换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。
**通过 设置channel.basicQos(1);** 我们生产了10条消息, 01消费者,休眠时间比较短,消费了9条消息消费者02,休眠时间较长,最后只消费了1条信息
总结:也就是通过参数设置,我们可以选择让空闲的消费者多去消费消息,而忙碌的消费者,则等空闲再去消费消息