事务机制
RabbitMQ中与事务机制有关的方法有三个,分别是Channel里面的txSelect(),txCommit()以及txRollback(),txSelect用于将当前Channel设置成是transaction模式,txCommit用于提交事务,txRollback用于回滚事务,在通过txSelect开启事务之后,我们便可以发布消息给broker代理服务器了,如果txCommit提交成功了,则消息一定是到达broker了,如果在txCommit执行之前broker异常奔溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback回滚事务了;
public class Producer {
public final static String QUEUE = "queue";
public final static String EXCHANGE = "com.lyyz.exchange";
public final static String ROUTINGKEY = "routingKey";
public static String message = "producer message";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("root");
factory.setPassword("root");
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//channel.exchangeDeclare(EXCHANGE,"direct",true,true,null);
channel.queueDeclare(QUEUE,true,false,true,null);
try {
//设置成是transaction模式
System.out.println(channel.txSelect());
//channel.basicPublish(EXCHANGE,ROUTINGKEY, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
channel.basicPublish("",QUEUE,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
//long i = 1/0;
String a = null;
//boolean b = a.equals("aaa");
//提交
System.out.println(channel.txCommit());
} catch (Exception e) {
System.out.println("catch");
//回滚
System.out.println(channel.txRollback());
} finally {
channel.close();
connection.close();
}
}
}
输出结构
#method<tx.select-ok>()
#method<tx.commit-ok>()或者#method<tx.rollback-ok>()
RabbitMQ开启事务对性能的消耗很大。一般用Confirm机制实现消息确认。
生产者-确认机制
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息。
在channel 被设置成 confirm 模式之后,所有被 publish 的后续消息都将被 confirm(即 ack) 或者被nack一次。但是没有对消息被 confirm 的快慢做任何保证,并且同一条消息不会既被 confirm又被nack 。
- 普通confirm模式:每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了。
- 批量confirm模式:每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
- 异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法.
public class Producer {
public final static String QUEUE = "queue";
public final static String EXCHANGE = "com.lyyz.exchange";
public final static String ROUTINGKEY = "routingKey";
public static String message = "producer message";
public static Scanner sCanner = null;
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("root");
factory.setPassword("root");
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//producer confirm消息确认机制
channel.queueDeclare(QUEUE,true,false,false,null);
//开启confirm 模式
channel.confirmSelect();
//3,异步confirm模式:提供一个回调方法,服务端confirm了一条或者多条消息后Client端会回调这个方法。
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println("ack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
}
});
while(sysIn()){
//1,普通confirm模式:每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了。
channel.basicPublish("",QUEUE,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
//2,批量confirm模式:每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
// for(int i = 0;i<10;i++){
// channel.basicPublish("",QUEUE,MessageProperties.PERSISTENT_TEXT_PLAIN,(message+i).getBytes());
// }
// if(!channel.waitForConfirms(1000)){
// System.out.println("send message failed.");
// }else{
// System.out.println("send message success.");
// }
}
channel.close();
connection.close();
}
public static boolean sysIn(){
Scanner sCanner = new Scanner(System.in);
message = sCanner.nextLine();
return message.equals("")?false:true;
}
}
消费者-确认机制
为了保证消息从队列可靠地到达消费者,RabbitMQ提供消息确认机制(message acknowledgment)。消费者在声明队列时,可以指定noAck参数,当noAck=false时,RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。否则,RabbitMQ会在队列中消息被消费后立即删除它。
采用消息确认机制后,只要令noAck=false,消费者就有足够的时间处理消息(任务),不用担心处理消息过程中消费者进程挂掉后消息丢失的问题,因为RabbitMQ会一直持有消息直到消费者显式调用basicAck为止。
当noAck=false时,对于RabbitMQ服务器端而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息;一部分是已经投递给消费者,但是还没有收到消费者ack信号的消息。如果服务器端一直没有收到消费者的ack信号,并且消费此消息的消费者已经断开连接,则服务器端会安排该消息重新进入队列,等待投递给下一个消费者(也可能还是原来的那个消费者)。
RabbitMQ不会为未ack的消息设置超时时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消息的时间可以很久很久。
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("root");
factory.setPassword("root");
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE,true,false,false,null);
QueueingConsumer consumer = new QueueingConsumer(channel);
//设置成手动确认
channel.basicConsume(Producer.QUEUE,false,consumer);
while(true){
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("Consumer "+msg);
// 接收消息 回复 ack
//channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
//接收消息 回复 nack requeue 参数是否重新写入队列
channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
}
}
}