摘要: 介绍confirm的工作机制。使用spring-amqp介绍事务以及发布确认的使用方式。因为事务以及发布确认是针对channel来讲,所以在一个连接中两个channel,一个channel可以使用事务,另一个channel可以使用发布确认,并介绍了什么时候该使用事务,什么时候该使用发布确认

confirm的工作机制

‍ Confirms是增加的一个确认机制的类,继承自标准的AMQP。这个类只包含了两个方法:confirm.select和confirm.select-ok。另外,basic.ack方法被发送到客户端。

‍ confirm.select是在一个channel中启动发布确认。注意:一个具有事务的channel不能放入到确认模式,同样确认模式下的channel不能用事务。

当confirm.select被发送/接收。发布者/broker开始计数(首先是发布然后confirm.select被记为1)。一旦channel为确认模式,发布者应该期望接收到basic.ack方法,delivery-tag属性显示确认消息的数量。

当broker确认了一个消息,会通知发布者消息被成功处理;‍

‍ basic的规则是这样的:‍

一个未被路由的具有manadatory或者immediate的消息被正确确认后触发basic.return;

另外,一个瞬时态的消息被确认目前已经入队;

持久化的消息在持久化到磁盘或者每个队列的消息被消费之后被确认。

关于confirm会有一些问题:

首先,broker不能保证消息会被confirm,只知道将会进行confirm。

第二,当未被确认的消息堆积时消息处理缓慢,对于确认模式下的发布,broker会做几个操作,日志记录未被确认的消息

第三,如果发布者与broker之间的连接删除了未能得到确认,它不一定知道消息丢失,所以可能会发布重复的消息。

最后,如果在broker中发生坏事会导致消息丢失,将会basic.nack那些消息

总之,Confirms给客户端一种轻量级的方式,能够跟踪哪些消息被broker处理,哪些可能因为broker宕掉或者网络失败的情况而重新发布。

确认并且保证消息被送达,提供了两种方式:发布确认和事务。(两者不可同时使用)在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。

事务

Spring AMQP做的不仅仅是回滚事务,而且可以手动拒绝消息,如当监听容器发生异常时是否重新入队。

持久化的消息是应该在broker重启前都有效。如果在消息有机会写入到磁盘之前broker宕掉,消息仍然会丢失。在某些情况下,这是不够的,发布者需要知道消息是否处理正确。简单的解决方案是使用事务,即提交每条消息。

案例:

RabbitTemplate的使用案例(同步),由调用者提供外部事务,在模板中配置了channe-transacted=true。通常是首选,因为它是非侵入性的(低耦合)

 <rabbit:template id="rabbitTemplate"  connection-factory="cachingConnectionFactory"
exchange="sslexchange" channel-transacted="true"/>
@Transactional
public void doSomething() {
     ApplicationContext context =
             new GenericXmlApplicationContext("spring-amqp-test.xml");
     RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
     String incoming = (String) rabbitTemplate.receiveAndConvert();
     // do some more database processing...
     String outgoing = processInDatabaseAndExtractReply(incoming);
     //数据库操作中如果失败了,outgoing这条消息不会被发送,incoming消息也会返回到broker服务器中,因为这是一条事务链。
    //可做XA事务,在消息传送与数据库访问中共享事务。
    rabbitTemplate.convertAndSend(outgoing);
}
private String processInDatabaseAndExtractReply(String incoming){
     return incoming;
}

异步使用案例(外部事务)

<bean id="rabbitTxManage" class="org.springframework.amqp.rabbit.transaction.RabbitTransactionManager">
     <property name="connectionFactory" ref="cachingConnectionFactory"></property>
</bean>
<rabbit:listener-container  connection-factory="cachingConnectionFactory" transaction-manager="rabbitTxManage" channel-transacted="true">
     <rabbit:listener ref="foo" method="onMessage" queue-names="rabbit-ssl-test"/> 
</rabbit:listener-container>

在容器中配置事务时,如果提供了transactionManager,channelTransaction必须为true;如果为false,外部的事务仍然可以提供给监听容器,造成的影响是在回滚的业务操作中也会提交消息传输的操作。

使用事务有两个问题:

Ø  一是会阻塞,发布者必须等待broker处理每个消息。如果发布者知道在broker死掉之前哪些消息没有被处理就足够了。

Ø  第二个问题是事务是重量级的,每次提交都需要fsync(),需要耗费大量的时间。

confirm模式下,broker将会确认消息并处理。这种模式下是异步的,生产者可以流水式的发布而不用等待broker,broker可以批量的往磁盘写入。

发布确认

发布确认必须配置在CachingConnectionFactory上

