RabbitMQ
遵循了AMQP规范
,用消息确认机制来保证:只要消息发送,就能确保被消费者消费来做到了消息最终一致性。而且开源,文档还异常丰富,貌似是实现分布式事务的良好载体
6.1 RabbitMQ消息确认机制
rabbitmq的整个发送过程如下
1. 生产者发送消息到消息服务
2. 如果消息落地持久化完成,则返回一个标志给生产者。生产者拿到这个确认后,才能放心的说消息终于成功发到消息服务了。否则进入异常处理流程。
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (!ack) { //try to resend msg } else { //delete msg in db } });
3. 消息服务将消息发送给消费者
4. 消费者接受并处理消息,如果处理成功则手动确认。当消息服务拿到这个确认后,才放心的说终于消费完成了。否则重发,或者进入异常处理。
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 + "'"); try { doWork(message); } finally { //确认收到消息 channel.basicAck(envelope.getDeliveryTag(), false); } } };
6.2 异常
我们来看看可能发送异常的四种
1. 直接无法到达消息服务
网络断了,抛出异常,业务直接回滚即可。如果出现connection closed
错误,直接增加 connection
数即可
connectionFactory.setChannelCacheSize(100);
2. 消息已经到达服务器,但返回的时候出现异常
rabbitmq
提供了确认ack机制,可以用来确认消息是否有返回。因此我们可以在发送前在db中(内存或关系型数据库)先存一下消息,如果ack异常则进行重发
/**confirmcallback用来确认消息是否有送达消息队列*/ rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { if (!ack) { //try to resend msg } else { //delete msg in db } }); /**若消息找不到对应的Exchange会先触发returncallback */ rabbitTemplate.setReturnCallback((message, replyCode, replyText, tmpExchange, tmpRoutingKey) -> { try { Thread.sleep(Constants.ONE_SECOND); } catch (InterruptedException e) { e.printStackTrace(); } log.info("send message failed: " + replyCode + " " + replyText); rabbitTemplate.send(message); });
3. 消息送达后,消息服务自己挂了
如果设置了消息持久化,那么ack= true
是在消息持久化完成后,就是存到硬盘上之后再发送的,确保消息已经存在硬盘上,万一消息服务挂了,消息服务恢复是能够再重发消息
4. 未送达消费者
消息服务收到消息后,消息会处于"UNACK"的状态,直到客户端确认消息
channel.basicQos(1); // accept only one unack-ed message at a time (see below) 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 + "'"); try { doWork(message); } finally { //确认收到消息 channel.basicAck(envelope.getDeliveryTag(), false); } } }; boolean autoAck = false; channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer);
5. 确认消息丢失
消息返回时假设确认消息丢失了,那么消息服务会重发消息。注意,如果你设置了autoAck= false
,但又没应答channel.baskAck
也没有应答channel.baskNack
,那么会导致非常严重的错误:消息队列会被堵塞住,所以,无论如何都必须应答
6. 消费者业务处理异常
消息监听接受消息并处理,假设抛异常了,第一阶段事物已经完成,如果要配置回滚则过于麻烦,即使做事务补偿也可能事务补偿失效的情况,所以这里可以做一个重复执行,比如guava
的retry
,设置一个指数时间来循环执行,如果n次后依然失败,发邮件、短信,用人肉来兜底。