### 准备

## 目标

了解 Spring AMQP 如何实现异步消息投递(推模式)

## 前置知识

《RabbitMQ入门_05_多线程消费同一队列》

## 相关资源

Sample code:<https://github.com/gordonklg/study>,rabbitmq module
源码版本:Spring AMQP 1.7.3.RELEASE

## 测试代码

gordon.study.rabbitmq.springamqp.AsyncConsumer.java

 

### 分析

## MessageListener

org.springframework.amqp.core.MessageListener 是 Spring AMQP 异步消息投递的监听器接口,它只有一个方法 onMessage,用于处理消息队列推送来的消息。

 
MessageListener 大概对应 amqp client 中的 Consumer 类。onMessage 方法大概对应 Consumer 类的 handleDelivery 方法。

从这也可以看出,Spring AMQP 的 Message 类至少包含 consumer tag、envelope、basic properties 和 message body 等信息
 

## MessageListenerContainer

org.springframework.amqp.rabbit.listener.MessageListenerContainer 可以看作 message linstener 的容器。但这个 Container 的语义并不是指它包含多个 message listener,实际上从方法注释和实现代码可以看出,MessageListenerContainer 只包含一个 MessageListener 。那 Container 的语义是什么呢?
 
一方面,Container 是指虽然只有一个 MessageListener 指定消息消费的逻辑,但是可以生成多个线程使用相同的 MessageListener 同时消费消息。代码第19行 setConcurrentConsumers 方法就是用来指定并发消费者的数量。可以把 MessageListenerContainer 看成下图中的 Subscriber group

 
另一方面,Container 代表生命周期管理的职责。MessageListener 仅仅实现消息消费逻辑,而整个消息消费何时开始、何时结束、如何设置、状态怎样等等问题全都是由 MessageListenerContainer(及其实现类)负责的。实际上,MessageListenerContainer 继承自 SmartLifecycle 接口,该接口是 Spring 容器提供的与生命周期管理相关的接口,实现该接口的类一般情况下会由 Spring 容器负责启动与停止。由于本例没有启用 Spring 容器环境,所以代码第26行需要主动调用 start 方法,消息消费才会开始执行。
 

## 内部实现思路

我们知道,amqp client 中的 Consumer 接口实际上只定义了回调方法,我们在回调方法(主要是 handleDelivery 方法)中实现自己的业务逻辑(对消息的消费)。Consumer 接口的回调方法实际上是在一个独立线程中执行的,当我们调用 Channel 的 basicConsume 方法时,amqp client 会创建线程处理消息、创建队列缓存从 broker 推送来的消息。然而这些内部实现并没有暴露出来,导致 Spring AMQP 必须自己重新编写一套类似的实现以获得最大的灵活度。
 
按照前面的分析,我们可以想象 Spring AMQP 为了实现自己的 message listener,需要哪些组件:
  • MessageListenerContainer 的实现类,即 org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer。它作为整个异步消息投递的核心类存在。
  • 因为 MessageListenerContainer 实际上管理了一个消费者线程组,因此需要相关线程类与线程调度类。Spring AMQP 中该线程类为 org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer,调度类当然就是 SimpleMessageListenerContainer,其 start 方法会启动线程
  • 消息队列推送过来的消息需要一个本地队列缓存。
  • 需要实现 amqp client 的 Consumer 接口。在该接口实现类中,我们简单的把消息放到本地队列中。org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$InternalConsumer 负责这件事
  • 根据单一职责原则,线程类只负责异步消费者的创建与(无限循环)消息消费;InternalConsumer 只负责实现 amqp client 的 Consumer 接口,与 amqp client 原生的异步消息投递实现对接,将消息放入本地队列。那么,我们还需要一个真正的异步消费者模型,用来管理消费行为与状态。org.springframework.amqp.rabbit.listener.BlockingQueueConsumer 承担这部分责任。从名字可以看出,BlockingQueueConsumer 采用 BlockingQueue 作为本地队列缓存消息。
  • 用户的业务逻辑是在 MessageListener 接口中实现的,框架的主要处理过程为:创建合适的连接与信道,从 amqp client 中获取消息暂存到本地缓存,从本地缓存读取消息并调用 MessageListener 接口的 onMessage 方法消费消息。