<bean id="cachingConnectionFactory" 
class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
     <property name="host" value="192.168.111.128"></property>
     <property name="port" value="5672"></property>
     <property name="username" value="admin"/>
     <property name="password" value="admin"/>
     <property name="publisherConfirms" value="true"/>
     <property name="publisherReturns" value="true"/>
</bean>

若使用confirm-callback或return-callback,必须要配置publisherConfirms或publisherReturns为true

每个rabbitTemplate只能有一个confirm-callback和return-callback

 //确认消息是否到达broker服务器,也就是只确认是否正确到达exchange中即可,只要正确的到达exchange中,broker即可确认该消息返回给客户端ack。
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
     @Override
     public void confirm(CorrelationData correlationData, boolean ack, String cause) {
         if (ack) {
             System.out.println("消息确认成功");
         } else {
             //处理丢失的消息(nack)
            System.out.println("消息确认失败");
         }
     }
});

使用return-callback时必须设置mandatory为true,或者在配置中设置mandatory-expression的值为true,可针对每次请求的消息去确定’mandatory’的boolean值,只能在提供’return -callback’时使用,与mandatory互斥。

 rabbitTemplate.setMandatory(true);
//确认消息是否到达broker服务器,也就是只确认是否正确到达exchange中即可,只要正确的到达exchange中,broker即可确认该消息返回给客户端ack。
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
     @Override
     public void returnedMessage(Message message, int replyCode, String replyText,
                                 String exchange, String routingKey) {
//重新发布
RepublishMessageRecoverer recoverer = new RepublishMessageRecoverer(errorTemplate,"errorExchange", "errorRoutingKey");
Throwable cause = new Exception(new Exception("route_fail_and_republish"));
recoverer.recover(message,cause);
         System.out.println("Returned Message:"+replyText);
     }
}); errorTemplate配置:
<rabbit:queue id="errorQueue" name="errorQueue" auto-delete="false" durable="true">
<rabbit:queue-arguments>
<entry key="x-ha-policy" value="all"/>
<entry key="ha-params" value="1"/>
<entry key="ha-sync-mode" value="automatic"/>
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:direct-exchange id="errorExchange" name="errorExchange" auto-delete="false" durable="true">
<rabbit:bindings>
<rabbit:binding queue="errorQueue" key="errorRoutingKey"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="200" />
<property name="maxInterval" value="30000" />
</bean>
</property>
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="5"/>
</bean>
</property>
</bean>
<rabbit:template id="errorTemplate" connection-factory="cachingConnectionFactory" exchange="errorExchange" queue="errorQueue" routing-key="errorRoutingKey" retry-template="retryTemplate" />

同一个连接不同channel使用事务和发布确认

private RabbitTemplate rabbitTemplate;

private TransactionTemplate transactionTemplate;
 @Before
public void init() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
    connectionFactory.setHost("192.168.111.128");
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("admin");
    connectionFactory.setPassword("admin");
    template = new RabbitTemplate(connectionFactory);
    template.setChannelTransacted(true);
    RabbitTransactionManager transactionManager = new RabbitTransactionManager(connectionFactory);
    transactionTemplate = new TransactionTemplate(transactionManager);
    connectionFactory.setPublisherConfirms(true);
    rabbitTemplate = new RabbitTemplate(connectionFactory);
}
//发布确认测试
@Test
public void testPublishConfirm(){
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
        @Override
        public void confirm(CorrelationData correlationData, boolean ack, String cause) {
            if(ack){
                System.out.println("消息确认成功");
            }else{
                System.out.println("消息确认失败");
            }
        }
    });
    //发送到一个不存在的exchange,则会触发发布确认
    rabbitTemplate.convertAndSend("asd","aaa","message");
    String message = (String) rabbitTemplate.receiveAndConvert(ROUTE);
    assertEquals("message",message);
}
//事务测试
@Test
public void testSendAndReceiveInTransaction() throws Exception {
    //由于有spring的事务参与,而发送操作在提交事务时,是不允许除template的事务有其他事务的参与,所以这里不会提交
    //队列中就没有消息,所以在channel.basicGet时命令返回的是basic.get-empty(队列中没有消息时),而有消息时,返回basic.get-ok
    String result = transactionTemplate.execute(new TransactionCallback<String>() {
        @Override
        public String doInTransaction(TransactionStatus status) {
            template.convertAndSend(ROUTE, "message");
            return (String) template.receiveAndConvert(ROUTE);
        }
    });
    //spring事务完成,对其中的操作需要提交,发送与接收操作被认为是一个事务链而提交
    assertEquals(null, result);
    //这里的执行不受spring事务的影响
    result = (String) template.receiveAndConvert(ROUTE);
    assertEquals("message", result);
}
转载:https://my.oschina.net/lzhaoqiang/blog/670749

学习:http://www.kancloud.cn/longxuan/rabbitmq-arron/117518

