ITPub博客

首页 > 应用开发 > Java > Spring AMQP

Spring AMQP

原创 Java 作者:fflixiang 时间:2024-02-27 22:09:40 0 删除 编辑

Spring AMQP 由 spring-amqp 和spring-rabbit 两部分组成, spring-amqp 包含org.springframework,amqp.core 包,包含一些不依赖特定消息中间件的类。例:message,exchage,queue,binding 等类

一、连接和资源管理

管理到RabbitM broker 的连接的核心组件是ConnectionFactory接口。ConnectionFactory 接口的职责是提供org.springframework.amqp.rabbit.connection的一个实例,它是com.rabbitmq.client.Connection的包装器

提供3种类型connectionfactory ,分别是:pooledChannelConnectionFactory,ThreadChannelConnectionFactory,CachingConnectionFactory 

pooledChannelConnectionFactory

该工厂基于Apache Pool2管理一个连接和两个通道( channel)池。一个池用于事务性通道,另一个用于非事务性通道。池是具有默认配置的GenericObjectPool。

ThreadChannelConnectionFactory

此连接工厂用于消息顺序有严格要求,且未使用scoped operations 的情况,此连接工厂管理

单个连接和两个threadLocal,一个用于事务c hannel,另一个用于非事务channel,这个连接工厂确保所有的操作是在同一个

线程使用同一个channel中执行。

CachingConnectionFactory ,

此连接工厂中connection 可用被共享使,channel被缓存。根据是否使用事务使用不同的缓存,默认缓存大小是25,使用setChannelCacheSize方 法控制。

丛1.3 版本开始,可以配置cacheMode 属性为CacheMode.CONNECTION 来缓存connection,而不是channel。丛1.5.5 版本开始,connetionLimit属性控制连接数,如果达到此控制连接数,channelCheckoutTimeLimit 和channelcheckoutTimeout 控制等待连接数量和超时时间。

配置底层客户端连接工厂

spring-rabbit的连接工厂依赖Rabbit client ConnectionFactory。在spring-rabbit 工厂上设置的与Rabbit client ConnectionFactory 相等的属性会传递到Rabbit client ConnectionFactory,如果需要设置Rabbit client ConnectionFactory的其它属性,可以创建一个Rabbit client ConnectionFactory 对象 然后spring-rabbit的连接工厂依赖此对象

丛1.4版本开始提供RabbitConnectionFactoryBean 类配置ssl属性,然后需要将此类对象注入到 Rabbit client ConnectionFactory 中。从sring AMQP 1.5开始可以直接在连接工厂设置。

路由连接工厂

AbstractRoutingConnectionFactory 提供一种在几个连接工厂根据lookupKey 选择目标连接工厂的机制,spring 提供一个simpleRoutingConnectFactory 实现类,从simpleResourceHolder 中获取线程上下文信息lookupKey

LocalizedQueueConnectionFactory 

当在cluster中使用HA queue 时,需要连接到lead queue ,在连接工厂中配置多个地址时,当一个地址连接失败时会尝试别外的地址。localizedQueueConnectionFactory 使用管理插件的REST API 决定lead queue.


从2.0.2版本开始,您可以将usePublisherConnection属性设置为true,以便在可能的情况下与侦听器容器使用不同的连接


二、发送消息

AmqpTemplate接口定义了发送和接收消息的所有基本操作,发送消息都这是通过此接口实现类执行的,RabbitTemplate 是常用的实现类

常用的方法有

void send(Message message) throws AmqpException;
void send(String routingKey, Message message) throws AmqpException;
void send(String exchange, String routingKey, Message message) throws AmqpException;

及常用带消息转换的简便方法

void convertAndSend(Object message) throws AmqpException;
void convertAndSend(String routingKey, Object message) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException;
void convertAndSend(Object message, MessagePostProcessor messagePostProcessor) throws AmqpException;
void convertAndSend(String routingKey, Object message,MessagePostProcessor messagePostProcessor) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message,MessagePostProcessor messagePostProcessor) throws AmqpException;


三、 接收消息

接收消息大体分为两种类型:Polling Consumer 和 注册监听(Asynchronous Consumer)

Polling Consumer 

在AmqpTemplate 接口中定义了接收消息相关方法, 这种方式不会自动到Mq服务取消息,需要调用方法去获取消息,且一次只能取一消息。这种方式会每一次接受消息创建一个QueueingConsume,比较费资源,所以很少使用。

AmqpTemplate接口接收消息方法,还有一此方便使用的方法。

