Rabbitmq的事务是针对于发送端来说的,有两种事务机制,一种是AMQP事务,另一种是将channel设置成confirm事务模式(同步和异步两种)。
public class RabbitmqUntil {
//获取连接
public static Connection getRabbitmqConnection() throws Exception{
//定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置服务地址
factory.setHost("127.0.0.1");
//设置端口(这里的端口号指定是AMQP协议所用的端口号)
factory.setPort(5672);
//设置数据库
factory.setVirtualHost("/test");
//设置用户名
factory.setUsername("test");
//设置密码
factory.setPassword("test");
return factory.newConnection();
}
}
public class AmqpSender {
//队列名称
final static String QUEUE_NAME = "amqp queue";
public static void sendMessage(String message)throws Exception{
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
Channel channel = rabbitmqConnection.createChannel();
/**
* 队列创建(如果队列存在,则不进行创建)
* 参数1:队列名
* 参数2:是否支持持久化,默认队列在内存中,重启会丢失。设置为true,会保存到erlang自带的数据库中
* 参数3:当连接关闭后是否自动删除队列;是否私有队列,如果私有,其他通道不能访问当前队列
* 参数4:当没有消费者,是否自动删除队列
* 参数5:其他参数(通过BasicProperties进行传输)
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//设置事务
channel.txSelect();
try{
//发布消息
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
//休眠5秒
Thread.sleep(5000);
//提交事务
channel.txCommit();
}catch (Exception e){
//事务回滚
channel.txRollback();
}finally {
//关闭资源
channel.close();
rabbitmqConnection.close();
}
}
public static void main(String[] args)throws Exception {
AmqpSender.sendMessage("hello I am amqp transaction");
}
}
注:该事务进行也可以进行批处理
public class AmqpReceiver {
//队列名称
final static String QUEUE_NAME = "amqp queue";
public static void receiveMessage() throws Exception{
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
final Channel channel = rabbitmqConnection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//创建消息接收者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听消息
channel.basicConsume(QUEUE_NAME,false,consumer);
}
public static void main(String[] args)throws Exception {
AmqpReceiver.receiveMessage();
}
}
信道设置成confirm模式后,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所匹配的队列后,broker就会发生一个确认给生产者(包含唯一id),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写人磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的系列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所以消息都已经得到了处理。可以看出confirm是自动提交回滚事务的,我们只需要监听事务是否成功并对其进行相应的处理即可。
1、confirm同步方法的实现(如果消息发送失败,10秒后重新推送)
public class ConfirmSender {
//队列名称
final static String QUEUE_NAME = "confirm queue";
/**
* @param message 发送的消息
* @param sendTime 用于休眠时间,表示休眠(sendTime/1000)秒后发送消息
*/
public static void sendMessage(String message,Long sendTime)throws Exception{
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
Channel channel = rabbitmqConnection.createChannel();
/**
* 队列创建(如果队列存在,则不进行创建)
* 参数1:队列名
* 参数2:是否支持持久化,默认队列在内存中,重启会丢失。设置为true,会保存到erlang自带的数据库中
* 参数3:当连接关闭后是否自动删除队列;是否私有队列,如果私有,其他通道不能访问当前队列
* 参数4:当没有消费者,是否自动删除队列
* 参数5:其他参数(通过BasicProperties进行传输)
*/
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//设置confirm模式
channel.confirmSelect();
//休眠一段时间后在发送
Thread.sleep(sendTime);
//发布消息
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
//如果成功
if(channel.waitForConfirms()){
System.out.println("推送消息成功");
}else{
//异常处理(10秒以后重推)
sendMessage(message,10000l);
}
//关闭资源
channel.close();
rabbitmqConnection.close();
}
public static void main(String[] args)throws Exception {
ConfirmSender.sendMessage("hello I am confirm",0l);
}
}
注:confirm模式会自动提交事务,通过channel.waitForConfirms()判断是否提交或回滚
public class ConfirmReceiver {
//队列名称
final static String QUEUE_NAME = "confirm queue";
public static void receiveMessage() throws Exception{
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
final Channel channel = rabbitmqConnection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//创建消息接收者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("confirm 同步:"+new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听消息
channel.basicConsume(QUEUE_NAME,false,consumer);
}
public static void main(String[] args)throws Exception {
ConfirmReceiver.receiveMessage();
}
}
2、confirm异步方法的实现(如果消息发送失败,10秒后重新推送)
public class ConfirmAsynSender {
//队列名称
final static String QUEUE_NAME = "confirm asyn queue";
/**
* @param message 发送的消息
* @param sendTime 用于休眠时间,表示休眠(sendTime/1000)秒后发送消息
*/
public static void sendMessage(final String message,Long sendTime)throws Exception {
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
Channel channel = rabbitmqConnection.createChannel();
/**
* 队列创建(如果队列存在,则不进行创建)
* 参数1:队列名
* 参数2:是否支持持久化,默认队列在内存中,重启会丢失。设置为true,会保存到erlang自带的数据库中
* 参数3:当连接关闭后是否自动删除队列;是否私有队列,如果私有,其他通道不能访问当前队列
* 参数4:当没有消费者,是否自动删除队列
* 参数5:其他参数(通过BasicProperties进行传输)
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//设置confirm模式
channel.confirmSelect();
//事务监听
channel.addConfirmListener(new ConfirmListener() {
//请求成功,multiple为true时表示批量提交
@Override
public void handleAck(long deliveryTay, boolean multiple) throws IOException {
System.out.println("消息发送成功 。。。。");
}
//请求失败
@Override
public void handleNack(long deliveryTay, boolean multiple) throws IOException {
System.out.println("消息发送失败 。。。。");
try {
sendMessage(message,10000l);
}catch (Exception e){
e.printStackTrace();
}
}
});
Thread.sleep(sendTime);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
}
public static void main(String[] args) throws Exception{
ConfirmAsynSender.sendMessage("I am confirm asyn",0l);
}
}
注:通过channel.addConfirmListener()实现异步通知
public class ConfirmAsynReceiver {
//队列名称
final static String QUEUE_NAME = "confirm asyn queue";
public static void receiveMessage() throws Exception{
//获取连接
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
//获取管道
final Channel channel = rabbitmqConnection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//创建消息接收者
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("confirm 异步:"+new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//监听消息
channel.basicConsume(QUEUE_NAME,false,consumer);
}
public static void main(String[] args)throws Exception {
ConfirmAsynReceiver.receiveMessage();
}
}
注:confirm的异步模式是通过channel.addConfirmListener()进行监听,并且实现两个方法handleAck()、handleNack()分别表示事务成功、失败。
关于deliveryTay、multiple的用法:
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序号)。我们可以维护两个消息序列号有序集合confirmSet和unConfirmSet。confirmSet用于记录所以消息的序列号,每次publish一条数据,confirmSet加一条数据,回调handleAck方法或者handleNack方法时,confirmSet集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。unConfirmSet用于记录发送失败的数据,每次在回调handleNack方法,在confrimSet删除掉数据之间,先将对于的数据添加到unConfirmSet集合中。
之所以这样设计,是因为当multiple为true时,此时是批量处理的,而deliveryTag是该批数据的最大元素,confirmSet每次处理都删除掉相应的数据,所以可以根据deliveryTag为最大值,取出后面所有的值,就是每一批次中的所有数据。
代码实现:
public class ConfirmSender {
public static void main(String[] args) throws Exception{
Connection rabbitmqConnection = RabbitmqUntil.getRabbitmqConnection();
Channel channel = rabbitmqConnection.createChannel();
channel.queueDeclare("confirm asyn queue",true,false,false,null);
channel.confirmSelect();
//存放所有消息的nextPublishSeqNo
final SortedSet confirmSet = Collections.synchronizedSortedSet(new TreeSet());
//存放失败消息的存放所有消息的nextPublishSeqNo,即未确认的序列号
final SortedSet unconfirmSet = Collections.synchronizedSortedSet(new TreeSet());
channel.addConfirmListener(new ConfirmListener() {
//请求成功,将相应的数据从集合中删除
@Override
public void handleAck(long deliveryTay, boolean multiple) throws IOException {
if(multiple){
confirmSet.headSet(deliveryTay+1).clear();
System.out.println("confirm multiple "+(deliveryTay+1) );
}else{
confirmSet.remove(deliveryTay);
System.out.println("confirm "+(deliveryTay) );
}
}
@Override
public void handleNack(long deliveryTay, boolean multiple) throws IOException {
if(multiple){
//将失败的消息序列号存放到unconfirmSet集合中
unconfirmSet.addAll(confirmSet.headSet(deliveryTay+1));
//将相应的数据从集合中删除
confirmSet.headSet(deliveryTay+1).clear();
System.out.println("confirm multiple "+(deliveryTay+1) );
}else{
//将失败的消息序列号存放到unconfirmSet集合中
unconfirmSet.add(deliveryTay);
//将相应的数据从集合中删除
confirmSet.remove(deliveryTay);
System.out.println("confirm "+(deliveryTay) );
}
}
});
String message="hello I am confirm asyn";
while(true){
long nextPublishSeqNo = channel.getNextPublishSeqNo();
channel.basicPublish("","confirm asyn queue",null,message.getBytes());
confirmSet.add(nextPublishSeqNo);
}
}
}