消息确认机制
在rabbitmq中,我们可以通过持久化数据,解决rabbitmq服务器异常的数据丢失问题.
问题:生产者将消息发送出去之后,消息到底有没有到底rabbitmq服务器,默认情况下是不知道的
两种方式:
- AMQP实现事务机制.
- Confirm模式.
事务机制
txSelect:用于将当前channel设置成transation模式
txCommit:用与提交事务
txRollback:回滚事务
package com.mmr.rabbit.transation;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
public class TxSend {
private static final String QUEUE_NAME="test_queue_tx";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msgString = "hello tx message";
try{
channel.txSelect();
channel.basicPublish("",QUEUE_NAME, null, msgString.getBytes());
int i = 1/0;
System.err.println("send:"+i+msgString);
channel.txCommit();
}catch(Exception e){
channel.txRollback();
System.err.println("send message txRollback");
}finally{
channel.close();
connection.close();
}
}
}
package com.mmr.rabbit.transation;
import java.io.IOException;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class TxRecv {
private static final String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
final Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.err.println("[1] Recv:" + message + "");
}
});
}
}
Confirm模式
生产者端confirm模式的实现原理
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入到磁盘之后发出,broker回传给生产者确认消息肿deliver-tag域包含了确认消息的序列,此外broker也可以这只basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理.
confirm模式最大好处是异步,异常返回Nack
开启confirm模式
channel.confirmSelect()
编程模式:
- 普通 发一条 waitForConfirms();
- 批量的 发一批 waitForConfirms();
- 异步confirm模式,提供一个回调方法.
环境搭建
单条/批量模式(串行)
package com.mmr.rabbit.confirm;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 普通模式
* @author GCX
*
*/
public class Send1 {
private static final String QUEUE_NAME="test_queue_confirm1";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false,false,null);
//生产者调用confirmSelect 将channel设置为confirm模式 注意
channel.confirmSelect();
String msgString = "hello confirm message!";
channel.basicPublish("", QUEUE_NAME, null, msgString.getBytes());
if(!channel.waitForConfirms()){
System.err.println("message send failed");
}else{
System.err.println("message send success");
}
channel.close();
connection.close();
}
}
package com.mmr.rabbit.confirm;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
/**
* 批量
* @author GCX
*
*/
public class Send1 {
private static final String QUEUE_NAME="test_queue_confirm1";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false,false,false,null);
//生产者调用confirmSelect 将channel设置为confirm模式 注意
channel.confirmSelect();
String msgString = "hello confirm message!";
for(int i =0 ; i<10 ; i++){
channel.basicPublish("", QUEUE_NAME, null, msgString.getBytes());
}
if(!channel.waitForConfirms()){
System.err.println("message send failed");
}else{
System.err.println("message send success");
}
channel.close();
connection.close();
}
}
package com.mmr.rabbit.confirm;
import java.io.IOException;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class Recv1 {
private static final String QUEUE_NAME="test_queue_confirm1";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("[1] Recv:"+new String(body,"UTF-8"));
}
});
}
}
异步模式
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前channel的消息序列号),我们需要自己为每一个channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉对应的一条(multiple=false)或多条(multiple=true)记录,从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构.
package com.mmr.rabbit.confirm;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
public class Send3 {
private static final String QUEUE_NAME="test_queue_confirm3";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//生产者调用confirmSelect 将channel设置为confirm模式 注意
channel.confirmSelect();
//未确认的消息标识
final SortedSet<Long> confirmSet=Collections.synchronizedSortedSet(new TreeSet<Long>());
//通道添加监听
channel.addConfirmListener(new ConfirmListener() {
//没有问题的handleAck
public void handleAck(long deliveryTag, boolean multiple)
throws IOException {
if(multiple){
System.out.println("----handleAck----multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("----handleAck-----multiple false");
confirmSet.remove(deliveryTag);
}
}
//handleNack
public void handleNack(long deliveryTag, boolean multiple)
throws IOException {
if(multiple){
System.out.println("---handleNack------multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("--handleNack-------multiple false");
confirmSet.remove(deliveryTag);
}
}
});
String msgStr="ssssss";
while(true){
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msgStr.getBytes());
confirmSet.add(seqNo);
}
}
}
package com.mmr.rabbit.confirm;
import java.io.IOException;
import com.mmr.rabbit.util.ConnectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
public class Recv1 {
private static final String QUEUE_NAME="test_queue_confirm1";
public static void main(String[] args) throws Exception{
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("[1] Recv:"+new String(body,"UTF-8"));
}
});
}
}