RabbitMQ并发消费源码解读

    目前项目采用spring-boot 2.1.6版本,并集成了RabbitMQ的相关功能,至于MQ的相关选型,由于之前项目组已经有项目采用了RabbitMQ,所以基于技术栈的稳定性,并没有变更,但是也简单了解了目前主流的MQ的使用场景,包括RabbitMQ,RocketMQ,Kafka这三种主流MQ,这里不过多阐述,感兴趣的同学可以google下,也可以参读下极客时间李玥老师的消息队列高手课专栏课程对于其中选型的介绍。

   题外话:发现了一个比较好的rabbitmq模拟器,分享给大家:http://tryrabbitmq.com/?exchange_id=exchanger&exchange_name=exchanger

    促使我阅读RabbitMQ源码的原因是,由于项目周期的紧张以及对于中间件的理解不够深刻,未考虑到并发场景下的消费堆积的问题,只想到了解耦并未考虑到消费的能力,怪自己太想的太ez,导致上生产切量以后,消息大量的堆积,好在有回滚方案能够兜底,但是经此一役,强迫自己对RabbitMQ消费的源码进行了一系列的解读。

    项目中业务代码使用@RabbitListener注解处理业务代码中MQ的消费,这个注解用于标记当前方法为一个消息监听器,用于监听指定的队列,如果containerFactory未指定,使用默认的bean name为rabbitListenerContainerFactory的RabbitListenerContainerFactory(SimpleRabbitListenerContainerFactory)实例对象创建一个MessageListenerContainer消息监听器容器,由于项目中采用的是默认配置,所以对应的containerFactory实际生产的对象为SimpleMessageListenerContainer。

    RabbitListenerAnnotationBeanPostProcessor

    此类为@RabbitListener注解的核心处理类,实现了BeanPostProcessor, Ordered, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware,SmartInitializingSingleton接口,其中BeanPostProcessor 接口中的注释为允许对新创建的bean实例进行定制化修改的工厂钩子函数,通俗点说就是spring IoC容器对bean初始化的后置处理类,用于在IoC容器初始化bean的前后通过回调接口加入定制化的业务逻辑;SmartInitializingSingleton接口用于在IoC容器启动单例对象预先实例化结束时触发的回调接口。在IoC启动过程中,实例化RabbitListenerAnnotationBeanPostProcessor对象的时候,通过实现SmartInitializingSingleton的afterSingletonsInstantiated方法,在初始化非延迟加载的singleton对象后,进行定制化的业务逻辑处理,对于RabbitListenerAnnotationBeanPostProcessor来说,其具体定制化业务如下:

RabbitListenerAnnotationBeanPostProcessor

    通过截图中标注的RabbitListenerEndpointRegistrar类(此类可以理解为RabbitListenerEndpointRegistry的包装类),在其实例化,属性配置完成后,调用afterPropertiesSet()(实现了spring的InitializingBean接口)方法,注册项目中所有使用@RabbitListener注解的方法,并将其封装为AmqpListenerEndpointDescriptor对象,由于项目中RabbitMQ使用的是Simple模式默认配置,所以通过resolveContainerFactory(descriptor)获取到的containerFactory实际对象类型为SimpleRabbitListenerContainerFactory,其内部的细节实例化操作这里不过多赘述。

RabbitListenerEndpointRegistrar

    而SimpleRabbitListenerContainerFactory看名字其实是用于新建一个SimpleMessageListenerContainer对象,其类继承结构如下:


SimpleMessageListenerContainer类结构

    在AbstractApplicationContext的refresh()方法中,实例化所有非延迟加载的单列对象,完成上下文的刷新工作以后,开始进行初始化LifecycleProcessor的工作,此接口继承Lifecycle接口



LifecycleProcessor类图

    LifecycleProcessor接口主要职责就是控制在当前IoC容器下bean的生命周期事件,获取IoC容器默认的LifecycleProcessor的实现DefaultLifecycleProcessor,调用onRefresh()方法,通过startBeans,遍历所有Lifecycle接口,如果子类是SmartLifecycle的实现,根据Phase返回值新建LifecycleGroup组,并塞入phases集合中,开始调用phases集合中所有Lifecycle实现的start方法,最终会调用SimpleMessageListenerContainer类中的doStart方法用于实例化RabbitMQ的消费者相关信息,截图如下:

SimpleMessageListenerContainer

    其中initializeConsumers()方法作用就是初始化所有的消息队列消费者,其中concurrentConsumers用于控制消费者的初始化数量,其值默认为1,而maxConcurrentConsumers用于处理在极端情况下,可以实例化的最大的消费者数量,可以参照对比理解成线程池的核心线程数与最大线程数,但是也有些许不一致,简单说明下:在mainLoop()的死循环中,消费之初都会判断maxConcurrentConsumers是否为空,非空的话都会调用checkAdjust(boolean receivedOk)方法进行适配,可以理解为弹性消费扩容,其中这两个consecutiveIdles,consecutiveMessages变量, 控制是需要新增/减少消费者的标志位,对应的参考值分别为consecutiveIdleTrigger,consecutiveActiveTrigger,默认为10,在活动的单个消费者连续接收的消息数量达到consecutiveActiveTrigger 10个的时候,开始调用considerAddingAConsumer()方法,在不大于maxConcurrentConsumers前提下新增消费者,反之就是减少消费数量,控制consumer的大小等于设置的值concurrentConsumers。

checkAdjust方法

initializeConsumers具体内容如下:

SimpleMessageListenerContainer

实际的消费者就是BlockingQueueConsumer这个对象,此类中包含几个比较重要的属性,下面通过其构造函数进行分析:

BlockingQueueConsumer

    BlockingQueue queue 实际从RabbitMQ Server端拉过来的消息的存放处,Delivery为具体的消息对象,这里不做细讲,prefetchCount为一个consumer预先通过channel从RabbitMQ Server拉过来的消息数量,默认为250个。

    也就是说,就算上述SimpleMessageListenerContainer类的concurrentConsumers,maxConcurrentConsumers属性你设置的合理,想要提高并发的消费且消息不积压,还需要合理的调整prefetchCount的值,不然单个BlockingQueueConsumer拉取多个(默认为250)Delivery消息过来,其实单个BlockingQueueConsumer内部是串行的进行消费的,也会产生消息积压的问题。


RabbitMQ并发消费配置

      上面是自己针对的RabbitMQ源码的理解,可能还有不足之处,如有问题,希望不吝指出,多谢。

你可能感兴趣的:(RabbitMQ并发消费源码解读)