确认并且保证消息被送达,提供了两种方式:发布确认和事务。(两者不可同时使用)在channel为事务时,不可引入确认模式;同样channel为确认模式下,不可使用事务。
发布确认:
Confirms给客户端一种轻量级的方式,能够跟踪哪些消息被broker处理,哪些可能因为broker宕掉或者网络失败的情况而重新发布。
确认消息是否到达broker服务器,也就是只确认是否正确到达exchange中即可,只要正确的到达exchange中,broker即可确认该消息返回给客户端ack。
有两种方式:消息的确认和消息发送失败的回调。
消息确认:
在配置connectionFactory 中启用消息确认:
<bean id="connectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
<property name="host" value="${rabbitmq.host}" />
<property name="port" value="${rabbitmq.port}" />
<property name="username" value="${rabbitmq.username}" />
<property name="password" value="${rabbitmq.password}" />
<property name="virtualHost" value="${rabbitmq.virtualHost}" />
<!-- 缓存模式 CONNECTION CHANNEL,默认的缓存模式是CHANNEL。
当缓存模式是 CONNECTION时, 队列的自动声明等等 (参考 the section called “Automatic Declaration of Exchanges, Queues and Bindings”) 将不再支持。
在框架(如. RabbitTemplate) 中使用的通道将会可靠地返回到缓存中.如果在框架外创建了通道 (如.直接访问connection(s)并调用 createChannel() ),
你必须可靠地返回它们(通过关闭),也许需要在 finally 块中以防止耗尽通道.
-->
<property name="cacheMode" value="CHANNEL"/>
<!-- 默认通道缓存25,多线程环境中,较小的缓存意味着通道的创建和关闭将以很高的速率运行.加大默认缓存大小可避免这种开销
如果达到了限制,调用线程将会阻塞,直到某个通道可用或者超时, 在后者的情况中,将抛出 AmqpTimeoutException异常.-->
<property name="channelCacheSize" value="10"/>
<!-- channelCheckoutTimeout属性. 当此属性的值大于0时, channelCacheSize会变成连接上创建通道数目的限制. -->
<property name="channelCheckoutTimeout" value="200"/>
<!-- 发布确认必须配置在CachingConnectionFactory上 -->
<property name="publisherConfirms" value="true"/>
</bean>
模板配置消息确认回调方法:
<bean id="confirmCallback" class="com.convict.rabbitmq.spring.sync.callback.MsgSendConfirmCallBack"/>
<!-- 同步访问rabbitmq-->
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg name="connectionFactory" ref="connectionFactory"/>
<property name="exchange" value="${rabbitmq.exchangeName}"/>
<!-- 也可以在发送的时候手动设置routing key -->
<property name="routingKey" value="${rabbitmq.msg.routing}"/>
<!--消息确认回调 -->
<property name="confirmCallback" ref="confirmCallback"/>
</bean>
MsgSendConfirmCallBack 代码如下:
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
System.out.println("消息确认成功");
} else {
//处理丢失的消息
System.out.println("消息确认失败,"+cause);
}
}
}
CachingConnectionFactory配置中启用回调
<property name="publisherReturns" value="true"/>
定义回调方法,并在消息模板中指定
<bean id="sendReturnCallback" class="com.convict.rabbitmq.spring.sync.callback.MsgSendReturnCallback"/>
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg name="connectionFactory" ref="connectionFactory"/>
<property name="exchange" value="${rabbitmq.exchangeName}"/>
<!-- 也可以在发送的时候手动设置routing key -->
<property name="routingKey" value="${rabbitmq.msg.routing}"/>
<property name="returnCallback" ref="sendReturnCallback"/>
</bean>
MsgSendReturnCallback 方法如下:
public class MsgSendReturnCallback implements RabbitTemplate.ReturnCallback {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
String msgJson = new String(message.getBody());
System.out.println("Returned Message:"+msgJson);
}
}
事务
Spring AMQP做的不仅仅是回滚事务,而且可以手动拒绝消息,如当监听容器发生异常时是否重新入队。
持久化的消息是应该在broker重启前都有效。如果在消息有机会写入到磁盘之前broker宕掉,消息仍然会丢失。在某些情况下,这是不够的,发布者需要知道消息是否处理正确。简单的解决方案是使用事务,即提交每条消息。
使用事务有两个问题:
一、会阻塞,发布者必须等待broker处理每个消息。如果发布者知道在broker死掉之前哪些消息没有被处理就足够了。
二、事务是重量级的,每次提交都需要fsync(),需要耗费大量的时间。
confirm模式下,broker将会确认消息并处理。这种模式下是异步的,生产者可以流水式的发布而不用等待broker,broker可以批量的往磁盘写入。
场景:业务处理伴随消息的发送,业务处理失败(事物回滚)后要求消息不发送。rabbitmq 使用调用者的外部事物。
示例:使用spring事物,来处理业务并发送消息,并且把消息记录到数据库中防止消息的丢失。
代码说明:
1、rabbitTemplate 使用外部事物配置
<!-- 同步访问rabbitmq-->
<bean id="rabbitTemplate" class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<constructor-arg name="connectionFactory" ref="connectionFactory"/>
<property name="exchange" value="${rabbitmq.exchangeName}"/>
<!-- 也可以在发送的时候手动设置routing key -->
<property name="routingKey" value="${rabbitmq.msg.routing}"/>
<!-- 使用外部事物 -->
<property name="channelTransacted" value="true"/>
</bean>
2、创建消息表 CREATE TABLE tb_msg(id BIGINT,tag INT,txt TEXT); 使用 spring-mybatis 搭建表的操作,配置事物。
3、模拟 MsgService 中业务处理方法如下(事物切面在service层):
public void addMsg(Msg msg) throws Exception {
//模拟业务操作
Thread.sleep(1000);
//消息保存到数据库,和业务操作为同一个事物
msgDao.insert(msg);
//发送消息
rabbitTemplate.convertAndSend(msg.getTxt());
if (msg.getId() == null || msg.getId() % 2 == 0) {//事物,偶数的回滚
throw new Exception();
}
}
当事物回滚,消息也不会发送。
注意:一个具有事务的channel不能放入到确认模式,同样确认模式下的channel不能用事务。
完整code:http://download.csdn.net/detail/convict_eva/9625428
参考:http://my.oschina.net/lzhaoqiang/blog/670749