1.简介
RabbitMQ
中,消息丢失可以简单的分为两种:客户端丢失和服务端丢失。针对这两种消息丢失,RabbitMQ
都给出了相应的解决方案。
2.防止客户端丢失消息
如图,生产者P向队列中生产消息,C1和C2消费队列中的消息,默认情况下,RabbitMQ
会平均的分发消费给C1C2(Round-robin dispatching
),假设一个任务的执行时间非常长,在执行过程中,客户端挂了(连接断开),那么,该客户端正在处理且未完成的消息,以及分配给它还没来得及执行的消息,都将丢失。因为默认情况下,RabbitMQ
分发完消息后,就会从内存中把消息删除掉。
3.消息确认(Message acknowledgment
)
为了解决上述问题,RabbitMQ
引入了消息确认机制,当消息处理完成后,给Server
端发送一个确认消息,来告诉服务端可以删除该消息了,如果连接断开的时候,Server
端没有收到消费者发出的确认信息,则会把消息转发给其他保持在线的消费者。
验证上述问题
首先,我们验证上述问题(客户端丢失消息)是否真的存在,对Consumer
进行如下改造。
先生产两条消息
启动消费者,在消费者接收到消息,还没处理完成的时候,强制关掉
这时,观察控制台,发现两条消息都没有了,1条是在执行中丢失的,还有1条,已经分配给这个Consumer
,还没来得及处理,也丢失了
这证明了上述问题是真的存在的,如果发生在生产环境,将产生难以预料的后果
引入消息确认机制
为了方便观察,我们用CMD
来运行Consumer
,要通过maven
打成可执行的JAR
包,需要在pom.xml
中增加如下配置
<build>
<finalName>Consumer</finalName>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.liyang.ticktock.rabbitmq.App</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
上述配置描述了最终打包名字、入口类路径、带上依赖包、使用1.8
版本的JDK
进行打包,配置完后,就可以通过maven
的install
方法,在target
目录生成可执行的jar
包,如果包大小很小,应检查配置,是不是没有带上依赖包
再次改造Consummer
类
install
成可执行jar
包,通过cmd
开启两个consumer
通过Sender
发送一条消息,然后用Ctrl+C
结束先收到消息的Consumer
,发现另外一个Consumer
接收到了未处理完的消息
问题得到了解决,现在消费者在执行过程中死掉也不会丢失消息了
看一下发送确认的方法
/**
* Acknowledge one or several received
* messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
* or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method
* containing the received message being acknowledged.
* @see com.rabbitmq.client.AMQP.Basic.Ack
* @param deliveryTag the tag from the received 这个是RabbitMQ用来区分消息的,文档在这 8 * @param multiple true to acknowledge all messages up to and 为true的话,确认所有消息,为false只确认当前消息
* including the supplied delivery tag; false to acknowledge just
* the supplied delivery tag.
* @throws java.io.IOException if an error is encountered
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
在官方文档中,这样描述deliveryTag
简单来说,就是RabbitMQ
内部用来区分消息的一个标签,从envelope
中获取就行了
忘记确认将引起内存泄漏
RabbitMQ
只有在收到消费者确认后,才会从内存中删除消息,如果消费者忘了确认(更多情况是因为代码问题没有执行到确认的代码),将会导致内存泄漏
验证一下
注释掉Consumer
中的确认代码
运行Sender
和Consumer
,不停的生产消费消息,发现消费者在正常的消费消息
查看控制台,发现已经被吃掉了43KB的内存,所以,在试用过程中,一定要保证消息确认在任何情况下都可以发出,否则即使消费者处理完成,RabbitMQ
也不会把消息在内存中清除,在该消费者断开连接之后,还会把消息转发给其他消费者重新处理,将引发难以预计的问题
4.消息的持久化
现在,消费者宕机已经无法影响到我们的消息了,但如果RabbitMQ
重启了,消息依然会丢失。所幸的是,RabbitMQ
提供了持久化的机制,将内存中的消息持久化到硬盘上,即使重启RabbitMQ
,消息也不会丢失。但是,仍然有一个非常短暂的时间窗口(RabbitMQ
收到消息还没来得及存到硬盘上)会导致消息丢失,如果需要严格的控制,可以参考官方文档
要使用RabbitMQ
的消息持久化,在声明队列时设置一个参数即可
注意,RabbitMQ
不允许对一个已经存在的队列用不同的参数重新声明,对于试图这么做的程序,会报错,所以,改动之前代码之前,要在控制台中把原来的队列删除
重新声明队列后,发现Durable
为true
重启RabbitMQ
队列的消息没有丢失
5.结束语
这一章介绍了RabbitMQ
消息的确认和持久化,后面将会继续深入介绍RabbitMQ
的其他特性