事务消息与数据库的事务类似,只是MQ的消息是要保证消息是否会全部发送成功,防止消息丢失的一种策略。
RabbitMQ有两种策略来解决这个问题:
1.通过AMQP的事务机制实现
2.使用发送者确认模式实现
1.事务
事务的实现主要是对信道(Channel)的设置,主要方法如下:
1. channel.txSelect() 声明启动事务模式
2.channel.txCommit() 提交事务
3.channel.txRollback()回滚事务
1.事务性消息发送
开启事务之后必须手动channel.txCommit();提交或者channel.txRollback();回滚。
packagerabbitmq;importcom.rabbitmq.client.Channel;importcom.rabbitmq.client.Connection;importcom.rabbitmq.client.ConnectionFactory;public classProducer {public static Connection getConnection() throwsException {//创建连接工程,下面给出的是默认的case
ConnectionFactory factory = newConnectionFactory();
factory.setHost("192.168.99.100");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");returnfactory.newConnection();
}public static voidmain(String[] args) {
Connection connection= null;
Channel channel= null;try{
connection=getConnection();
channel=connection.createChannel();/*** 声明一个队列。
* 参数一:队列名称
* 参数二:是否持久化
* 参数三:是否排外 如果排外则这个队列只允许有一个消费者
* 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
* 参数五:队列的附加属性
* 注意:
* 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
* 2.队列名可以任意取值,但需要与消息接收者一致。
* 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。*/channel.queueDeclare("myQueue", true, false, false,null);//启动事务,必须用txCommit()或者txRollback()回滚
channel.txSelect();//假设这里处理业务逻辑
String message = "hello,message!";/*** 发送消息到MQ
* 参数一:交换机名称,为""表示不用交换机
* 参数二:为队列名称或者routingKey.当指定了交换机就是routingKey
* 参数三:消息的属性信息
* 参数四:消息内容的字节数组*/channel.basicPublish("", "myQueue", null, message.getBytes());//提交事务
channel.txCommit();
}catch(Exception e) {
e.printStackTrace();
}finally{try{if (channel != null) {//回滚。如果未异常会提交事务,此时回滚无影响
channel.txRollback();
channel.close();
}if (connection != null) {
connection.close();
}
}catch(Exception ignore) {//ignore
}
}
}
}
测试可以注释掉提交事务的代码发现mq不会新增消息。
2.消费者事务测试
经测试,自动确认模式下。即使事务不提交,也会读取到消息并从队列移除。也就是暂时得出的结论是事务对消费者无效。
packagerabbitmq;importjava.io.IOException;importjava.util.concurrent.TimeoutException;importcom.rabbitmq.client.Channel;importcom.rabbitmq.client.Connection;importcom.rabbitmq.client.ConnectionFactory;importcom.rabbitmq.client.DefaultConsumer;importcom.rabbitmq.client.Envelope;importcom.rabbitmq.client.AMQP.BasicProperties;public classConsumer {public staticConnectionFactory getConnectionFactory() {//创建连接工程,下面给出的是默认的case
ConnectionFactory factory = newConnectionFactory();
factory.setHost("192.168.99.100");
factory.setPort(5672);
factory.setUsername("guest");
factory.setPassword("guest");
factory.setVirtualHost("/");returnfactory;
}public static void main(String[] args) throwsIOException, TimeoutException {
ConnectionFactory connectionFactory=getConnectionFactory();
Connection newConnection= null;
Channel createChannel= null;try{
newConnection=connectionFactory.newConnection();
createChannel=newConnection.createChannel();/*** 声明一个队列。
* 参数一:队列名称
* 参数二:是否持久化
* 参数三:是否排外 如果排外则这个队列只允许有一个消费者
* 参数四:是否自动删除队列,如果为true表示没有消息也没有消费者连接自动删除队列
* 参数五:队列的附加属性
* 注意:
* 1.声明队列时,如果已经存在则放弃声明,如果不存在则会声明一个新队列;
* 2.队列名可以任意取值,但需要与消息接收者一致。
* 3.下面的代码可有可无,一定在发送消息前确认队列名称已经存在RabbitMQ中,否则消息会发送失败。*/createChannel.queueDeclare("myQueue", true, false, false,null);/*** 开启事务
* 消费者开启事务后,即使不提交也会获取到消息并且从队列删除。
* 结论:
* 事务对消费者没有任何影响*/createChannel.txSelect();/*** 接收消息。会持续坚挺,不能关闭channel和Connection
* 参数一:队列名称
* 参数二:消息是否自动确认,true表示自动确认接收完消息以后会自动将消息从队列移除。否则需要手动ack消息
* 参数三:消息接收者的标签,用于多个消费者同时监听一个队列时用于确认不同消费者。
* 参数四:消息接收者*/createChannel.basicConsume("myQueue", true, "", newDefaultConsumer(createChannel) {
@Overridepublic voidhandleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,byte[] body) throwsIOException {
String string= new String(body, "UTF-8");
System.out.println("接收到d消息: -》 " +string);
}
});
}catch(Exception e) {
e.printStackTrace();
}finally{
}
}
}
上面是自动确认模式的消费者,不受事务的影响。
如果是手动确认消息的消费者,在开启事务下,必须手动commit,否则不会移除消息。
如下:手动确认模式+事务的用法
packagerabbitmq;importjava.io.IOException;import