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


    前面几篇记录了收发消息的demo,今天记录下关于 消息确认方面的 问题.

    下面是几个问题:

    1.为什么要进行消息确认?

    2.rabbitmq消息确认 机制是什么样的?

    3.发送方如何确认消息发送成功?什么样才算发送成功?

    4.消费方如何告知rabbitmq消息消费成功或失败?

    5.使用spring的代码示例

    1.为什么要进行消息确认?

    经常会听到丢消息的字眼, 对于前面的demo来说,就存在丢消息的隐患.

    发送者没法确认是否发送成功,消费者处理失败也无法反馈.

    没有消息确认机制,就会出现消息莫名其妙的没了,也不知道什么情况.

    2.rabbitmq消息确认 机制是什么样的?

    首先看官网对消息确认的介绍http://www.rabbitmq.com/confirms.html

    网上会有很多总结的博客(包括现在看的),很多就是对官网的翻译.所以看资料首先要去官网看看,这很关键.

    看上图官网的介绍.唯一保证消息不丢失的是使用事务,但是性能太差,作为补偿,有了消息确认机制.

    并说明了开启方法,以及和事务模式不共存.

    还写了一个例子,但是点进去那个链接已经失效了,新版的源码上也没有这个例子,我找了最近一版是3.6.7上面还有.

    点这里看官方的例子

    3.发送的消息什么样才算成功或失败? 如何确认?

    判断消息成功或失败,其实就是看进行消息确认的时机,因为成功或失败后就会把结果告诉发送方.还是看官方解释:

    意思如下:

    确认消息不能路由时(exchange确认不能路由到任何queue),进行确认操作(确认失败).如果发送方设置了mandatory模式,则会先调用basic.return方法.

    消息可以路由时,当需要发送的队列都发送成功后,进行消息确认.对于持久化的队列,意味着已经写入磁盘,对于镜像队列,意味着所有镜像都接受成功.

    至于如何确认的问题,上面已经写了 basic.ack方法

    4.消费方如何告知rabbitmq消息消费成功或失败?

    如图可知,根据消费方不同的确认模式,确认时机也不同.

    自动确认会在消息发送给消费者后立即确认,如果手动则当消费者调用ack,nack,reject几种方法时进行确认.

    一般会设置手动模式,业务失败后可以进行一些操作.

    5.使用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=null, messageId=null, userId=null, appId=null, clusterId=null, type=null, correlationId=null, replyTo=null, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, deliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=false, receivedExchange=DIRECT_EX, receivedRoutingKey=CONFIRM_TEST, deliveryTag=1, messageCount=0]:currentTime:1483786948506  
    4. test3---message:currentTime:1483786948536  
    5. confirm--:correlationData:null,ack:false,cause:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-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=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-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=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-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=404, reply-text=NOT_FOUND - no exchange 'DIRECT_EXNO' in vhost '/', class-id=60, method-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的

  • 相关阅读:
    鸟哥的linux私房菜学习-(八)Linux 文件与目录管理
    我的作品
    聊聊软件测试面试的一些事
    如何做一名专业的软件测试工程师
    测试Leader应该做哪些事
    软件测试工程师的岗位职责
    一个完整的性能测试流程
    做接口测试需要哪些技能
    软件质量保障体系建设
    性能测试常见瓶颈分析及调优方法
  • 原文地址:https://www.cnblogs.com/jack87224088/p/8875353.html
Copyright © 2020-2023  润新知