RabbitMQ消息确认(发送确认,接收确认)

消息确认

每个 Consumer 可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer 出错或异常退出,而数据还没有处理完成,那么这段数据就丢失了。因为我们采用 no-ack 的方式进行确认,也就是说,每次 Consumer 接到数据后,不管是否处理完成,RabbitMQ Server 会立即把这个 Message 标记为完成,然后从 Queue 中删除。

为了保证数据不被丢失,RabbitMQ 支持消息确认机制,这种机制下不能采用 no-ack,而应该是在处理完数据后发送 ack。如果处理中途 Consumer 退出了,但是没有发送 ack,那么 RabbitMQ 就会把这个 Message 发送到下一个 Consumer,这样就保证了在 Consumer 异常退出的情况下数据也不会丢失。

这里并没有用到超时机制,RabbitMQ 仅仅通过 Consumer 的连接中断来确认该 Message 并没有被正确处理,也就是说,RabbitMQ 给 Consumer 足够长的时间来做数据处理。

之前的例子中,这就是消息确认机制的应用,这种情况下,即使中断任务执行,也不会影响 RabbitMQ 中消息的处理,RabbitMQ 会将其发送给下一个 Consumer 进行处理。

如果忘记了 ack,那么后果很严重。当 Consumer 退出时,Message 会重新分发。然后 RabbitMQ 会占用越来越多的内存,由于 RabbitMQ 会长时间运行,因此这个“内存泄漏”是致命的。针对这行场景,可以通过以下命令进行 debug:

sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged

消息持久化

为了保证在 RabbitMQ 退出或者 crash 了数据不丢失,需要将 Queue 和 Message 持久化。

使用spring的代码示例

下面是一个使用spring整合的代码示例:

首先是rabbitmq的配置文件:

[html]  view plain  copy
 
 
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rabbit="http://www.springframework.org/schema/rabbit"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  5.     http://www.springframework.org/schema/beans/spring-beans.xsd  
  6.     http://www.springframework.org/schema/rabbit  
  7.     http://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd">  
  8.     <!-- spring-rabbit.xsd的版本要注意,很1.4以前很多功能都没有,要用跟jar包匹配的版本 -->  
  9.       
  10.     <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" />  
  11.   
  12.     <rabbit:connection-factory   
  13.         id="connectionFactory"  
  14.         host="${rabbit.host}"   
  15.         port="${rabbit.port}"   
  16.         username="${rabbit.username}"   
  17.         password="${rabbit.password}"  
  18.         publisher-confirms="true"   
  19.     />  
  20.   
  21.     <rabbit:admin connection-factory="connectionFactory" />  
  22.   
  23.     <!-- 给模板指定转换器 --><!-- mandatory必须设置true,return callback才生效 -->  
  24.     <rabbit:template id="amqpTemplate"   connection-factory="connectionFactory"   
  25.         confirm-callback="confirmCallBackListener"  
  26.         return-callback="returnCallBackListener"   
  27.         mandatory="true"   
  28.     />  
  29.       
  30.     <rabbit:queue name="CONFIRM_TEST" />  
  31.           
  32.     <rabbit:direct-exchange name="DIRECT_EX" id="DIRECT_EX" >  
  33.         <rabbit:bindings>  
  34.             <rabbit:binding queue="CONFIRM_TEST" />  
  35.         </rabbit:bindings>  
  36.     </rabbit:direct-exchange>  
  37.   
  38.     <!-- 配置consumer, 监听的类和queue的对应关系 -->  
  39.     <rabbit:listener-container  
  40.         connection-factory="connectionFactory" acknowledge="manual" >  
  41.         <rabbit:listener queues="CONFIRM_TEST" ref="receiveConfirmTestListener" />  
  42.     </rabbit:listener-container>  
  43.   
  44. </beans>  



然后发送方:

[java]  view plain  copy
 
 
  1. import org.springframework.amqp.core.AmqpTemplate;  
  2. import org.springframework.beans.factory.annotation.Autowired;  
  3. import org.springframework.stereotype.Service;  
  4.   
  5. @Service("publishService")  
  6. public class PublishService {  
  7.     @Autowired    
  8.     private AmqpTemplate amqpTemplate;   
  9.       
  10.     public void send(String exchange, String routingKey, Object message) {    
  11.         amqpTemplate.convertAndSend(exchange, routingKey, message);  
  12.     }    
  13. }  


