rabbitmq学习笔记8 : 事务管理

一、概念

Rabbitmq的事务是针对于发送端来说的,有两种事务机制,一种是AMQP事务,另一种是将channel设置成confirm事务模式(同步和异步两种)。

二、AMQP事务

  1. AMQP提供三个方法进行事务管理,分别是txSelect()用于设置事务、txCommit()用于提交事务、txRollback()用于回滚事务
  2. 代码实现(基于简单队列做例子)
  • 创建MQ连接工具类
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();
    }
}
  • 执行上面两个类的main方法,可以看到消息接收者在5秒后才接收到数据,这是因为消息生产者休眠了5秒后才进行事务提交。如果在提交之前,抛出一个异常,这时进行事务回滚,此时并不会发送消息

 

三、confirm事务模式(需自己实现事务回滚)

信道设置成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);
        }
    }
}

 

你可能感兴趣的:(rabbitmq)