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

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


RabbitMQ并发消费配置

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容