Spring整合rabbitmq实践(二):扩展功能

Spring整合rabbitmq实践(一):基础使用配置
Spring整合rabbitmq实践(三):源码-@RabbitListener实现过程
spring-rabbit消费过程解析及AcknowledgeMode选择

3. 扩展实践

3.1. MessageConverter

前面提到只要在RabbitTemplate中配置了MessageConverter,在发送和接收消息的时候就能自动完成Message和自定义java对象的自动转换。

MessageConverter接口只有两个方法:


public interface MessageConverter {
   

/**

* Convert a Java object to a Message.

* @param object the object to convert

* @param messageProperties The message properties.

* @return the Message

* @throws MessageConversionException in case of conversion failure

*/

Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException;

/**

* Convert from a Message to a Java object.

* @param message the message to convert

* @return the converted Java object

* @throws MessageConversionException in case of conversion failure

*/

Object fromMessage(Message message) throws MessageConversionException;

}

即使不手动配置MessageConverter,也会有一个默认的SimpleMessageConverter,

它会直接将java对象序列化。

官方文档不建议使用这个MessageConverter,因为SimpleMessageConverter是将java对象在producer端序列化,然后在consumer端反序列化,这会将producer和consumer紧密地耦合在一起,并且仅限于java平台。

推荐用JsonMessageConverter、Jackson2JsonMessageConverter,这两个是都将java对象转化为json再转为byte[]来构造Message对象,前一个用的是jackson json lib,后一个用的是jackson 2 json lib。


    @Bean

    public MessageConverter jsonMessageConverter() {
   

        return new Jackson2JsonMessageConverter();

    }

    @Bean

    @Autowired

    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,

                                        MessageConverter messageConverter) {
   

        RabbitTemplate template = new RabbitTemplate(connectionFactory);

        template.setMessageConverter(messageConverter);

        return template;

    }

还有一些其它的MessageConverter实现类,当然如果有需要也可以自己实现。

3.2. Exception Handling

spring-rabbit暴露了两个接口可供实现用来处理@RabbitListener注解方法抛出的异常。

RabbitListenerErrorHandler

示例:

    @Bean
    public RabbitListenerErrorHandler rabbitListenerErrorHandler(){
   
        return new RabbitListenerErrorHandler() {
   
            @Override
            public Object handleError(Message amqpMessage,
                                      org.springframework.messaging.Message<?> message,
                                      ListenerExecutionFailedException exception) throws Exception {
   
                System.out.println(message);
                throw exception;
            }
        };
    }

    @RabbitListener(queues = "test_queue_1", errorHandler = "rabbitListenerErrorHandler")
    public void listen(Message message){
   
        ...
    }

这个是设置在@RabbitListener注解上的,只对当前注解的方法生效(当前方法抛异常时被调用)。

org.springframework.util.ErrorHandler

这个是spring-core包下面的ErrorHandler,可实现这个接口设置在RabbitListenerContainerFactory里面,示例:

    @Bean
    @Autowired
    public RabbitListenerContainerFactory rabbitListenerContainerFactory(CachingConnectionFactory cachingConnectionFactory,
                                                                         MessageConverter messageConverter){
   
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(cachingConnectionFactory);
        factory.setMessageConverter(messageConverter);
        factory.setConcurrentConsumers(1);
        factory.setMaxConcurrentConsumers(1);
        factory.setPrefetchCount(1);
        factory.setErrorHandler(new ErrorHandler() {
   
            @Override
            public void handleError(Throwable t) {
   
                throw new AmqpRejectAndDontRequeueException(t);
            }
        });
        factory.setDefaultRequeueRejected(false);
        return factory;
    }

这个ErrorHandler对所有@RabbitListener注解方法生效。

对比
  • 作用范围:RabbitListenerErrorHandler只对当前@RabbitListener注解方法生效,ErrorHandler对所有@RabbitListener注解方法生效;
  • 调用顺序:RabbitListenerErrorHandler先被调用,ErrorHandler后被调用;
  • 处理粒度:RabbitListenerErrorHandler粒度比较细,可以获取到当前Message,以便做细致处理,ErrorHandler只能获取到Throwable参数;
  • 默认配置:RabbitListenerErrorHandler没有默认配置,ErrorHandler有默认值ConditionalRejectingErrorHandler

ConditionalRejectingErrorHandler的作用:

  • 打印日志;
  • 部分异常导致的失败不会requeue消息(默认处理失败的消息会requeueAcknowledgeMode.NONE模式除外)。

3.3. Transactions

rabbitmq和spring-amqp官方文档对事务的描述都非常少,简单介绍一下了解到的信息。

rabbitmq官方文档对amqp事务的整体定位是这样的:

Overall the behaviour of the AMQP tx class, and more so its implementation on RabbitMQ, is closer to providing a ‘batching’ feature than ACID capabilities known from the database world.

amqp事务仅仅适用于publish和ack,rabbitmq增加了reject的事务。其它操作都不具备事务特性。也就是说,rabbitmq本身的事务可以保证producer端发出的消息成功被broker收到(不能保证一定会进入queue),consumer端发出的确认信息成功被broker收到,其它诸如consumer端具体的消费逻辑之类如果想要获得事务功能,需要引入外部事务。