Message receive() throws AmqpException;
Message receive(String queueName) throws AmqpException;
Message receive(long timeoutMillis) throws AmqpException;
Message receive(String queueName, long timeoutMillis) throws AmqpException;

使用 Polling Consumer 例

@Component
public class PollReceiveMessage {
    private Logger log = LoggerFactory.getLogger(PollReceiveMessage.class);
    @Autowired
    private AmqpTemplate amqpTemplate;
    public String pollReceive() {
        Object content = amqpTemplate.receiveAndConvert( "queue.poll");
        log.info((String) content);
        return (String)content;
    }
}


注册监听(Asynchronous Consumer)

这种接收消息的方式,因为注册一个守护 程序 监听消息,也可以叫异步方式。可以通过各种方式注册 守护 程序,常用方式是 RabbitListener注解方式,每次获取多少消息通过预取参数控制,默认目前是250。这里有重要组成部分:

Message Listener

是当接收到消息后进行消费的业务代码,其需要实现MessageListener接口域ChannelAwareMessageListener

public interface MessageListener {
  void onMessage(Message message);
}
public interface ChannelAwareMessageListener {
  void onMessage(Message message, Channel channel) throws Exception;


监听容器(Container)

是AMQP Queue和MessageListener实例之间的 连接桥梁,目前有两种 Container,分别是SimpleMessageListenerContainer和DirectMessageListenerContainer

@Configuration
@EnableRabbit
public class AppConfig {
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory());
        factory.setConcurrentConsumers(3);
        factory.setMaxConcurrentConsumers(10);
        factory.setContainerCustomizer(container -> /* customize the container */);        return factory;
    }
}

容器工厂提供了添加MessagePostProcessor实例的方法,这些实例在接收消息之后(在调用侦听器之前)和发送回复之前应用。

还可以在 容器工厂上添加RetryTemplate和RecoveryCallback,当retry次数达到时会调用 RecoveryCallback。

factory.setRetryTemplate(retryTemplate);
factory.setReplyRecoveryCallback(ctx -> {
    Message failed = SendRetryContextAccessor.getMessage(ctx);
    Address replyTo = SendRetryContextAccessor.getAddress(ctx);
    Throwable t = ctx.getLastThrowable();
    ...    return null;
});

处理异常

默认情况下,如果带注释的侦听器方法抛出异常,则将其抛出到容器,并根据容器和代理配置对消息进行重新请求和重新交付、丢弃或路由到死信交换。不会向发送方返回任何内容。

从2.0版本开始,@RabbitListener注释有了两个新属性:errorHandler和returnExceptions

批量处理

当消息发送端发送批量消息时接收端也是批量接收消息、处理消息。从2.2版本开始支持批量接收、处理 发送端单条发送消息,通过 监听容器工厂的batchListener 属性审控制。

此外,SimpleMessageListenerContainer中还添加了一个新的属性consumerBatchEnabled。当设置为true时,容器将创建批量消息,最大为batchSize;如果receiveTimeout超时而没有新消息到达,也会处理接收到的部分批量消息。如果接收到生产者创建的批量消息,则将其 批量消息添加到消费者端批处理;因此,实际处理消息数量可能超过batchSize, 当consumerBatchEnabled为true时,deBatchingEnabled必须为true


使用RabbitListener注解注册监听

这是最常用的方法

@Component
public class ReceiveMessage {
    private Logger log = LoggerFactory.getLogger(ReceiveMessage.class);
    @RabbitListener(queues = "queue.test")
    public void processMessage(String content) {
        log.info(content);
    }
}

注:通过使用RabbitListenerContainerFactory在后台为每个带 RabbitListener注解的方法创建一个消息侦听器容器。

       需要在配置类上添加 @EnableRabbit注解来允许 abbitListener注解使用

使用RabbitListenerEndpoint  注册监听端点  

@Configuration
@EnableRabbit
public class ProgramReceiveMessage implements RabbitListenerConfigurer {
    private Logger log = LoggerFactory.getLogger(ProgramReceiveMessage.class);
    @Override
    public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
       SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
       endpoint.setId("queue.poll");
       endpoint.setQueueNames("queue.poll");
       endpoint.setMessageListener(message -> log.info(message.toString()));
       registrar.registerEndpoint(endpoint);
    }
}

使用 SimpleMessageListenerContainer接口 注册监听

@Configuration
public class ExampleAmqpConfiguration {   
        private Logger log = LoggerFactory.getLogger(ExampleAmqpConfiguration.class); 
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());        
        return container;
    }
    @Bean
    public CachingConnectionFactory rabbitConnectionFactory() {
        CachingConnectionFactory connectionFactory =  new CachingConnectionFactory("localhost");
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");       
        return connectionFactory;
    }  
    @Bean
    public MessageListener exampleListener() {
       return new MessageListener() {
             @Override
             public void onMessage(Message message) {
               log.info("received: " + message);
        }
    };
}  
}


