RabbitMq消费者是如何监听消息

前言

前段时间遇到个面试问题,因为好久没回顾相关知识点,导致没回答上来,属实有点小尴尬,问题是在使用消息中间件的过程中,消费者是如何进行消息消费的,当时我记得就回答了个,消费者通过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方法,通过线程池进行消费者线程的创建并启动。

相关推荐
hlsd#13 分钟前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)17 分钟前
三层架构和MVC以及它们的融合
后端·mvc
亦世凡华、18 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
河西石头19 分钟前
一步一步从asp.net core mvc中访问asp.net core WebApi
后端·asp.net·mvc·.net core访问api·httpclient的使用
2401_8574396930 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66631 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
小码编匠1 小时前
领域驱动设计(DDD)要点及C#示例
后端·c#·领域驱动设计
德育处主任Pro2 小时前
『Django』APIView基于类的用法
后端·python·django
哎呦没4 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端