我有将邮件发送到RabbitMQ的旧系统。
系统仅使用一个队列:q.finance.invoice
,但是它有两种消息类型,其中消息类型在标头上可用。
第一种
Type : invoice.created
{
"field_1" : "",
"field_2" : "",
}
第二种类型
Type : invoice.paid
{
"field_5" : "",
"field_6" : "",
}
因此,现在我的消费者需要根据数据类型选择性地处理消息。
如果消息是在spring之前发布的,Spring就有@RabbitHandler
可以这样做。
我不能使用@RabbitHandler
注释。
我认为是因为@RabbitHandler
正在基于旧系统中不存在的__TypeId__
标头转换邮件。
我如何模拟这种@RabbitHandler
行为(根据其类型获取数据)?
因此,我使用@RabbitListener
来消费消息。
但是@RabbitListener
正在接收所有类型的消息。
我们使用@RabbitListener
的另一个原因是我们的错误处理程序取决于Message
和Channel
我们拥有的基本方法签名如下:
@RabbitListener(queues = "q.finance.invoice")
public void listenInvoicePaid(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
// convert message body JSON string to object
// process it
}
我正在尝试根据类型进行手动拒绝,这是可行的。但是我确定当我有很多侦听器或队列时,它是不可扩展的
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;
import com.rabbitmq.client.Channel;
@Service
public class InvoiceListenerOnMethod {
private static final Logger log = LoggerFactory.getLogger(InvoiceListenerOnMethod.class);
@RabbitListener(queues = "q.finance.invoice")
public void listenInvoiceCreated(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
throws IOException {
if (!StringUtils.equalsIgnoreCase("invoice.created", message.getMessageProperties().getType())) {
log.warn("[on Method] Rejecting invoice created : {}", message);
channel.basicReject(tag, true);
return;
}
log.info("[on Method] Listening invoice created : {}", message);
}
@RabbitListener(queues = "q.finance.invoice")
public void listenInvoicePaid(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
throws IOException {
if (!StringUtils.equalsIgnoreCase("invoice.paid", message.getMessageProperties().getType())) {
log.warn("[on Method] Rejecting invoice paid : {}", message);
channel.basicReject(tag, true);
return;
}
log.info("[on Method] Listening invoice paid : {}", message);
}
}
要点是,当我有4条消息(付费创建-创建)时,侦听器可以运行4次以上,因为我们无法控制谁接收哪条消息。因此,listenInvoicePaid()
与在listenInvoiceCreated()
中也可以在ack()之前出现多个reject()相同的方式
因此,在正确处理所有消息之前,我总共可以打电话给大约10条消息。
有任何建议修复该代码吗?
答案 0 :(得分:0)
我还没有进行过Rabbit的春季集成工作,但总的来说,只有一个队列来处理不同消息类型的想法听起来有些麻烦:
许多消费者可能会收到他们无法处理的消息类型,因此不得不拒绝它们,这样消息将回到兔子,然后一次又一次...所有集群的性能可能会恶化因此。
所以我认为您可以遵循两种路径:
实现单个侦听器,该侦听器可以处理两种类型的消息。无需更改Rabbit,但在Java方面可能是具有挑战性的重构。
幸运的是,Rabbit MQ在路由消息方面非常灵活。根据路由密钥,报头(无论什么类型),配置交换以将类型A的消息路由到队列A,将类型B的消息路由到队列B,Rabbit中有不同类型的Exchange,您肯定会找到最合适的配置。
我个人会选择第二条路径。
答案 1 :(得分:0)
您可以将MessagePostProcessor
添加到容器工厂的afterReceiveMessagePostProcessor
属性中。在后处理器中,您可以检查JSON body()
并将__TypeId__
标头设置为适当的类名称。
有关示例,请参见this answer。
答案 2 :(得分:0)
可能的实施
这是幼稚的if-else方式,谢谢Mark。这是您的建议(第一种选择)。 至于第二种选择,我做不到,因为发布者是我没有代码的旧系统
@RabbitListener(queues = "q.finance.invoice")
public void listenInvoiceCreated(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long tag,
@Header("type") String type) throws IOException {
if (StringUtils.equalsIgnoreCase(type, "invoice.paid")) {
log.info("Delegate to invoice paid handler");
} else if (StringUtils.equalsIgnoreCase(type, "invoice.created")) {
log.info("Delegate to invoice created handler");
} else {
log.info("Delegate to default handler");
}
}
第二种实现方式
感谢Gary,这就是我实施的方法。我认为这是更清洁的方法。接下来,我只需要将消息后处理程序提取到其他类以实现可维护性,因此就不会使我的@RabbitListener
配置文件
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.course.finance.message.invoice.InvoiceCreatedMessage;
import com.course.finance.message.invoice.InvoicePaidMessage;
@Configuration
public class RabbitmqConfig {
@Bean(name = "rabbitListenerContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setAfterReceivePostProcessors(new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
var type = message.getMessageProperties().getHeaders().get("type").toString();
String typeId = null;
if (StringUtils.equalsIgnoreCase(type, "invoice.paid")) {
typeId = InvoicePaidMessage.class.getName();
} else if (StringUtils.equalsIgnoreCase(type, "invoice.created")) {
typeId = InvoiceCreatedMessage.class.getName();
}
Optional.ofNullable(typeId).ifPresent(t -> message.getMessageProperties().setHeader("__TypeId__", t));
return message;
}
});
return factory;
}
@Bean
Jackson2JsonMessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
RabbitTemplate rabbitTemplate(Jackson2JsonMessageConverter converter, ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
}
Listener
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import com.course.finance.message.invoice.InvoiceCreatedMessage;
import com.course.finance.message.invoice.InvoicePaidMessage;
@Service
@RabbitListener(queues = "q.finance.invoice")
public class InvoiceListener {
private static final Logger log = LoggerFactory.getLogger(InvoiceListener.class);
@RabbitHandler
public void listenInvoiceCreated(InvoiceCreatedMessage message) {
log.info("Listening invoice created : {}", message);
}
@RabbitHandler
public void listenInvoicePaid(InvoicePaidMessage message) {
log.info("Listening invoice paid : {}", message);
}
@RabbitHandler(isDefault = true)
public void listenDefault(Message message) {
log.info("Default invoice listener : {}", message.getMessageProperties().getHeaders());
}
}