四、发布确认和返回(Publisher Confirms and Returns)

发布确认( Publisher  Confirms)

用于确认消息能否发送到到RabbitMq交换机。 发布确认有3种类型,分别是none,correlated,simple 这三种类型,通过 ConnectionFactory 接口属性控制, ConnectionFactory 的实现类中,只有CachingConnectionFacotory支持 correlated类型 发布确认。 发布确认可以是同步确认,也可以是异步回调确认。

simple 类型发布确认

使用PooledChannelConnectionFactory 连接工厂

/**
 * 需要 apache commons-pool2,不支持PublisherReturns,支持Simple Confirms
 */
@Bean
public PooledChannelConnectionFactory connectionFactory() {
    ConnectionFactory clientnFactory = new ConnectionFactory();
    PooledChannelConnectionFactory connectionFactory = new PooledChannelConnectionFactory(clientnFactory);
    connectionFactory.setSimplePublisherConfirms(true);
    return connectionFactory;
}

同步确认

    @Autowired
    private RabbitTemplate rabbitTemplate;
    public boolean syncConfirmsSend(String message) {
//      return  rabbitTemplate.invoke(rabbitOperations  -> {
//            rabbitOperations.convertAndSend("direct.test","routingSimple",message);
//            rabbitOperations.waitForConfirmsOrDie(10_00);
//            return true;
//        },(tag,multiple) -> {
//          log.info("Ack: " + tag + ":" + multiple);
//      },(tag,multiple) ->{
//          log.info("Nack: " + tag + ":" + multiple);
//      });
        return  rabbitTemplate.invoke(rabbitOperations  -> {
            rabbitOperations.convertAndSend("direct.test","routingSimple",message);
            rabbitOperations.waitForConfirmsOrDie(10_00);
            return true;
        });
    }


correlated  类型发布确认

通常这种类型的发布确认采用异步回调的方式进行,通过RabbitTemplate.setConfirmCallback(ConfirmCallback callback)注册ConfirmCallback函数。一个 RabbitTemplate只能有一个 ConfirmCallback回调函数。

从2.1版本开始,CorrelationData对象有一个ListenableFuture,可以使用它来获取结果,而不是在RabbitTemplate上使用ConfirmCallback

使用CachingConnectionFactory连接工厂

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    connectionFactory.setPublisherReturns(true);
    connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
    return connectionFactory;
}

RabbitTemplate 配置异步确认

@Bean
public RabbitTemplate customTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate  = new RabbitTemplate(connectionFactory);
    rabbitTemplate.setReturnsCallback(returnedMessage -> {
        log.info("replycode: " + returnedMessage.getReplyCode());
        log.info("replyText(): " + returnedMessage.getReplyText());
    });
    return rabbitTemplate;
}

发送信息

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public boolean send(String message) throws ExecutionException, InterruptedException, TimeoutException {
        CorrelationData cd = new CorrelationData();
        rabbitTemplate.convertAndSend("direct.test","routingSimple",message,cd);
        return true;
    }


发布 返回( Publisher  Returns)

用于确认消息能否发送到到队列, 正确发送到Exchange,但没有被路由到Queue才会回调。 需要将RabbitTemplate 的mandatory 属性设置为true,或者mandatory-expression 属性计算为true,还需要将CachingConnectionFactory 的publisherReturns 属性设置为true。返回信息是通过RabbitTemplat.setReturnsCallback(ReturnsCallback callback)注册的ReturnsCallbak函数

@Bean
public CachingConnectionFactory rabbitConnectionFactory() {
    CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost");
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    connectionFactory.setPublisherReturns(true);
    connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
    return connectionFactory;
}

RabbitTemplate  配置

@Bean
public RabbitTemplate customTemplate(ConnectionFactory connectionFactory) {
    RabbitTemplate rabbitTemplate  = new RabbitTemplate(connectionFactory);
    rabbitTemplate.setMandatory(true);
    rabbitTemplate.setReturnsCallback(returnedMessage -> {
        log.info("replycode: " + returnedMessage.getReplyCode());
        log.info("replyText(): " + returnedMessage.getReplyText());
    });
    rabbitTemplate.setConfirmCallback((correlationData,ack,cause) ->{
        log.info("correlationData" + correlationData.getId());
        log.info("ack: " + ack);
        log.info("cause: " + cause);
    });
    return rabbitTemplate;
}
public void send(String message) {
    CorrelationData cd = new CorrelationData();
    rabbitTemplate.convertAndSend("direct.test","routingSimple",message,cd);
}