引入rabbitmq事务很简单,将RabbitTemplate或者RabbitListenerContainerFactory的channelTransacted属性设为true即可,示例:


    @Autowired

    @Bean

    public AmqpTemplate amqpTemplate(ConnectionFactory amqpConnectionFactory){
   

        RabbitTemplate rabbitTemplate = new RabbitTemplate();

        rabbitTemplate.setConnectionFactory(amqpConnectionFactory);

        rabbitTemplate.setChannelTransacted(true);

        return rabbitTemplate;

    }

这样,获得的Channnel就有了事务功能。

也可以直接操作Channel:


    Channel channel = cachingConnectionFactory.createConnection().createChannel(true);

    try {
   

        //channel.txSelect();上面createChannel已经设为true了,这句可以去掉

        channel.basicPublish("xxx", "xxx", new AMQP.BasicProperties(), JSON.toJSONString(event).getBytes());

            channel.txCommit();

    } catch (IOException e) {
   

        try {
   

            channel.txRollback();

        } catch (IOException e1) {
   

        }

    } finally {
   

        try {
   

            channel.close()

        } catch (Exception e) {
   

        }

    }

需要注意的是,直接通过Connection获取的Channel需要手动close:

Channels used within the framework (e.g. RabbitTemplate) will be reliably returned to the cache. If you create channels outside of the framework, (e.g. by accessing the connection(s) directly and invoking createChannel()), you must return them (by closing) reliably, perhaps in a finally block, to avoid running out of channels.

对于producer端,同样的发送一条消息到一个不存在的exchange:


amqpTemplate.convertAndSend("notExistExchange", "routingKey", object);

如果关闭事务,如上文提到过,CachingConnectionFactory会打出一条错误日志,但程序会正常运行。

如果打开事务,由于消息没有到达broker,这里会抛出异常。

对于consumer端,当consumer正在处理一条消息时:

如果broker挂掉,程序会不断尝试重连,当broker恢复时,会重新收到这条消息;

如果程序挂掉,broker发现还没有收到consumer的确认信息但consumer没了,会将这条消息恢复;

长时间没有收到consumer端的确认信息,也会将消息从unacked状态变成ready状态;

如果程序处理消息期间抛异常,broker会收到一个nack或者reject,也会将这条消息恢复。

所以,rabbitmq是可以将没有成功消费的消息恢复的,个人觉得consumer端使用rabbitmq事务的意义并不是很大,也许可以用于consumer端消息去重:

consumer处理成功向rabbitmq发出了ack,consumer默认rabbitmq收到了这个ack所以consumer认为这条消息处理结束,但实际可能rabbitmq没有收到ack又将这条消息放回queue然后重新发给consumer导致消息重复处理。如果开启了事务,能保证rabbitmq一定能收到确认信息,否则事务提交失败。

另外,需要注意的是,开启事务会大幅降低消息发送及接收效率,因为当已经有一个事务存在时,后面的消息是不能被发送或者接收(对同一个consumer而言)的,所以以上两种场景都不推荐使用事务来解决。

3.4. Listeners


    @Bean

    public ChannelListener channelListener() {
   

        return new ChannelListener() {
   

            @Override

            public void onCreate(Channel channel, boolean transactional) {
   

                logger.info("channel number:{}, nextPublishSqlNo:{}",

                        channel.getChannelNumber(),

                        channel.getNextPublishSeqNo());

            }

            @Override

            public void onShutDown(ShutdownSignalException signal) {
   

                logger.error("channel shutdown, reason:{}, errorLevel:{}",

                        signal.getReason().protocolMethodName(),

                        signal.isHardError() ? "connection" : "channel");

            }

        };

    }

ChannelListener接口,监听Channel的创建和异常关闭。


    @Bean

    public BlockedListener blockedListener() {
   

        return new BlockedListener() {
   

            @Override

            public void handleBlocked(String reason) throws IOException {
   

                logger.info("connection blocked, reason:{}", reason);

            }

            @Override

            public void handleUnblocked() throws IOException {
   

                logger.info("connection unblocked");

            }

        };

    }

BlockedListener监听Connection的block和unblock。


    @Bean

    public ConnectionListener connectionListener() {
   

        return new ConnectionListener() {
   

            @Override

            public void onCreate(Connection connection) {
   

                logger.info("connection created.");

            }

            public void onClose(Connection connection) {
   

                logger.info("connection closed.");

            }

            public void onShutDown(ShutdownSignalException signal) {
   

                logger.error("connection shutdown, reason:{}, errorLevel:{}",

                        signal.getReason().protocolMethodName(),

                        signal.isHardError() ? "connection" : "channel");

            }

        };

    }

ConnectionListener监听Connection的创建、关闭和异常终止。


    @Bean

    public RecoveryListener recoveryListener() {
   

        return new RecoveryListener() {
   

            @Override

            public void handleRecovery(Recoverable recoverable) {
   

                logger.info("automatic recovery completed");

            }

            @Override

            public void handleRecoveryStarted(Recoverable recoverable) {
   

                logger.info("automatic recovery started");

            }

        };

    }

RecoveryListener监听开始自动恢复Connection、自动恢复连接完成。

ConnectionListener、ChannelListener、RecoveryListener设置到ConnectionFactory即可。


    @Autowired

    @Bean

    public CachingConnectionFactory cachingConnectionFactory(ConnectionListener connectionListener,

                                                            ChannelListener channelListener,

                                                            RecoveryListener recoveryListener) {
   

        CachingConnectionFactory connectionFactory = 
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值