这个官网的[第二个例子](http://www.rabbitmq.com/tutorials/tutorial-two-java.html)中的消息应答和消息持久化部分。我把它摘出来作为单独的一块儿来分享。
# Message acknowledgment(消息应答)
执行一个任务可能需要花费几秒钟,你可能会担心如果一个消费者在执行任务过程中挂掉了。基于现在的代码,一旦RabbitMQ将消息分发给了消费者,就会从内存中删除。在这种情况下,如果杀死正在执行任务的消费者,会丢失正在处理的消息,也会丢失已经分发给这个消费者但尚未处理的消息。
但是,我们不想丢失任何任务,如果有一个消费者挂掉了,那么我们应该将分发给它的任务交付给另一个消费者去处理。
为了确保消息不会丢失,RabbitMQ支持消息应答。消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕了。RabbitMQ可以删除它了。
如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。这样,你就可以确认即使消费者偶尔挂掉也不会不丢失任何消息了。
没有任何消息超时限制;只有当消费者挂掉时,RabbitMQ才会重新投递。即使处理一条消息会花费很长的时间。
消息应答是**默认打开**的。我们明确地把它们关掉了(autoAck=true)。现在将应答打开,一旦我们完成任务,消费者会自动发送消息应答。
~~~
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
~~~
修改一下Worker.java
~~~
channel.basicQos(1);//保证一次只分发一个
// 创建队列消费者
final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
System.out.println(" [x] Proccessing... at " +new Date().toLocaleString());
try {
for (char ch: message.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
} finally {
System.out.println(" [x] Done! at " +new Date().toLocaleString());
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
~~~
我们还是运行1个生产者,2个消费者,在消息处理过程中,人为让一个消费者挂掉,然后会看到剩下的任务都会被另外的消费者执行。
运行结果如下:
![](https://box.kancloud.cn/2016-02-18_56c53cbd36d7e.jpg)
如果你关闭了自动消息应答,手动也未设置应答,这是一个很简单错误,但是后果却是极其严重的。消息在分发出去以后,得不到回应,所以不会在内存中删除,结果RabbitMQ会越来越占用内存,最终的结果,你懂得。。。
# Message durability(消息持久化)
我们已经了解了如何确保即使消费者死亡,任务也不会丢失。但是如果RabbitMQ服务器停止,我们的任务仍将失去!
当RabbitMQ退出或者崩溃,将会丢失队列和消息。除非你不要队列和消息。两件事儿必须保证消息不被丢失:我们必须把“队列”和“消息”设为持久化。
~~~
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
~~~
尽管这行代码是正确的,但他不会在我们当前的设置中起作用。因为我们已经定义了一个名叫hello的未持久化的队列。RabbitMQ不允许使用不同的参数设定重新定义已经存在的队列,并且会向尝试如此做的程序返回一个错误。一个快速的解决方案——就是声明一个不同名字的队列,比如task_queue。
(当然,我们也可以登录到RabbitMQ的服务管理页面,RabbitMQ默认的端口是5672,管理页面默认端口是15672,页面地址为:http://localhost:15672,使用是用户名和密码登录。RabbitMQ的默认密码和用户名都是guest。点开“queue”那栏,可以看到队列列表,点击“hello”杜列,会展开队列的详细信息。把页面拉到最后,有一项“Delete / purge”,点开,点击“Delete”按钮,就可以把队列删除掉了。 然后再运行代码的时候,就会创建一个支持持久化的hello队列。)
上述的代码需要在生产者和消费者都要作出同样的修改。
在这一点上我们确信task_queue的队列不会丢失,即使RabbitMQ服务重启。现在我们需要将消息标记为持久性的——通过设置 MessageProperties(实现BasicProperties)为PERSISTENT_TEXT_PLAIN。
现在你可以启动RabbitMQ服务器,执行一次生产者NewTask的程序,然后关闭服务器,再重新启动服务器,运行消费者Work做下实验。可以发现消费者依旧可以读出消息来。说明在RabbitMQ服务器关闭后,消息和队列信息都已经做了持久化。再次启动后,会重新加载到服务器中,消费者运行后,就可以正常的从队列中获取消息了。