amqp事务的更多相关文章

  1. 学习RabbitMQ(三):AMQP事务机制

    本文转自:http://m.blog.csdn.net/article/details?id=54315940 在使用RabbitMQ的时候,我们可以通过消息持久化操作来解决因为服务器的异常奔溃导致的 ...

  2. RabbitMQ AMQP 事务机制

    1,在之前的文章中介绍了RabbitMQ的五种队列形式 其中,在工作队列中,为了保证消费者的公平性,采用了channel.basicQos(1),保证了每次只发一条消息给消费者消费,并且使用手动签收的 ...

  3. RabbitMQ介绍2 - AMQP协议

    这一节介绍RabbitMQ的一些概念,当然也是AMQP协议的概念.官方网站也有详细解释,包括协议的命令: http://www.rabbitmq.com/tutorials/amqp-concepts ...

  4. RabbitMQ---9、消息确认机制(事务+Confirm)

    转载至:https://blog.csdn.net/u013256816/article/details/55515234 参考资料:https://www.cnblogs.com/520playbo ...

  5. RabbitMQ 之消息确认机制(事务+Confirm)

    概述 在 Rabbitmq 中我们可以通过持久化来解决因为服务器异常而导致丢失的问题,除此之外我们还会遇到一个问题:生产者将消息发送出去之后,消息到底有没有正确到达 Rabbit 服务器呢?如果不错得 ...

  6. rabbitmq 持久化 事务 发送确认模式

    部分内容来自:http://blog.csdn.net/hzw19920329/article/details/54315940 http://blog.csdn.net/hzw19920329/ar ...

  7. RabbitMQ学习之:(十一)AMQP.0-10规范,中文翻译1,2,3章 (转载)

    From:http://blog.sina.com.cn/s/blog_4aba0c8b0100p6ho.html From: http://blog.sina.com.cn/s/blog_4aba0 ...

  8. rabbitmq(1)-入门

    参考: documentation: https://www.rabbitmq.com/documentation.htmldemo: https://www.rabbitmq.com/getstar ...

  9. RabbitMQ集群和失败处理

    RabbitMQ内建集群的设计用于完成两个目标:允许消费者和生产者在RabbitMQ节点在奔溃的情况下继续运行,以及通过添加更多的节点来线性扩展消息通信的吞吐量.当失去一个RabbitMQ节点时客户端 ...

随机推荐

  1. 044. asp.net主题之二为主题添加CSS样式和动态加载主题

    1. 新建任意一个网站, 默认主页为Default.aspx, 增加一个App_Themes目录, 用于存储主题, 添加一个MyTheme的主题, 在MyTheme主题下添加一个样式表文件, 默认名称 ...

  2. Mac系统终端命令行不执行命令 总出现command not found解决方法

    配置过安卓开发环境,改过bash_profile这个文件,最后不知怎么的只有cd命令能执行,我猜测可能修改bash_profile文件后没有保存 导致的     保存命令是:  source .bas ...

  3. NULL指针、零指针、野指针

    1.1.空指针 如果 p 是一个指针变量,则 p = 0; p = 0L; p = '\0'; p = 3 - 3; p = 0 * 17;p=(void*)0; 中的任何一种赋值操作之后, p 都成 ...

  4. 加密配置文件(App.Config和Web.config)中connectionStrings通用方法

    1. 背景:根据项目的要求,需要对配置文件配置的数据库连接字符串进行加密,也就是对ConnectinString节点的内容进行加密存储,同时考虑到代码使用连接字符串不需要进行更改,C#会自动对加密的内 ...

  5. PHP 获取指定目录下所有文件(包含子目录)

    PHP 获取指定目录下所有文件(包含子目录) //glob — 寻找与模式匹配的文件路径 $filter_dir = array('CVS', 'templates_c', 'log', 'img', ...

  6. HTML5常用的方法

    1.html禁止手机页面放大缩小 在页面head中加入<meta name="viewport" content="width=device-width, init ...

  7. 在UIView上添加tableView设置代理属性

  8. jquery mobile radio,checkbox button 样式设置

    <label><input type=checkbox ></label>,<input type=checkbox id="checkbox &q ...

  9. PHP表单数据验证

    背景: 在上次项目的时候,一直不明白为什么要对数据验证,我能保证我每次请求的数据都是合法的,但是在后面的时候,原来“用户”并不是那样听话,他总是要给我们找麻烦,然后可能让我们的服务器崩掉.但是只对单个 ...

  10. APP开发和web开发的区别

    1.web开发: 1.浏览器直接访问,协议是http,基本都是PC访问,也就是在家和办公场合,网络比较好. 2.web如果多个服务集成的话使用的是ESB来统一集成 2.APP开发 1.手机app客户端 ...