## 内部流程分析

SimpleMessageListenerContainer 的 start 方法会根据 int concurrentConsumers 的值创建对应数量的 BlockingQueueConsumer 实例,并放入 Set<BlockingQueueConsumer> consumers 中。接着为每一个 BlockingQueueConsumer 创建对应的消息处理线程 AsyncMessageProcessingConsumer(实现了 Runnable 接口),并通过 Executor taskExecutor = new SimpleAsyncTaskExecutor() 这个自实现的线程池启动每一个 AsyncMessageProcessingConsumer 线程。最后通过判断每一个 AsyncMessageProcessingConsumer 的 FatalListenerStartupException startupException 属性是否有值来判断 SimpleMessageListenerContainer 是否正常启动了所有的消息监听器。
 
构建 BlockingQueueConsumer 的构造函数参数很多,其中 ConnectionFactory 是代码第17行创建的 CachingConnectionFactory,AcknowledgeMode 默认为 AUTO。
org.springframework.amqp.core.AcknowledgeMode 定义了三种确认模式:
  • NONE:不确认,相当于 amqp client 中 Channel.basicConsume 方法中 autoAck 参数值设为 true
  • MANUAL:用户通过 channel aware listener 手动控制消息确认
  • AUTO:Spring AMQP 框架根据 message listener 的 onMessage 执行过程中是否抛出异常来决定是否确认消息消费
 
AsyncMessageProcessingConsumer 的 run 方法比较复杂,粗略解读一下
  1. 调用 BlockingQueueConsumer 的 start 方法(不是 Runnable 接口)。
  2. start 方法先通过 ConnectionFactoryUtils.getTransactionalResourceHolder 静态方法创建出供该线程使用的 channel,该方法返回类型是 RabbitResourceHolder。这部分代码涉及到事务,很复杂,但是本文的测试代码不涉及事务,目前只要了解多个 AsyncMessageProcessingConsumer 会生成多个 RabbitResourceHolder 实例,但是由于使用了 CachingConnectionFactory 的默认缓存模式,所以这些 RabbitResourceHolder 实例共用同一个(AMQP)连接,每个 AsyncMessageProcessingConsumer 独享该连接创建的一个(AMQP)信道即可
  3. start 方法接着创建 InternalConsumer 实例,并调用刚创建的 AMQP 信道的 basicQos 和  basicConsume 方法开始接受消息。这样,当队列接受到消息时,amqp client 会主动调用 InternalConsumer 的 handleDelivery 方法。该方法调用 BlockingQueueConsumer.this.queue.put(new Delivery(consumerTag, envelope, properties, body)); 将消息放到 BlockingQueueConsumer 的 BlockingQueue<Delivery> queue 中。org.springframework.amqp.rabbit.support.Delivery 类封装了 amqp client 通过  handleDelivery 方法回送过来的所有参数。
    有两个细节值得说一下:第一,BlockingQueueConsumer 可以同时消费多个队列,对每个队列,都会调用 basicConsume 方法让 InternalConsumer 监听当前队列(即同一个信道,同一个 Consumer ,不同的队列);其二,可以通过 ConsumerTagStrategy tagStrategy 设定 Tag 命名规则。
  4. 接着,在 while 循环中调用 SimpleMessageListenerContainer 的 receiveAndExecute 方法,不停的尝试从 queue 中获取 Delivery 实例,将之转化为 Message,然后执行 MessageListener 的 onMessage 回调方法。
  5. 如果执行成功,则调用 AMQP 信道的 basicAck 方法确认消息消费成功。
  6. 如果执行过程中发生异常,则将异常转化为 ListenerExecutionFailedException 抛出。默认情况下,Spring AMQP 处理用户自定义异常的逻辑非常简单:调用 AMQP 信道的 basicReject 方法将消息退回队列,打印 warning 级别的日志,但不会打破 AsyncMessageProcessingConsumer 线程的 while 循环,消息消费继续进行。这部分内容下篇文章分析。
 
 
 