消费方:

[java]  view plain  copy
 
 
  1. import org.springframework.amqp.core.Message;  
  2. import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;  
  3. import org.springframework.stereotype.Service;  
  4.   
  5. import com.rabbitmq.client.Channel;  
  6.   
  7. @Service("receiveConfirmTestListener")  
  8. public class ReceiveConfirmTestListener implements ChannelAwareMessageListener {    
  9.     @Override  
  10.     public void onMessage(Message message, Channel channel) throws Exception {  
  11.         try{  
  12.             System.out.println("consumer--:"+message.getMessageProperties()+":"+new String(message.getBody()));  
  13.             channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);  
  14.         }catch(Exception e){  
  15.             e.printStackTrace();//TODO 业务处理  
  16.             channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);  
  17.         }  
  18.     }    
  19. }    


确认后回调:

[java]  view plain  copy
 
 
  1. import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;  
  2. import org.springframework.amqp.rabbit.support.CorrelationData;  
  3. import org.springframework.stereotype.Service;  
  4.   
  5. @Service("confirmCallBackListener")  
  6. public class ConfirmCallBackListener implements ConfirmCallback{  
  7.     @Override  
  8.     public void confirm(CorrelationData correlationData, boolean ack, String cause) {  
  9.         System.out.println("confirm--:correlationData:"+correlationData+",ack:"+ack+",cause:"+cause);  
  10.     }  
  11. }  

失败后return回调:

[java]  view plain  copy
 
 
  1. import org.springframework.amqp.core.Message;  
  2. import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;  
  3. import org.springframework.stereotype.Service;  
  4.   
  5. @Service("returnCallBackListener")  
  6. public class ReturnCallBackListener implements ReturnCallback{  
  7.     @Override  
  8.     public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {  
  9.         System.out.println("return--message:"+new String(message.getBody())+",replyCode:"+replyCode+",replyText:"+replyText+",exchange:"+exchange+",routingKey:"+routingKey);  
  10.     }  
  11. }  

测试类:

[java]  view plain  copy
 
 
  1. import org.junit.Test;  
  2. import org.junit.runner.RunWith;  
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.test.context.ContextConfiguration;  
  5. import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
  6.   
  7. import com.dingcheng.confirms.publish.PublishService;    
  8.     
  9. @RunWith(SpringJUnit4ClassRunner.class)    
  10. @ContextConfiguration(locations = {"classpath:application-context.xml"})    
  11. public class TestConfirm {    
  12.     @Autowired    
  13.     private PublishService publishService;    
  14.       
  15.     private static String exChange = "DIRECT_EX";  
  16.         
  17.     @Test    
  18.     public void test1() throws InterruptedException{    
  19.         String message = "currentTime:"+System.currentTimeMillis();  
  20.         System.out.println("test1---message:"+message);  
  21.         //exchange,queue 都正确,confirm被回调, ack=true  
  22.         publishService.send(exChange,"CONFIRM_TEST",message);    
  23.         Thread.sleep(1000);  
  24.     }    
  25.       
  26.     @Test    
  27.     public void test2() throws InterruptedException{    
  28.         String message = "currentTime:"+System.currentTimeMillis();  
  29.         System.out.println("test2---message:"+message);  
  30.         //exchange 错误,queue 正确,confirm被回调, ack=false  
  31.         publishService.send(exChange+"NO","CONFIRM_TEST",message);    
  32.         Thread.sleep(1000);  
  33.     }    
  34.       
  35.     @Test    
  36.     public void test3() throws InterruptedException{    
  37.         String message = "currentTime:"+System.currentTimeMillis();  
  38.         System.out.println("test3---message:"+message);  
  39.         //exchange 正确,queue 错误 ,confirm被回调, ack=true; return被回调 replyText:NO_ROUTE  
  40.         publishService.send(exChange,"",message);    
  41. //        Thread.sleep(1000);  
  42.     }    
  43.       
  44.     @Test    
  45.     public void test4() throws InterruptedException{    
  46.         String message = "currentTime:"+System.currentTimeMillis();  
  47.         System.out.println("test4---message:"+message);  
  48.         //exchange 错误,queue 错误,confirm被回调, ack=false  
  49.         publishService.send(exChange+"NO","CONFIRM_TEST",message);    
  50.         Thread.sleep(1000);  
  51.     }    
  52. }    


