【问题标题】:RabbitMQ sending message in transactionRabbitMQ 在事务中发送消息
【发布时间】:2016-11-22 19:13:35
【问题描述】:

是否可以在事务中运行下面的代码,这样如果在业务处理中抛出异常我们可以回滚我们发送到队列的消息?

rabbitTemplate.convertAndSend("queue1", data);

//do some processing

rabbitTemplate.convertAndSend("queue2", data);

如果在将消息发送到 queue1 后出现问题,但我们无法将消息发送到 queue2,则需要这样做。或者如果在向队列发送消息时出现网络问题或其他问题。

【问题讨论】:

    标签: spring-boot rabbitmq spring-amqp


    【解决方案1】:

    如果此代码在侦听器容器线程(onMessage()@RabbitListener)上运行,并且容器和模板都具有setChannelTransacted(true),则发布(和交付)将在同一个事务中运行;抛出异常会导致一切都回滚。

    如果这是在某个任意的 java 类中(不在容器线程上运行),那么您需要在方法运行之前启动事务...

        @Transactional
        public void send(String in) {
            this.template.convertAndSend("foo", in);
            if (in.equals("foo")) {
                throw new RuntimeException("test");
            }
            this.template.convertAndSend("bar", in);
        }
    

    这是一个完整的 Spring Boot 应用程序,演示了该功能...

    @SpringBootApplication
    @EnableTransactionManagement
    public class So40749877Application {
    
        public static void main(String[] args) {
            ConfigurableApplicationContext context = SpringApplication.run(So40749877Application.class, args);
            Foo foo = context.getBean(Foo.class);
            try {
                foo.send("foo");
            }
            catch (Exception e) {}
            foo.send("bar");
            RabbitTemplate template = context.getBean(RabbitTemplate.class);
            // should not get any foos...
            System.out.println(template.receiveAndConvert("foo", 10_000));
            System.out.println(template.receiveAndConvert("bar", 10_000));
            // should be null
            System.out.println(template.receiveAndConvert("foo", 0));
            RabbitAdmin admin = context.getBean(RabbitAdmin.class);
            admin.deleteQueue("foo");
            admin.deleteQueue("bar");
            context.close();
        }
    
        @Bean
        public RabbitTemplate amqpTemplate(ConnectionFactory connectionFactory) {
            RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
            rabbitTemplate.setChannelTransacted(true);
            return rabbitTemplate;
        }
    
        @Bean
        public Queue foo() {
            return new Queue("foo");
        }
    
        @Bean
        public Queue bar() {
            return new Queue("bar");
        }
    
        @Bean
        public Foo fooBean() {
            return new Foo();
        }
    
        @Bean
        public PlatformTransactionManager transactionManager(ConnectionFactory connectionFactory) {
            return new RabbitTransactionManager(connectionFactory);
        }
    
        public static class Foo {
    
            @Autowired
            private RabbitTemplate template;
    
            @Transactional
            public void send(String in) {
                this.template.convertAndSend("foo", in);
                if (in.equals("foo")) {
                    throw new RuntimeException("test");
                }
                this.template.convertAndSend("bar", in);
            }
    
        }
    
    }
    

    编辑

    消费者方面的交易;这在使用 Spring 时通常不适用,因为它管理事务,但在直接使用客户端时...

    Connection connection = cf.createConnection();
    Channel channel = connection.createChannel(true);
    channel.basicQos(1);
    channel.txSelect();
    CountDownLatch latch = new CountDownLatch(1);
    channel.basicConsume("foo", new DefaultConsumer(channel) {
    
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                byte[] body) throws IOException {
            System.out.println(new String(body));
    
            getChannel().txRollback(); // delivery won't be requeued; remains unacked
    
            if (envelope.isRedeliver()) {
                getChannel().basicAck(envelope.getDeliveryTag(), false);
                getChannel().txCommit(); // commit the ack so the message is removed
                getChannel().basicCancel(consumerTag);
                latch.countDown();
            }
            else { // first time, let's requeue
                getChannel().basicReject(envelope.getDeliveryTag(), true);
                getChannel().txCommit(); // commit the reject so the message will be requeued
            }
        }
    
    });
    latch.await();
    channel.close();
    connection.close();
    

    注意txRollback 在这种情况下什么都不做;只有确认(或拒绝)是事务性的。

    【讨论】:

    • 谢谢加里,我做了同样的事情,但错过了@EnableTransactionManagement,它现在可以工作了。虽然在那种方法中我使用 amqpAdmin 来声明队列(我必须这样做,我知道它不好),现在它不会回滚,虽然它不是什么大问题,有没有办法回滚呢!
    • 否;基础架构更改不参与事务 - 请参阅 here 了解 rabbitmq 事务语义。
    • 在该链接中,您能否详细说明以下声明:“在消费端,确认是事务性的,而不是消息本身的消费。”
    • 在使用 Spring 时一般不适用,因为它管理事务。希望我上面的编辑解释了消费者方面交易的语义。
    • Gary,如果我们稍微修改上面的 send 方法并额外调用 db 并且我们想使用 atomikas 执行类似两阶段提交的操作,我尝试过但遇到了 atomikas 配置的问题。期待春天启动所有自动。就像@Transactional public void send(String in) { this.template.convertAndSend("foo", in); jdbc.save(实体); if (in.equals("foo")) { throw new RuntimeException("test"); } }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-25
    相关资源
    最近更新 更多