Spring AMQP 源码分析 04 - MessageListener的更多相关文章

  1. Spring AMQP 源码分析 07 - MessageListenerAdapter

    ### 准备 ## 目标 了解 Spring AMQP 如何用 POJO 处理消息 ## 前置知识 <Spring AMQP 源码分析 04 - MessageListener> ## 相 ...

  2. Spring AMQP 源码分析 06 - 手动消息确认

    ### 准备 ## 目标 了解 Spring AMQP 如何手动确认消息已成功消费 ## 前置知识 <Spring AMQP 源码分析 04 - MessageListener> ## 相 ...

  3. Spring AMQP 源码分析 05 - 异常处理

    ### 准备 ## 目标 了解 Spring AMQP Message Listener 如何处理异常 ## 前置知识 <Spring AMQP 源码分析 04 - MessageListene ...

  4. Spring AMQP 源码分析 08 - XML 配置

    ### 准备 ## 目标 通过 XML 配置文件使用 Spring AMQP ## 前置知识 <Spring AMQP 源码分析 07 - MessageListenerAdapter> ...

  5. Spring AMQP 源码分析 02 - CachingConnectionFactory

    ### 准备 ## 目标 了解 CachingConnectionFactory 在默认缓存模式下的工作原理   ## 前置知识   <Spring AMQP 源码分析 01 - Impatie ...

  6. Spring AMQP 源码分析 03 - MessageConverter

    ### 准备 ## 目标 了解 Spring AMQP 消息转化实现   ## 相关资源 Quick Tour for the impatient:<http://docs.spring.io/ ...

  7. Spring AMQP 源码分析 01 - Impatient

    ### 准备   ## 目标 了解 Spring AMQP 核心代码   ## 前置知识 RabbitMQ 入门   ## 相关资源   Quick Tour for the impatient:&l ...

  8. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  9. spring事务源码分析结合mybatis源码(一)

    最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...

随机推荐

  1. 3:2 OGNL 简介

    OGNL : (对象图导航语言) 从一个对象到另一个对象 OGNL来源于Xwork: OGNL的作用: OGNL在数据进出值栈的时候进行类型转换

  2. 支持向量机(SVM)中的 SMO算法

    1. 前言 最近又重新复习了一遍支持向量机(SVM).其实个人感觉SVM整体可以分成三个部分: 1. SVM理论本身:包括最大间隔超平面(Maximum Margin Classifier),拉格朗日 ...

  3. 待解决:PDF header signature not found

  4. WireShark 基本介绍

    文中内容主要转自:http://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html 一.Wireshark 与 Fiddler 比较: F ...

  5. Kotlin新语言简介和快速入门知识点

    Kotlin新语言简介和快速入门知识点 简介:Kotlin是最近由JetBrains发布的一种基于JVM的编程语言,已经被Google宣布为开发Android App的一级语言Kotlin有着与Jav ...

  6. 05: jQuery

    目录: jQuery参考网站 W3school 1.1 JQuery作用 1.2 jQuery与DOM比较 与 相互转换 1.3 jQuery选择器 1.4 jQuery筛选与过滤 1.5 jQuer ...

  7. 20145329 《网络对抗技术》浏览器MS11_050安全漏洞攻击

    两台虚拟机: kali ip:192.168.96.130 windows xp sp3(包含IE7)ip:192.168.96.128 1.在kali终端中开启msfconsole. 2.进入漏洞模 ...

  8. 20145335郝昊《网络攻防》Exp4 Msf基础

    20145335郝昊<网络攻防>Exp4 Msf基础 实验内容 掌握metasploit的基本应用方式,掌握常用的三种攻击方式的思路. 一个主动攻击,如ms08_067; 一个针对浏览器的 ...

  9. CEF解决加载慢问题

    转载:http://blog.csdn.net/weolar/article/details/51994895 CEF加载慢的时候,加上以下代码,通过命令行的方式: CefRefPtr<CefC ...

  10. [VS 2015] VS2015 完整ISO镜像包

    区别 :https://www.visualstudio.com/zh-cn/products/compare-visual-studio-2015-products-vs 完整ISO镜像:下载 VS ...