消费者端 消息确认

消息确认针对两对象,一个是对rabbitMq服务 消耗 确认,另一个是对消息发布者 回复。

对 rabbitMq服务 消息 消耗确认

默认当接收到消息后自动回复确认信息到 rabbitMq服务,可以通过配置监听容器acknowledgeMode 属性手动回复。

监听容器 acknowledgeMode属性可以在@RabbitListener里指定ackMode来修改,"MANUAL" 是修改为手动回复。

手动回复成功(ack)

void basicAck(long deliveryTag, boolean multiple) throws IOException;

deliveryTag: 表示消息投递序号,每次消费或消息重新投递后, deliveryTag 都会增加

multiple:是否批量确认,值为true则会一次性确认所有小于当前消息 deliveryTag的消息

手动回复失败(nack)

void basicNack(long deliveryTag, boolean multiple, boolean requeue)
        throws IOException;

deliveryTag: 表示消息投递序号,每次消费或消息重新投递后,deliveryTag 都会增加

multiple:是否批量确认,值为true则会一次性确认所有小于当前消息deliveryTag的消息

requeue:   是否重新入队列

@Component
public class ReceiveMessage {
    private Logger log = LoggerFactory.getLogger(ReceiveMessage.class);
    private static final ConcurrentMap<String,Boolean> concurrentHashMap = new ConcurrentHashMap();
    @RabbitListener(queues = "queue.test",ackMode = "MANUAL")
    public String processMessage(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag,  @Payload String content) throws IOException {
        Boolean ackflage = concurrentHashMap.get(content);
        if (Boolean.TRUE.equals(ackflage)) {
            log.info(content);
            concurrentHashMap.remove(content);
            channel.basicAck(tag, false);
            return "success";
        } else {
            log.info(content);
            concurrentHashMap.put(content, Boolean.TRUE);
            channel.basicNack(tag, false, true);
            return "FAIL";
        }
    }
}


对消息发布者回复

消息发布者回复其实就是再发送回复信息到rabiitMq 服务队列,默认是发送到原始消息 头中指定的ReplyTo 指定队列,也可以使用@SendTo注释来设置发送地址。

发送消息并接收 回复消息

    @Autowired
    private RabbitTemplate rabbitTemplate;
    public Object send(String message) {
      CorrelationData cd = new CorrelationData();
      Message msg = MessageBuilder.withBody(message.getBytes()).build();
      return rabbitTemplate.convertSendAndReceive("direct.test","directRoute",msg ,cd);
    }

回复消息默认地址

@Component
public class ReceiveMessage {
    private Logger log = LoggerFactory.getLogger(ReceiveMessage.class);
    @RabbitListener(queues = "queue.test")
    public String processMessage(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag,
                                   Message content) throws IOException ){
      // do some more work
     return "success";
    }

SendAndReceive 方法的 默认5分秒超时,可以使用 replyTimeout 属性控制,如果没设置maddatory 属性为true,超时返回null,否则超时会抛出AmqpMessageReturnedException异常。

回复消息 @SendTo地址

@Component
public class ReceiveMessage {
    private Logger log = LoggerFactory.getLogger(ReceiveMessage.class);
    
    @RabbitListener(queues = "queue.test")
    @SendTo("direct.test/routingReply")
    public Message processMessage(Message content) {
        // do some more work
        return  MessageBuilder.withBody("success".getBytes()).andProperties(content.getMessageProperties()).build();
    }
    @RabbitListener(queues = "queue.reply")
    public void replyResponse(Message message){
        String CorrelationId = message.getMessageProperties().getHeaders().get("spring_returned_message_correlation").toString();
        //  String CorrelationId = message.getMessageProperties().getCorrelationId();
        // do some more work
    }
}

@SendTo 的值是exchange/routingKey 格式。


五、消息转换器

消息转换是指将消息对象与Message对象 ( org.springframework.amqp.core.Message )之间转换。 RabbitTemplate 中为方便提供一些对 消息对象与Message对象的简便方法。

消息转换接口,spring 提供几个其实现类。

public interface MessageConverter {
    Message toMessage(Object object, MessageProperties messageProperties)
            throws MessageConversionException;
    
    Object fromMessage(Message message) throws MessageConversionException;
}


SimpleMessageConverter

  MessageConverter的实现类SimpleMessageConverter是默认的 消息转换器。能处文体字符串、java 序列化对象和byte数组。

