1.Rabbitmq的消息确认机制(事务+confirm(证实))
在rabbitmq中我们可以通过持久化数据解决rabbitmq服务器异常导致的数据丢失问题,但又有了新问题,生产者将消息发送出去之后,消息到底有没有到达rabbitmq服务器?默认情况下是不知道的。
两种方式:
AMQP实现了事务机制
Confirm模式
2.事务机制:
txSelect txCommit txRollback
txSelect:用户将当前channel设置成transaction模式
txCommit:用于提交事务
txRollback:回滚事务
Send.java
public class Send {
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 t = 9/0; 测试回滚
channel.txCommit();
System.out.println("成功了!");
}catch(Exception e){
channel.txRollback();
System.out.println("回滚了!");
System.out.println(e.getMessage());
}
channel.close();
connection.close();
}
}
TxReceive.java
public class TxReceive {
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);
channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("recv[tx] msg:"+new String(body));
}
});
}
}
Confirm模式:
生产者端confirm模式的实现原理:
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
Confirm模式最大的好处在于它是异步的。
开启confirm模式:channel.confirmSelect()
编程模式:
1.普通 发一条 waitForConfirms()
Send1.java
/**
* 普通模式
*/
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模式
//注意:不能重复设置模式 如果设置了事务模式,则不能再设置成confirm模式
channel.confirmSelect();
String msgString = "hello confirm message!";
channel.basicPublish("",QUEUE_NAME,null,msgString.getBytes());
if (!channel.waitForConfirms()){
System.out.println("message send failed!");
}else {
System.out.println("message send ok!");
}
channel.close();
connection.close();
}
}
Receive.java
public class Receive {
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, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("recv[confirm] msg:"+new String(body));
}
});
}
}
2.批量的 发一批 waitForConfirms()
Send2.java
/**
* 批量模式
*/
public class Send2 {
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模式
//注意:不能重复设置模式 如果设置了事务模式,则不能再设置成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.out.println("message send failed!");
}else {
System.out.println("message send ok!");
}
channel.close();
connection.close();
}
}
3.异步的 confirm模式:提供一个回调方法
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条 (multiple=false)或多条(multiple=true)记录。从程序运行效率上来看,这个unconfirm集合最好采用有序集合SortedSet存储结构。
Send3.java
public class Send3 {
private static final String QUEUE_NAME = "test_queue_confirm3";
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();
//存放未确定的消息
SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
//通道添加监听
channel.addConfirmListener(new ConfirmListener() {
//没有问题的handleAck
@Override
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);
}
}
@Override
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);
}
}
}