前言
前段时间遇到个面试问题,因为好久没回顾相关知识点,导致没回答上来,属实有点小尴尬,问题是在使用消息中间件的过程中,消费者是如何进行消息消费的,当时我记得就回答了个,消费者通过RabbitListener注解监听broker里对应的queue队列里面的消费,从而进行进行服务端的消费,这么回答显然很拉,那么下面就让我们对mq的具体消费场景来看看,消费者具体消费的步骤是什么;
RabbitMq消费的两种方式
注解配置方式
@RabbitListener注解是RabbitMq里面的一个核心的组件,当生产者将消息推送到指定的queue中,@RabbitListener注解会将消息转发给标记该注解的方法;
通过对源码注解的参数分析,可以看到有containerFactory(监听工厂)这个参数,我们可以通过自定义监听工厂,让消费者通过自定义的消费模式进行消费;
这时候就需要将SimpleRabbitListenerContainerFactory创建SimpleMessageListenerContainer对象,放入Spring容器;
直接在RabbitMqConfig的配置里面,声明SimpleRabbitListenerContainerFactory的bean,将mq的connectionFactory连接工厂,mq默认是自动确认,改为手动,可以自定义MessageListener 方法,通过日志打印消息信息
typescript
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
// RabbitMQ默认是自动确认,这里改为手动确认消息
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// 设置需要手动确认消息的队列,可以同时设置多个,前提是队列需要提前创建好
container.setQueueNames("order.message.queue");
// 设置监听消息的方法,匿名内部类方式
container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
// 开始消费消息
log.info("body:\n{}", JSON.toJSONString(new String(message.getBody())));
log.info("prop:\n{}", JSON.toJSONString(message.getMessageProperties()));
// 手动确认
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, false); // 肯定确认
});
return container;
}
消费者使用自己声明的监听工厂
typescript
@RabbitListener(containerFactory = "simpleMessageListenerContainer")
public void consumer(Message msg){
//simpleMessageListenerContainer 定义了消息的监听日志打印,这里就不需要打log了
//可以专注于写消费者业务逻辑
}
发布订阅模式
直接通过注解绑定Exchange上的队列,有新消息的时候,会自动发送到它绑定的队列上;
less
@RabbitListener(bindings = @QueueBinding(
value = @Queue,
exchange = @Exchange(name = "exchange.system", type = ExchangeTypes.FANOUT)
), containerFactory = "simpleMessageListenerContainer")
public void consumer2(Message msg){
//simpleMessageListenerContainer 定义了消息的监听日志打印,这里就不需要打log了
//可以专注于写消费者业务逻辑
}
RabbitMq消费者源码分析
前面介绍了消费者消费的两种方式,下面该进行源码的分析了,消费者是如何一步步进行消费的;
监听配置类启动
首先,消费者也是需要启动的,离不开Spring Bean初始化过程,RabbitListenerAnnotationBeanPostProcessor继承了BeanPostProcessor 、BeanFactoryAware看起来是不是很熟悉,bean的初始化的关键接口
- 首先是后置处理器解析注解
java
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
//获取代理bean
Class<?> targetClass = AopUtils.getTargetClass(bean);
//buildMetadata 获取注解信息
final TypeMetadata metadata = this.typeCache.computeIfAbsent(targetClass, this::buildMetadata);
for (ListenerMethod lm : metadata.listenerMethods) {
//循环注解配置,一个注解可能有多个配置
for (RabbitListener rabbitListener : lm.annotations) {
processAmqpListener(rabbitListener, lm.method, bean, beanName);
}
}
if (metadata.handlerMethods.length > 0) {
processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
}
return bean;
}
ListenerMethod对象存储了注解的信息,method和RabbitListener
kotlin
private static class ListenerMethod {
final Method method; // NOSONAR
final RabbitListener[] annotations; // NOSONAR
ListenerMethod(Method method, RabbitListener[] annotations) { // NOSONAR
this.method = method;
this.annotations = annotations;
}
}
- 然后就是上面循环里的processAmqpListener方法,创建MethodRabbitListenerEndpoint
scss
protected Collection<Declarable> processAmqpListener(RabbitListener rabbitListener, Method method, Object bean,
String beanName) {
//检查是否是jdk动态代理
Method methodToUse = checkProxy(method, bean);
//创建监听端点
MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
//填充端点方法
endpoint.setMethod(methodToUse);
//填充端点配置
return processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
}
- 端点填充后,需要进行注册,调用RabbitListenerEndpointRegistrar 的registerEndpoint方法,这里也是通过继承BeanFactoryAware ,InitializingBean接口,会调用afterPropertiesSet方法
kotlin
public void registerEndpoint(RabbitListenerEndpoint endpoint,
@Nullable RabbitListenerContainerFactory<?> factory) {
//断言判断endpoint不为空
Assert.notNull(endpoint, "Endpoint must be set");
Assert.hasText(endpoint.getId(), "Endpoint id must be set");
Assert.state(!this.startImmediately || this.endpointRegistry != null, "No registry available");
// Factory may be null, we defer the resolution right before actually creating the container
AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
synchronized (this.endpointDescriptors) {
//是否注册时立即启动
if (this.startImmediately) { // Register and start immediately
this.endpointRegistry.registerListenerContainer(descriptor.endpoint, // NOSONAR never null
resolveContainerFactory(descriptor), true);
}
else {
this.endpointDescriptors.add(descriptor);
}
}
}
- registerListenerContainer监听容器的创建
kotlin
public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
boolean startImmediately) {
//省略部分代码
...
synchronized (this.listenerContainers) {
Assert.state(!this.listenerContainers.containsKey(id),
"Another endpoint is already registered with id '" + id + "'");
//创建MessageListenerContainer核心监听容器
MessageListenerContainer container = createListenerContainer(endpoint, factory);
this.listenerContainers.put(id, container);
if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
List<MessageListenerContainer> containerGroup;
if (this.applicationContext.containsBean(endpoint.getGroup())) {
containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
}
else {
containerGroup = new ArrayList<MessageListenerContainer>();
this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
}
//将容器放入list里
containerGroup.add(container);
}
}
- createListenerContainer看下创建监听的核心方法,核心工厂还是用的RabbitListenerContainerFactory
kotlin
public C createListenerContainer(RabbitListenerEndpoint endpoint) {
C instance = createContainerInstance();
JavaUtils javaUtils =
JavaUtils.INSTANCE
.acceptIfNotNull(this.connectionFactory, instance::setConnectionFactory)
.acceptIfNotNull(this.errorHandler, instance::setErrorHandler);
if (this.messageConverter != null && endpoint != null && endpoint.getMessageConverter() == null) {
endpoint.setMessageConverter(this.messageConverter);
}
//省略部分代码
...
//公共应用重写 ,将每个SimpleMessageListenerContainer容器放入list中
applyCommonOverrides(endpoint, instance);
//监听容器初始化
initializeContainer(instance, endpoint);
if (this.containerCustomizer != null) {
this.containerCustomizer.configure(instance);
}
- 接下来就是调用RabbitListenerEndpointRegistry.#start方法进行监听容器开启 通过实现Lifecycle类调用start方法进行开启
typescript
@Override
public void start() {
//循环遍历多个监听容器,循环进行开启
for (MessageListenerContainer listenerContainer : getListenerContainers()) {
startIfNecessary(listenerContainer);
}
}
private void startIfNecessary(MessageListenerContainer listenerContainer) {
if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
listenerContainer.start();
}
}
- 监听容器的start方法,org.springframework.amqp.rabbit.listener .AbstractMessageListenerContainer
- 又回到了SimpleMessageListenerContainer这个对象,我们在上面创建的消费者监听对象,重写doStart方法进行启动,就会一直进行消息的监听;
scss
@Override
protected void doStart() {
Assert.state(!this.consumerBatchEnabled || getMessageListener() instanceof BatchMessageListener
|| getMessageListener() instanceof ChannelAwareBatchMessageListener,
"When setting 'consumerBatchEnabled' to true, the listener must support batching");
checkListenerContainerAware();
super.doStart();
synchronized (this.consumersMonitor) {
if (this.consumers != null) {
throw new IllegalStateException("A stopped container should not have consumers");
}
//配置消费者个数
int newConsumers = initializeConsumers();
if (this.consumers == null) {
logger.info("Consumers were initialized and then cleared " +
"(presumably the container was stopped concurrently)");
return;
}
//如果消费小于等于0,则打印"消费者已经在运行"
if (newConsumers <= 0) {
if (logger.isInfoEnabled()) {
logger.info("Consumers are already running");
}
return;
}
//将消费者放入set集合
Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
for (BlockingQueueConsumer consumer : this.consumers) {
AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
processors.add(processor);
//使用线程池执行消费新城
getTaskExecutor().execute(processor);
if (getApplicationEventPublisher() != null) {
getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
}
}
//等待消费者线程启动成功
waitForConsumersToStart(processors);
}
}
总结
至此RabbitMq的消费者消费方式,以及消费者监听流程源码分析已经大致梳理完了,总结就是通过@RabbitListener注解创建SimpleMessageListenerContainer核心监听对象,调用start方法,通过线程池进行消费者线程的创建并启动。