Jackson2JsonMessageConverter

使用 Jackson2JsonMessageConverter

@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
    Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
    jsonConverter.setClassMapper(classMapper());    
    return jsonConverter;
}


六、修改消息

spring 提供了许多扩展点。它们允许你在消息发送到RabbitMQ之前或者在消息被接收后立即对消息执行一些处理,

1、使用带MessagePostProcessor 参数的AmqpTemplate 接收和发送方法

2、 AmqpTemplate 设置属性setBeforePublishPostProcessors() and setAfterReceivePostProcessors()


七、异常处理

在 org.springframework.amqp 中所有的异常都是AmqpException 异常的子类,监听(Listenner)抛出的异常是包裹在 ListenerExecutionFailedException异常中, 丛1.3.2版本开始默认异常处理器ConditionalRejectingErrorHandler 对带不可恢复的错误异常不会重新入队列(也叫fatal exception), 从1.6.3版本开始,将用户异常添加到致命列表的简便方法是创建ConditionalRejectingErrorHandler.DefaultExceptionStrategy的子类,将覆盖isUserCauseFatal(Throwable cause)方法,对需要添加到致命异常返回true。对非 fatal exception,默认消息重新入队列,当设置属性defaultRequeueRejected为 false时,消息被废弃,不会重新入队列,

默认重新入队列的行为可能会导致永无止境的循环执行,为解决这问题,可以在监听器上添加StatefulRetryOperationsInterceptor 拦截器,此拦截器带个MessageRecoverer, 此MessageRecoverer 抛出AmqpRejectAndDontRequeueException或 ImmediateRequeueAmqpException 异常,此MessageRecoverer 在retry次数达到时调用。

AmqpRejectAndDontRequeueException。消息不会重新入队列,无论defaultrequeuereejded属性的设置如何。

ImmediateRequeueAmqpException  消息都将被重新入队列,无论defaultrequeuereejject属性的设置如何,从1.3版本开始,提供了一个新的republishmessagercoverer,允许在重试失败后发布失败的消息。

@Bean
RetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateless()
            .maxAttempts(5)
            .recoverer(new RepublishMessageRecoverer(amqpTemplate(), "something", "somethingelse"))
            .build();
}

对一此同步执行操作,可以捕获并响应异常,也可以使用spring retry ,Spring AMQP提供了两个创建Spring  retry 拦截器的bean  StatefulRetryOperationsInterceptor 和 StatelessRetryOperationsInterceptor 

@Bean
public StatefulRetryOperationsInterceptor interceptor() {
    return RetryInterceptorBuilder.stateful()
            .maxAttempts(5)
            .backOffOptions(1000, 2.0, 10000) // initialInterval, multiplier, maxInterval
            .build();
}


八、事务

Spring Rabbit框架支持同步和异步用例中的自动事务管理,在RabbitTemplate和SimpleMessageListenerContainer中,都有一个channelTransacted标志,如果为true,则告诉框架使用事务通道,也可以 提供一个PlatformTransactionManager的实现类作为外部事务管理,作为正在进行的操作的上下文。

@Transactional
public void doSomething() {
    String incoming = rabbitTemplate.receiveAndConvert();
    // do some more database processing...
    String outgoing = processInDatabaseAndExtractReply(incoming);
    rabbitTemplate.convertAndSend(outgoing);
}
@Configuration
public class ExampleExternalTransactionAmqpConfiguration {
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer() {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(rabbitConnectionFactory());
        container.setTransactionManager(transactionManager());
        container.setChannelTransacted(true);
        container.setQueueName("some.queue");
        container.setMessageListener(exampleListener());
        return container;
    }
}

注:1、RabbitTransactionManager 没提供XA transactions功能

       2、rabbitMQ事务与其它事务(例如数据库)之间同步是“Best Effort One Phase Commit”,即有可能实际提交时失败,而且时只记录一条日志,不会抛出异常去回滚事务。可以使用提交事务的线程上调用ConnectionUtils.checkAfterCompletion() 来检查。

 条件回滚(Conditional Rollback)

@Bean
public AbstractMessageListenerContainer container() {
    ...
    container.setTransactionManager(transactionManager);
    RuleBasedTransactionAttribute transactionAttribute =        new ListenerFailedRuleBasedTransactionAttribute();
    transactionAttribute.setRollbackRules(Collections.singletonList(        new NoRollbackRuleAttribute(DontRollBackException.class)));
    container.setTransactionAttribute(transactionAttribute);
    ...
}




来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/23748560/viewspace-3000673/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论

注册时间:2014-04-14

  • 博文量
    8
  • 访问量
    23375