测试结果:

[html]  view plain  copy
 
 
  1. test1---message:currentTime:1483786948506  
  2. test2---message:currentTime:1483786948532  
  3. consumer--:MessageProperties [headers={spring_return_correlation=445bc7ca-a5bd-47e2-8ba3-f0448420e441}, timestamp=nullmessageId=nulluserId=nullappId=nullclusterId=nulltype=nullcorrelationId=nullreplyTo=nullcontentType=text/plain, contentEncoding=UTF-8, contentLength=0deliveryMode=PERSISTENTexpiration=nullpriority=0redelivered=falsereceivedExchange=DIRECT_EXreceivedRoutingKey=CONFIRM_TESTdeliveryTag=1messageCount=0]:currentTime:1483786948506  
  4. test3---message:currentTime:1483786948536  
  5. confirm--:correlationData:null,ack:false,cause:channel error; protocol method: #method<channel.close>(reply-code=404reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60method-id=40)  
  6. confirm--:correlationData:null,ack:false,cause:Channel closed by application  
  7. [ERROR] 2017-01-07 19:02:28 org.springframework.amqp.rabbit.connection.CachingConnectionFactory.shutdownCompleted(CachingConnectionFactory.java:281):--> Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60method-id=40)    
  8.  return--message:currentTime:1483786948536,replyCode:312,replyText:NO_ROUTE,exchange:DIRECT_EX,routingKey:  
  9. confirm--:correlationData:null,ack:true,cause:null  
  10. test4---message:currentTime:1483786948546  
  11. confirm--:correlationData:null,ack:false,cause:channel error; protocol method: #method<channel.close>(reply-code=404reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60method-id=40)  
  12. [ERROR] 2017-01-07 19:02:28 org.springframework.amqp.rabbit.connection.CachingConnectionFactory.shutdownCompleted(CachingConnectionFactory.java:281):--> Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60method-id=40)    
  13.    


代码和配置里面,已经都有注释,就不在多说明了.(callback是异步的,所以测试中sleep1秒钟等待下)

总结下就是:

如果消息没有到exchange,则confirm回调,ack=false

如果消息到达exchange,则confirm回调,ack=true

exchange到queue成功,则不回调return

exchange到queue失败,则回调return(需设置mandatory=true,否则不回回调,消息就丢了)


备注:需要说明,spring-rabbit和原生的rabbit-client ,表现是不一样的.

测试的时候,原生的client,exchange错误的话,直接就报错了,是不会到confirmListener和returnListener的


源码地址:https://github.com/qq315737546/spring-rabbit


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ 中,消费者接收消息后,需要发送 ACK 确认接收,以告知 RabbitMQ消息已经被正确地接收并处理。如果消费者接收消息后没有发送 ACK 确认接收,那么 RabbitMQ 将会认为该消息没有被正确地处理,会重新将消息发送给其他消费者。 在 RabbitMQ 中,发送 ACK 确认接收的方式有两种: 1. 自动确认模式 在自动确认模式下,当消费者接收消息后,RabbitMQ 会自动发送 ACK 确认接收,不需要手动发送 ACK。这种模式下,如果消息处理失败,那么消息就会被丢弃,因此,只有在消息处理相对简单、不需要进行复杂的错误处理时,才适合使用自动确认模式。 2. 手动确认模式 在手动确认模式下,当消费者接收消息后,需要手动发送 ACK 确认接收。如果消息处理失败,可以发送 NACK 拒绝接收,然后重新将消息发送给其他消费者。手动确认模式可以保证消息的可靠性和一致性,但需要消费者手动发送 ACK、NACK 等命令,因此比较复杂。 在 RabbitMQ 的 Java 客户端中,可以使用 channel.basicAck() 方法手动发送 ACK 确认接收,使用 channel.basicNack() 方法发送 NACK 拒绝接收。例如,以下代码演示了如何手动发送 ACK 确认接收: ```java channel.basicConsume(queueName, false, new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 处理消息 // ... // 手动发送 ACK 确认接收 channel.basicAck(envelope.getDeliveryTag(), false); } }); ``` 在上述代码中,第二个参数设置为 false,表示关闭自动确认模式,需要手动发送 ACK 确认接收。当消息处理完成后,调用 channel.basicAck() 方法发送 ACK 确认接收。这样可以保证消息被正确地处理,并且可以避免消息丢失的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值