可参考https://blog.youkuaiyun.com/hzw19920329/article/details/54340711
在rabbitmq中,可以通过持久化数据,解决服务器异常之后数据丢失问题。
消费者也可以通过手动应答来告知消息队列有没有收到消息。
但是生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器,默认情况下是不知道的。
两种方式:
AMQP实现了事务机制
confirm
事务机制,针对生产者,消费者无需修改
public class Provider {
private static final String QUEUE_NAME="queue_tx";
public static void main(String[] args) throws Exception{
Connection conn = RabbitMqUtil.getConnection();
Channel channel = conn.createChannel();
try {
//开启事务
channel.txSelect();
channel.basicPublish("", QUEUE_NAME, null, "message".getBytes());
//提交
channel.txCommit();
} catch (Exception e) {
//回滚
System.out.println("异常,数据回滚");
channel.txRollback();
}
finally {
channel.close();
conn.close();
}
}
}
这种方式会导致发送消息的同时还夹杂着其他的请求,降低了吞吐量。
confirm模式
生产者将channel设置成confirm模式,一旦channel进入confirm模式,所有在该channel上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitmq就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,rabbitmq回传给生产者的确认消息中的deliver-tag包含了确认消息的序列号,此外rabbitmq也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式可以是异步的。
开启confirm模式:channel.confirmSelect()
当生产者发送消息给消息队列之后,可以通过channel.waitForConfirm方法来确认消息是否已经送达成功,返回true说明送达成功,false则失败。
生产者代码
public class Provider {
private static final String QUEUE_NAME = "confirm_queue";
public static void main(String[] args) throws Exception {
Connection conn = RabbitMqUtil.getConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//开启confirm模式
channel.confirmSelect();
//发送消息
for(int i=0; i<10; i++) {
channel.basicPublish("", QUEUE_NAME, null, ("first confirm message"+i).getBytes());
}
//判断消息是否发送成功
if(channel.waitForConfirms()) {
System.out.println("发送消息成功");
}
channel.close();
conn.close();
}
}
注意,如果一个队列已经被设置为事务模式,那么就不能再将其设置成confirm模式了。
异步模式
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序列号),我们需要为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录,从程序运行效率上来说,unconfirm集合最好采用有序集合SortedSet存储。
public class Provider_asyn {
private static final String QUEUE_NAME = "confirm_queue_asyn";
public static void main(String[] args) throws Exception {
Connection conn = RabbitMqUtil.getConnection();
Channel channel = conn.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.confirmSelect();
//存放发送消息的消息编号
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
//添加channel的监听
channel.addConfirmListener(new ConfirmListener() {
//发送不成功执行,deliveryTag为消息的编号,multiple表示是否是多条,true表示批量发送确认消息,表示在此消息编号之前的所有消息都已经接收到了
//通过该方法可以用于实现失败消息重发,保持消息接口幂等性。根据具体的业务逻辑实现。
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
if(multiple) {
System.out.println("发送失败,multiple:"+multiple);
confirmSet.headSet(deliveryTag+1).clear();
}
else {
confirmSet.remove(deliveryTag);
}
}
//发送成功执行,deliveryTag为消息的编号,multiple表示是否是多条
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// TODO Auto-generated method stub
if(multiple) {
System.out.println("发送成功,multiple:"+multiple);
confirmSet.headSet(deliveryTag+1).clear();
}
else {
confirmSet.remove(deliveryTag);
}
}
});
while(true) {
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, "异步消息发送".getBytes());
confirmSet.add(seqNo);
}
}
}