事务
事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
- channel.txSelect()声明启动事务模式;
- channel.txComment()提交事务;
- channel.txRollback()回滚事务;
在发送消息之前,需要声明channel为事务模式,提交或者回滚事务即可。
开启事务后,客户端和RabbitMQ之间的通讯交互流程:
- 客户端发送给服务器Tx.Select(开启事务模式)
- 服务器端返回Tx.Select-Ok(开启事务模式ok)
- 推送消息
- 客户端发送给事务提交Tx.Commit
- 服务器端返回Tx.Commit-Ok
以上就完成了事务的交互流程,如果其中任意一个环节出现问题,就会抛出IoException移除,这样用户就可以拦截异常进行事务回滚,或决定要不要重复消息。
那么,既然已经有事务了,为何还要使用发送方确认模式呢,原因是因为事务的性能是非常差的。根据相关资料,事务会降低2~10倍的性能。
发送方确认模式
基于事务的性能问题,RabbitMQ团队为我们拿出了更好的方案,即采用发送方确认模式,该模式比事务更轻量,性能影响几乎可以忽略不计。
原理:生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),由这个id在生产者和RabbitMQ之间进行消息的确认。
不可路由的消息,当交换器发现,消息不能路由到任何队列,会进行确认操作,表示收到了消息。如果发送方设置了mandatory模式,则会先调用addReturnListener监听器。
可路由的消息,要等到消息被投递到所有匹配的队列之后,broker会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker回传给生产者的确认消息中delivery-tag域包含了确认消息的序列号。
confirm模式最大的好处在于他可以是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方法来处理该确认消息,如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条nack消息,生产者应用程序同样可以在回调方法中处理该nack消息决定下一步的处理。
Confirm的三种实现方式:
方式一:channel.waitForConfirms()普通发送方确认模式;消息到达交换器,就会返回true。
方式二:channel.waitForConfirmsOrDie()批量确认模式;使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未到达交换器就会抛出IOException异常。
方式三:channel.addConfirmListener()异步监听发送方确认模式;
普通发送发确认模式
public class ProducerConfirm {
public final static String EXCHANGE_NAME = "producer_confirm";
private final static String ROUTE_KEY = "error";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/**
* 创建连接连接到RabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
// 设置MabbitMQ所在主机ip或者主机名
factory.setHost("127.0.0.1");
// 创建一个连接
Connection connection = factory.newConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 指定转发
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
String message = new String(body);
System.out.println("RabbitMq返回的replyCode: "+replyCode);
System.out.println("RabbitMq返回的replyText: "+replyText);
System.out.println("RabbitMq返回的exchange: "+exchange);
System.out.println("RabbitMq返回的routingKey: "+routingKey);
System.out.println("RabbitMq返回的message: "+message);
}
});
// 启用发送者确认模式
channel.confirmSelect();
//所有日志严重性级别
for(int i=0;i<2;i++){
// 发送的消息
String message = "Hello World_"+(i+1);
//参数1:exchange name
//参数2:routing key
channel.basicPublish(EXCHANGE_NAME, ROUTE_KEY, true,null, message.getBytes());
System.out.println(" Sent Message: [" + ROUTE_KEY +"]:'"+ message + "'");
if(channel.waitForConfirms()){
System.out.println("send success");
}else{
System.out.println("send failure");
}
}
// 关闭频道和连接
channel.close();
connection.close();
}
}
批量确认模式
public class ProducerBatchConfirm {
public final static String EXCHANGE_NAME = "producer_wait_confirm";
private final static String ROUTE_KEY = "error";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/**
* 创建连接连接到RabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ所在主机ip或者主机名
factory.setHost("127.0.0.1");
// 创建一个连接
Connection connection = factory.newConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 指定转发
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
String message = new String(body);
System.out.println("RabbitMq返回的replyCode: "+replyCode);
System.out.println("RabbitMq返回的replyText: "+replyText);
System.out.println("RabbitMq返回的exchange: "+exchange);
System.out.println("RabbitMq返回的routingKey: "+routingKey);
System.out.println("RabbitMq返回的message: "+message);
}
});
// 启用发送者确认模式
channel.confirmSelect();
//所有日志严重性级别
for(int i=0;i<2;i++){
// 发送的消息
String message = "Hello World_"+(i+1);
//参数1:exchange name
//参数2:routing key
channel.basicPublish(EXCHANGE_NAME, ROUTE_KEY, true,null, message.getBytes());
System.out.println(" Sent Message: [" + ROUTE_KEY +"]:'"+ message + "'");
}
channel.waitForConfirmsOrDie();
// 关闭频道和连接
channel.close();
connection.close();
}
}
异步确认:
public class ProducerConfirmAsync {
public final static String EXCHANGE_NAME = "producer_async_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/**
* 创建连接连接到MabbitMQ
*/
ConnectionFactory factory = new ConnectionFactory();
// 设置MabbitMQ所在主机ip或者主机名
factory.setHost("127.0.0.1");
// 创建一个连接
Connection connection = factory.newConnection();
// 创建一个信道
Channel channel = connection.createChannel();
// 指定转发
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
// 启用发送者确认模式
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple)
throws IOException {
System.out.println("deliveryTag:"+deliveryTag
+",multiple:"+multiple);
}
public void handleNack(long deliveryTag, boolean multiple)
throws IOException {
}
});
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode, String replyText,
String exchange, String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
String message = new String(body);
System.out.println("RabbitMq路由失败: "+routingKey+"."+message);
}
});
String[] severities={"error","warning"};
for(int i=0;i<100;i++){
String severity = severities[i%2];
// 发送的消息
String message = "Hello World_"+(i+1)+("_"+System.currentTimeMillis());
channel.basicPublish(EXCHANGE_NAME, severity, true,
MessageProperties.PERSISTENT_BASIC, message.getBytes());
System.out.println("----------------------------------------------------");
System.out.println(" Sent Message: [" + severity +"]:'"+ message + "'");
}
// 关闭频道和连接
//channel.close();
//connection.close();
}
}