RabbitMQ-笔记

-------------------------------------------------------------------------------------------------

为什么要使用消息队列

  1. 异步处理
  2. 系统解耦
    解耦是消息中间队列解决的最本质问题。所谓解耦,简单一点就是一个事务,只关心核心的流程。而需要依赖其他系统但不那么重要的事情,有通知即可,无需等待结果。换句话说,关心的是“通知,而非“处理”。
    比如下单过程中,需要发送短信积分,如果下游系统过慢(比如短信网关速度不好),主流程一直在等待。用户肯定不希望支付下单的过程中几分钟之后才得到结果。那么我们只需要通知短信系统“我们支付成功了”,不一定非要等待它处理完成。
  3. 流量削峰
    试想上下游对于事件的处理能力是不同的。比如,Web前端每秒承受上千万的请求,并不是什么神奇的事情,只需要加多一点机器,再搭建一些LVS负载均衡设备和Nginx等即可。但数据库的处理能力却十分有限,即使使用SSD加分库分表,单机的处理能力仍然在万级。由于成本的考虑,我们不能奢求数据库的机器数量追上前端。
    这种问题同样存在于系统和系统之间,如短信系统可能由于短板效应,速度卡在网关上(每秒几百次请求),跟前端的并发量不是一个数量级。但用户晚上个半分钟左右收到短信,一般是不会有太大问题的。如果没有消息队列,两个系统之间通过协商、滑动窗口等复杂的方案也不是说不能实现。但系统复杂性指数级增长,势必在上游或者下游做存储,并且要处理定时、拥塞等一系列问题。而且每当有处理能力有差距的时候,都需要单独开发一套逻辑来维护这套逻辑。所以,利用中间系统转储两个系统的通信内容,并在下游系统有能力处理这些消息的时候,再处理这些消息,是一套相对较通用的方式。
  4. 广播
    消息队列的基本功能之一是进行广播。如果没有消息队列,每当一个新的业务方接入,我们都要联调一次新接口。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,是下游的事情,无疑极大地减少了开发和联调的工作量。
  5. 最终一致性
    最终一致性指的是两个系统的状态保持一致,要么都成功,要么都失败。当然有个时间限制,理论上越快越好,但实际上在各种异常的情况下,可能会有一定延迟达到最终一致状态,但最后两个系统的状态是一样的。
    业界有一些为“最终一致性”而生的消息队列,如Notify(阿里)、QMQ(去哪儿)等,其设计初衷,就是为了交易系统中的高可靠通知。

本地事务维护业务变化和通知消息,一起落地(失败则一起回滚),然后RPC到达broker,在broker成功落地后,RPC返回成功,本地消息可以删除。否则本地消息一直靠定时任务轮询不断重发,这样就保证了消息可靠落地broker。
broker往consumer发送消息的过程类似,一直发送消息,直到consumer发送消费成功确认。

总结
消息队列不是万能的。对于需要强事务保证而且延迟敏感的,RPC是优于消息队列的。
对于一些无关痛痒,或者对于别人非常重要但是对于自己不是那么关心的事情,可以利用消息队列去做。
支持最终一致性的消息队列,能够用来处理延迟不那么敏感的“分布式事务”场景,而且相对于笨重的分布式事务,可能是更优的处理方式。
当上下游系统处理能力存在差距的时候,利用消息队列做一个通用的“漏斗”。在下游有能力处理的时候,再进行分发。

为什么要选择RabbitMQ

  1. 基于AMQP协议
  2. 高并发
  3. 高性能
  4. 高可用
  5. 强大的社区支持,以及很多公司都在使用
  6. 支持插件
  7. 支持多语言

AMQP协议介绍

出现背景

越是大型的公司越是不可避免的使用来自众多供应商的MQ产品,来服务企业内部的不同应用。如果应用已经订阅了TIBCO MQ信息,若突然需要消费来自IBM MQ的消息,则实现起来会非常困难。这些产品使用不同的api,不同的协议,因而毫无疑问无法联合起来组成单一的总线。为了解决这个问题,Java Message Service(JMS)在2001年诞生了。JMS试图通过提供公共java api的方式,隐藏单独MQ产品供应商提供的实际接口,从而跨越了壁垒和解决了互通问题。从技术上讲,java应用程序只需要对JMS API编程,选择合适的MQ驱动即可。JMS会打理好其他部分的。问题是你在尝试使用单独编准化接口来整合众多不同的接口。这就像是把不同的类型的衣服粘在一起:缝合处终究会裂开。使用JMS(Java Message Service)的应用程序会变得更加脆弱。我们需要新的消息通信标准化方案。

  • 高级消息队列协议(AMQP)是面向消息的中间件的开放标准应用层协议。 AMQP的特征是消息导向,排队,路由(包括点对点和发布和订阅),可靠性和安全性。

  • AMQP要求消息传递提供商和客户端的行为在不同供应商实现可互操作的情况下,以与SMTP,HTTP,FTP等相同的方式创建了可互操作的系统。 中间件的以前标准化发生在API级别(例如JMS),并且专注于使程序员与不同中间件实现的交互标准化,而不是提供多个实现之间(AMQP的实现)的互操作性。与定义API和消息传递实现必须提供的一组行为的JMS不同,AMQP是线级协议。 线级协议是以网络流作为字节流发送的数据格式的描述。 因此,无论实现语言如何,任何可以创建和解释符合此数据格式的消息的工具都可以与任何其他兼容工具进行互操作。

  • AMQP协议是具有现代特征的二进制协议。一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开发标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。

  • AMQP是一种二进制应用层协议,旨在有效地支持各种消息应用和通信模式。 它提供流控制的面向消息的通信,其中包括消息传递保证,例如最多一次(每个消息被投递一次或从不投递消息),至少一次(每个消息肯定要被传递,但可以在不同的时间)和确定一次(其中消息将始终确定到达并仅执行一次),以及基于SASL和/或TLS的身份验证和/或加密。 它假定一个基本的可靠传输层协议,如传输控制协议(TCP)。

  • AMQP规范定义在几个层次中:(i)类型系统(传递的消息类型),(ii)用于将消息从一个进程转移到另一个进程的对称异步协议,(iii)标准的可扩展消息格式(iv)一系列的标准化但可扩展的“消息传递功能”。

一些概念梳理

  • Server:又称为Broker。接收客户端连接,实现AMQP的服务器实体。
  • Connection:连接,应用程序与Broker的网络连接。
  • Channel:信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道。客户端可建立多个Channel,每个Channel代表一个会话任务。
  • Message:消息。服务器和应用程序之间传递的数据,本质上就是一段数据,由Properties和Body组成。
  • Exchange:交换机。接收消息,根据路由键转发消息到绑定的队列。
  • Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key。
  • Routing key:一个虚拟地址,虚拟机可用它来确定如何路由一个特定消息。
  • Queue:也称为Message Queue,消息队列,保存消息并将它们转发给消费者。
  • Virtual Host:其实是一个虚拟概念。类似于权限控制组,一个Virtual Host里面可以有若干个Exchange和Queue,可以用来隔离Exchange和Queue。,同一个Virtual Host里面不能有相同名称的Exchange和Queue。但是权限控制的最小粒度是Virtual Host。(下面会讲到)
 
AMQP协议模型

总结
生产者将消息发送到Exchange交换机的,不是发送到Queue上的,生产者不知道消息是谁消费,有哪些消费者消费。Exchange根据一定的路由规则将消息转发到Queue。
消费者是监听队列的,不知道是哪个生产者发送的。

AMQP我的理解
一个开放的面向消息中间件的协议,所有此协议的实现可以进行互相操作,无论实现语言如何,任何符合此协议的数据格式的消息工具都可以与任何其他兼容工具进行互操作。而以前JMS(Java Message Service),将不同的中间件的实现进行API层次的标准化。

-------------------------------------------------------------------------------------------------

RabbitMQ的安装

RabbitMQ下载地址

RabbitMQ是由LShift提供的一个Advanced Message Queuing Protocol(AMQP)的开源实现,由以高性能,健壮以及可伸缩性出名的Erlang写成,因此也继承了这些优点。

二进制的安装方式,

我本地选择131这台服务器进行安装,修改hostname

[root@localhost ~]# ifconfig
eth1      Link encap:Ethernet  HWaddr 00:50:56:2F:74:85  
          inet addr:192.168.1.131  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::250:56ff:fe2f:7485/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:22129 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8960 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:29664818 (28.2 MiB)  TX bytes:581671 (568.0 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:4 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:240 (240.0 b)  TX bytes:240 (240.0 b)

修改hostname

[root@localhost ~]# cd /etc/sysconfig/
[root@localhost sysconfig]# vim network

配置如下:

NETWORKING=yes
HOSTNAME=mqserver

修改hosts

[root@localhost sysconfig]# vim /etc/hosts

配置如下:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.1.131 mqserver

关闭防火墙:

[root@localhost sysconfig]# chkconfig iptables off

重启一下机器:

[root@localhost sysconfig]# reboot

将下载好的rabbitmq-server-3.6.10-1.el6.noarch.rpm放到指定的目录下/usr/local/software

安装rabbitmq-server

[root@mqserver software]# rpm -ivh rabbitmq-server-3.6.10-1.el6.noarch.rpm
warning: rabbitmq-server-3.6.10-1.el6.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 6026dfca: NOKEY
error: Failed dependencies:
    erlang >= R16B-03 is needed by rabbitmq-server-3.6.10-1.el6.noarch
    socat is needed by rabbitmq-server-3.6.10-1.el6.noarch

缺少erlang-18.3-1.el6.x86_64.rpm的依赖,到RabbitMQ的指定目录下去下载erlang-18.3-1.el6.x86_64.rpm也上传至指定目录,

erlang-18.2-1.el6.x86_64.rpm针对centos6.*版本的,erlang-18.2-1.el7.centos.x86_64.rpm针对的centos7.*版本

安装erlang

[root@mqserver software]# rpm -ivh erlang-18.3-1.el6.x86_64.rpm

注意安装erlang的时候有时候会报缺少包之类的错误,可以先执行下面的命令先下载好整个linux系统的一些依赖:

[root@mqserver software]# yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

验证Erlang是否安装成功

[root@mqserver software]# erl
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V7.3  (abort with ^G)
1> 

再次去安装RabbitMQ服务

[root@mqserver software]# rpm -ivh rabbitmq-server-3.6.10-1.el6.noarch.rpm
warning: rabbitmq-server-3.6.10-1.el6.noarch.rpm: Header V4 RSA/SHA512 Signature, key ID 6026dfca: NOKEY
error: Failed dependencies:
    socat is needed by rabbitmq-server-3.6.10-1.el6.noarch

再次提醒我们有一些依赖没有安装,将socat-1.7.3.2-1.el6.lux.x86_64.rpm上传至当前目录
安装socat

[root@mqserver software]# rpm -ivh socat-1.7.3.2-1.el6.lux.x86_64.rpm

再去安装rabbitmq

[root@mqserver software]# rpm -ivh rabbitmq-server-3.6.10-1.el6.noarch.rpm

启动rabbitmq服务:

[root@mqserver software]# cd /etc/init.d 
[root@mqserver init.d]# ./rabbitmq-server restart
Restarting rabbitmq-server: RabbitMQ is not running
SUCCESS
rabbitmq-server.

查看默认端口5672(默认端口)是否启动,

[root@mqserver init.d]# lsof -i:5672
COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam    2793 rabbitmq   49u  IPv6  35431      0t0  TCP *:amqp (LISTEN)

二进制方式安装方式总结

  1. 安装erlang
  2. 安装socat
  3. 安装rabbitmq

使用tar包安装

本地使用133服务器进行安装

查看网卡

[root@mqserver software]# ifconfig
eth1      Link encap:Ethernet  HWaddr 00:50:56:2F:EC:B5  
          inet addr:192.168.1.133  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::250:56ff:fe2f:ecb5/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:72432 errors:0 dropped:0 overruns:0 frame:0
          TX packets:38885 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:95728391 (91.2 MiB)  TX bytes:2941953 (2.8 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:10 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:744 (744.0 b)  TX bytes:744 (744.0 b)

修改hostname

[root@localhost ~]# cd /etc/sysconfig/
[root@localhost sysconfig]# vim network

配置如下:

NETWORKING=yes
HOSTNAME=mqserver

修改hosts

[root@localhost sysconfig]# vim /etc/hosts

配置如下:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.1.133 mqserver

关闭防火墙:

[root@localhost sysconfig]# chkconfig iptables off

重启一下机器:

[root@localhost sysconfig]# reboot

rabbitmq-server-generic-unix-3.6.10.tar.xzerlang-18.3-1.el6.x86_64.rpm文件上传到指定目录下(/usr/local/software)

安装erlang,跟上面一样先安装一些依赖

[root@mqserver software]# yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

再去安装erlang

[root@mqserver software]# rpm -ivh erlang-18.3-1.el6.x86_64.rpm

解压tar包(关于tar的下载地址也在博客最上面的链接上)

[root@mqserver software]# tar -xvf rabbitmq-server-generic-unix-3.6.10.tar.xz

进入解压后的sbin目录下:

[root@mqserver software]# cd rabbitmq_server-3.6.10/sbin
[root@mqserver sbin]# ll
total 44
-rwxr-xr-x. 1 1023 1023  1480 May 25 06:55 rabbitmqctl
-rwxr-xr-x. 1 1023 1023  1885 May 25 06:55 rabbitmq-defaults
-rwxr-xr-x. 1 1023 1023 12095 May 25 06:55 rabbitmq-env
-rwxr-xr-x. 1 1023 1023  1362 May 25 06:55 rabbitmq-plugins
-rwxr-xr-x. 1 1023 1023 10971 May 25 06:55 rabbitmq-server
[root@mqserver sbin]# ./rabbitmq-server & 
[1] 2127
[root@mqserver sbin]# 
              RabbitMQ 3.6.10. Copyright (C) 2007-2017 Pivotal Software, Inc.
  ##  ##      Licensed under the MPL.  See http://www.rabbitmq.com/
  ##  ##
  ##########  Logs: /usr/local/software/rabbitmq_server-3.6.10/var/log/rabbitmq/rabbit@mqserver.log
  ######  ##        /usr/local/software/rabbitmq_server-3.6.10/var/log/rabbitmq/rabbit@mqserver-sasl.log
  ##########
              Starting broker...
 completed with 0 plugins.

检查rabbitmq是否启动

[root@mqserver sbin]# lsof -i:5672
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam    2216 root   48u  IPv6  17882      0t0  TCP *:amqp (LISTEN)

停止服务:

[root@mqserver sbin]# ./rabbitmqctl stop

使用./rabbitmq-server -detached 也是后台启动

[root@mqserver sbin]# ./rabbitmq-server -detached
Warning: PID file not written; -detached was passed.
[root@mqserver sbin]# lsof -i:5672
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam    2601 root   48u  IPv6  19438      0t0  TCP *:amqp (LISTEN)

编写二个脚本:start.sh,stop.sh

[root@mqserver sbin]# vim start.sh

内容是

./rabbitmq-server -detached
[root@mqserver sbin]# vim stop.sh

内容是

./rabbitmqctl stop

授权,可以使用这二个脚本进行mq的启动和停止

[root@mqserver sbin]# chmod 777 start.sh stop.sh
[root@mqserver sbin]# ./stop.sh 
Stopping and halting node rabbit@mqserver
[root@mqserver sbin]# lsof -i:5672
[root@mqserver sbin]# ./start.sh 
Warning: PID file not written; -detached was passed.
[root@mqserver sbin]# lsof -i:5672
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam    3017 root   48u  IPv6  20001      0t0  TCP *:amqp (LISTEN)

RabbitMQ命令的基本操作

查看rabbitmq-server的所在位置,以下三个命令仅在二进制安装方式下有效

[root@mqserver ~]# type rabbitmq-server
rabbitmq-server is /usr/sbin/rabbitmq-server

管理插件的命令:

[root@mqserver sbin]# type rabbitmq-plugins
rabbitmq-plugins is hashed (/usr/sbin/rabbitmq-plugins)

rabbitmqctl指令的位置,rabbitmqctl指令很强大,下面我们会讲到

[root@mqserver ~]# type rabbitmqctl
rabbitmqctl is /usr/sbin/rabbitmqctl

了解一下强大的命令:rabbitmqctl

rabbitmqctl status:查看rabbitmq的运行状态。

[root@iZbp1jcwx7sfb1nwzrehy6Z sbin]# rabbitmqctl status

查看rabbitmqctl所有的命令

[root@mqserver ~]# rabbitmqctl

比如下面这些,查看当前mq的队列,exchanges,connections,channels,consumers等等

   list_queues [-p <vhost>] [--offline|--online|--local] [<queueinfoitem> ...]
    list_exchanges [-p <vhost>] [<exchangeinfoitem> ...]
    list_bindings [-p <vhost>] [<bindinginfoitem> ...]
    list_connections [<connectioninfoitem> ...]
    list_channels [<channelinfoitem> ...]
    list_consumers [-p <vhost>]
    status

查看queues,刚开始安装rabbitmq的时候没有队列:

[root@mqserver init.d]# rabbitmqctl list_queues
Listing queues

查看exchanges

[root@mqserver init.d]# rabbitmqctl list_exchanges
Listing exchanges
amq.direct      direct
        direct
amq.match       headers
amq.rabbitmq.log        topic
amq.topic       topic
amq.headers     headers
amq.rabbitmq.trace      topic
amq.fanout      fanout

查看bingding,刚安装服务的时候也没有binding

[root@mqserver init.d]# rabbitmqctl list_bindings
Listing bindings

查看一些用户:

[root@mqserver init.d]# rabbitmqctl list_users
Listing users
guest   [administrator]

查看vhosts

[root@mqserver init.d]# rabbitmqctl list_vhosts
Listing vhosts
/

添加一个用户:

[root@mqserver init.d]# rabbitmqctl add_user zhihao.miao 123456
Creating user "zhihao.miao"
[root@mqserver init.d]# rabbitmqctl list_users
Listing users
zhihao.miao     []
guest   [administrator]

此时发现zhihao.miao这个用户还没有权限。

设置权限,设置完成之后发现设置成功:

[root@mqserver init.d]# rabbitmqctl set_user_tags zhihao.miao administrator
Setting tags for user "zhihao.miao" to [administrator]
[root@mqserver init.d]# rabbitmqctl list_users
Listing users
zhihao.miao     [administrator]
guest   [administrator]

删除用户:

[root@iZbp1jcwx7sfb1nwzrehy6Z sbin]# rabbitmqctl  delete_user zhihao.miao
Deleting user "admin"
[root@iZbp1jcwx7sfb1nwzrehy6Z sbin]# rabbitmqctl list_users
Listing users
guest   [administrator]

停止rabbitmq

./rabbitmqctl stop

rabbitmq的参数设置

RabbitMQ配有默认内置设置。 在某些环境(例如开发和质量保证)中,这些可以是完全足够的。 如果运行正常,则可能根本不需要任何配置。 但是在一些情况下,我们需要配置一些参数区分于默认的配置。
比如rabbitmq的默认端口是5672,如何去改变它呢?

三种配置参数的方法

比如我想想rabbitmq默认的使用端口5672改为5673,那么怎么操作,使用配置文件 Configuration File的方式。
RabbitMQ核心应用, Erlang 服务and RabbitMQ 插件都会使用rabbitmq.config进行相关参数配置。

如果是Generic UNIX安装方式在$RABBITMQ_HOME/etc/rabbitmq/的目录下放置rabbitmq.config
RPM的安装方式那么就在/etc/rabbitmq/目录下放置rabbitmq.config
我的192.168.1.131服务器上就是使用的RPM的安装方式,

vim rabbitmq.config

配置方式:

[
    {rabbit, [{tcp_listeners, [5673]}]}
].

官网提供了一份rabbitmq.config示列,此示例文件包含您可能想要设置的大多数配置项(省略一些非常模糊的配置)以及这些设置的文档的示例。 所有配置项都在示例中注释掉,因此您可以取消注册所需的内容。请注意,不要将其当作一般的推荐配置。

其他的配置:

 
其他配置项

插件的安装

查看当前可以安装的插件(使用tar安装的进入相关的解压包下的sbin目录):

[root@mqserver sbin]# cd /usr/sbin/
[root@mqserver sbin]# ./rabbitmq-plugins list
 Configured: E = explicitly enabled; e = implicitly enabled
 | Status:   * = running on rabbit@mqserver
 |/
[  ] amqp_client                       3.6.10
[  ] cowboy                            1.0.4
[  ] cowlib                            1.0.2
[  ] rabbitmq_amqp1_0                  3.6.10
[  ] rabbitmq_auth_backend_ldap        3.6.10
[  ] rabbitmq_auth_mechanism_ssl       3.6.10
[  ] rabbitmq_consistent_hash_exchange 3.6.10
[  ] rabbitmq_event_exchange           3.6.10
[  ] rabbitmq_federation               3.6.10
[  ] rabbitmq_federation_management    3.6.10
[  ] rabbitmq_jms_topic_exchange       3.6.10
[  ] rabbitmq_management               3.6.10
[  ] rabbitmq_management_agent         3.6.10
[  ] rabbitmq_management_visualiser    3.6.10
[  ] rabbitmq_mqtt                     3.6.10
[  ] rabbitmq_recent_history_exchange  3.6.10
[  ] rabbitmq_sharding                 3.6.10
[  ] rabbitmq_shovel                   3.6.10
[  ] rabbitmq_shovel_management        3.6.10
[  ] rabbitmq_stomp                    3.6.10
[  ] rabbitmq_top                      3.6.10
[  ] rabbitmq_tracing                  3.6.10
[  ] rabbitmq_trust_store              3.6.10
[  ] rabbitmq_web_dispatch             3.6.10
[  ] rabbitmq_web_mqtt                 3.6.10
[  ] rabbitmq_web_mqtt_examples        3.6.10
[  ] rabbitmq_web_stomp                3.6.10
[  ] rabbitmq_web_stomp_examples       3.6.10
[  ] sockjs                            0.3.4```

安装管控页面:

[root@mqserver sbin]# ./rabbitmq-plugins enable rabbitmq_management

卸载插件:

[root@mqserver sbin]# ./rabbitmq-plugins enable rabbitmq_management

安装管控台之后,rabbitmq的web管控台默认占用的端口是15672

[root@mqserver sbin]# lsof -i:15672
COMMAND  PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam    3432 rabbitmq   50r  IPv4  25122      0t0  TCP *:15672 (LISTEN)

默认的用户名密码是guest,guest,但是只能在localhost:15672或者127.0.0.1:15672上登录。

如何让guest来进行ip登录呢?

loopback用户,只能用localhost,127.0.0.1地址登录。如果将自己设置的用户加入到loopback_users,那么此时该用户也只能通过localhost,127.0.0.1地址进行登录了。

[root@mqserver sbin]# cd /etc/rabbitmq/
[root@mqserver rabbitmq]# vim rabbitmq.config 

rabbitmq.config的内容如下,

[
    {rabbit, [{tcp_listeners, [5672]},{loopback_users,[]}]}
].

重启rabbitmq服务,

[root@mqserver software]# cd /etc/init.d 
[root@mqserver init.d]# ./rabbitmq-server restart

配置之后就可以使用guest用户访问http://192.168.1.131:15672/#就可以了。

-------------------------------------------------------------------------------------------------

Exchange概念

Exchange:交互机,根据路由键转发消息到绑定的队列。

 
RabbitMQ架构图

自己说说Exchange在RabbitMQ消息中间件中的作用:
服务器发送消息不会直接发送到队列中(Queue),而是直接发送给交换机(Exchange),然后根据确定的规则,RabbitMQ将会决定消息该投递到哪个队列。这些规则称为路由键(routing key),队列通过路由键绑定到交换机上。消息发送到服务器端(broker),消息也有自己的路由键(也可以是空),RabbitMQ也会将消息和消息指定发送的交换机的绑定(binding,就是队列和交互机的根据路由键映射的关系)的路由键进行匹配。如果匹配的话,就会将消息投递到相应的队列。

Exchange的类型主要有四种,分别是

Direct Exchange:将消息中的Routing key与该Exchange关联的所有Binding中的Routing key进行比较,如果相等,则发送到该Binding对应的Queue中。

Topic Exchange:将消息中的Routing key与该Exchange关联的所有Binding中的Routing key进行对比,如果匹配上了,则发送到该Binding对应的Queue中。

Fanout Exchange:直接将消息转发到所有binding的对应queue中,这种exchange在路由转发的时候,忽略Routing key

Headers Exchange:将消息中的headers与该Exchange相关联的所有Binging中的参数进行匹配,如果匹配上了,则发送到该Binding对应的Queue中。

查看exchanges属性,在管控台上查看http://192.168.1.131:15672/#/exchanges

 
管控台
 
创建一个exchange

相关属性的说明,如果有多个Virtual host,则还会有Virtual host属性。一般默认的Virtual host是"/",我们知道Virtual host可以做最小粒度的权限控制。

 
创建Virtual host的界面
  • Virtual host:属于哪个Virtual host。
  • Name:名字,同一个Virtual host里面的Name不能重复。
  • Durability: 是否持久化,Durable:持久化。Transient:不持久化。
  • Auto delete:当最后一个绑定(队列或者exchange)被unbind之后,该exchange自动被删除。
  • Internal: 是否是内部专用exchange,是的话,就意味着我们不能往该exchange里面发消息。
  • Arguments: 参数,是AMQP协议留给AMQP实现做扩展使用的。
    alternate_exchange配置的时候,exchange根据路由路由不到对应的队列的时候,这时候消息被路由到指定的alternate_exchange的value值配置的exchange上。(下面的博客会有说明这参数的具体使用)
 
我们设定一个Auto delete属性是Yes的exchange
 
绑定关系

unbing之后该exchange删除。

命令行查看exchange信息

使用命令查看exchanges列表,默认的Virtual host

[root@mqserver ~]# rabbitmqctl list_exchanges
Listing exchanges
amq.direct      direct
        direct
amq.match       headers
amq.rabbitmq.log        topic
amq.topic       topic
amq.headers     headers
amq.rabbitmq.trace      topic
amq.fanout      fanout

指定某个Virtual host的exchanges列表,我指定的事默认的Virtual host(/)

[root@mqserver ~]# rabbitmqctl list_exchanges -p /
Listing exchanges
amq.direct      direct
        direct
amq.match       headers
amq.rabbitmq.log        topic
amq.topic       topic
amq.headers     headers
amq.rabbitmq.trace      topic
amq.fanout      fanout

使用restful api查看exchanges列表,api的文档地址,(http://192.168.1.131:15672/api/)

对应着下面的链接,当前管控台的url/api


 
 

具体的地址,输入对应的用户名和密码,看到对应用户的exchanges列表

http://192.168.1.131:15672/api/exchanges

Direct Exchange

将消息中的Routing key与该Exchange关联的所有Binding中的Routing key进行比较,如果相等,则发送到该Binding对应的Queue中。

  1. 一个Exchange可以Binding一个或多个Queue
  2. 绑定可以指定Routing keyBinding的多个Queue可以使用相同的Routing key,也可以使用不同的Routing key
 
 

创建三个Exchange,名称分别是login,logout,register三个exchange。

创建几个Queue,名称分别是PCWAPAPPOA

指定它们的Binding关系,

测试:


 
login exchange的绑定关系

查看队列中的消息

 
 

其他的可自行测试

特别的Exchange
默认的Exchange(名字为空,AMQP default)

  1. 默认的Exchange不能进行Binding操作
  2. 任何发送到该Exchange的消息都会被转发到Routing key指定的Queue
  3. 如果vhost中不存在Routing key中指定的队列名,则该消息会被抛弃。
 
指定到OA队列中
 
队列为OA接收到该消息

Topic Exchange

将消息中的Routing key与该Exchange关联的所有Binding中的Routing key进行对比,如果匹配上了,则发送到该Binding对应的Queue中。

匹配规则

* 匹配一个单词
# 匹配0个或多个字符
*,# 只能写在.号左右,且不能挨着字符
单词和单词之间需要用.隔开。

列子

  • Routing key是user.log.#,因为#是匹配0个或多个字符,所以下面的可以匹配:
user.log
user.log.info
user.log.a
user.log.info.login
  • Routing key是user.log.,因为 匹配一个单词,所以
user.log.info 可以匹配
user.log 不能匹配
user.log.info.login 不能匹配,二个单词
  • Routing key是#.log.#
    可以匹配:
log
user.log
log.info
user.log.info
user.log.info.a
  • Routing key是.log.
log 不匹配
user.log 不匹配
log.info 不匹配
user.log.info 匹配,前后各一个单词
user.log.info.a 不匹配
a.user.log.info 不匹配
  • Routing key是*.action.#
action 不符合
action.log 不符合
user.action.log 符合
user.action.log.info 符合
user.action 符合
user.log.action 不符合
  • Routing key是#.action.*
action 不符合
user.action 不符合
user.action.action 符合
user.action.login 符合
user.action.login.count 不符合
  • Routing key是* 表示匹配一个单词
  • Routing key是#,或者#.# 表示匹配所有

如果指定了Exchange是Topic类型的,但是相应的Binding中的Routing key *#都没有,则相等才转发,类似于Direct Exchange
如果Binding中的Routing key#或者#.#,则全部转发,类似Fanout Exchange(下面会讲到)

测试:
建立一个名称为logtopic类型的Exchange

新建三个队列q1,q2,q3分别绑定 * ,#,#.#

 
定义的exchange与queue及Routing key

自己定义一脚本,便于测试的时候清除所有队列的消息:

cd /u01
vim purge.sh
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/q1/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/q2/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/q3/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/action_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/log2_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/log_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/sys_log_info_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/sys_log_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user2_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user_action_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user_log_debug_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user_log_info_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user_log_queue/contents
curl -X DELETE -u zhihao.miao:123456 http://192.168.1.131:15672/api/queues/%2F/user_queue/contents

给脚本授权

chmod 777 purge.sh

执行脚本

sh purge.sh

测试发送Routing key为这些的消息。

user
user.log
user.log.info
user.log.debug
sys
sys.log.debug

特殊情况,定义一队列q4exchange指定bindingreg,没有*#,那么当发送的消息route keyreg的时候,能够发送到q4上,此时类似于Direct Exchange

总结
topic Exchange可以实现Direct ExchangeFanout Exchange的效果。

Fanout Exchange

直接将消息转发到所有binding的对应queue中,这种exchange在路由转发的时候,忽略Routing key

Fanout Exchange这种exchange效率最高,fanout > direct > topic

定义一个Fanout类型的Exchange,绑定了一些队列,发送的时候全部队列都能收到消息,而与其bind或者发送消息指定的Routing key无关。

 
 

使用topic Exchange实现Fanout Exchange
topic Exchange将所有bindingqueuerouting key都指定为#或者#.#,此时消息也是全部转发。

Headers Exchange

将消息中的headers与该Exchange相关联的所有Binging中的参数进行匹配,如果匹配上了,则发送到该Binding对应的Queue中。

匹配规则:
如果Binding中的
x-match = all:表示所有的键值对都匹配才能转发到消息。
x-match = any: 表示只要有键值对匹配就能转发消息。

注意:

  1. Binging的时候,至少需要指定两个参数,其中的一个是x-match = allx-match = any
  2. Binging的时候,不需要指定Routing key
  3. 发送消息的时候,不需要指定Routing key
  4. 转发消息的时候,忽略Routing key
  5. 如果是x-match = all则发送的headers不能比bingding的参数少,否则匹配不上。

列子:


 
 
 
 

此时header.debug符合条件,收到消息。

 
 

此时还是header.debug符合条件,收到消息。

 
 

此时header.debug和header.error都收到消息。

 
 

此时header.debug和header.error都收到消息。

-------------------------------------------------------------------------------------------------

Binding详解

 
黄线部分就是binding

ExchangeExchangeQueue之间的虚拟连接,Binding中可以包含Routing key或者参数

 
创建binding

注意:

  • default Exchange不能进行Binding,也不需要进行绑定。
  • 除default Exchange之外,其他任何Exchange都需要和Queue进行Binding,否则无法进行消息路由(转发)
  • Binding的时候,可以设置一个或多个参数,其中参数要特别注意参数类型,如果Routing key中指定的参数类型和消息中指定的参数类型不一致(header Exchange)也不能进行消息转发。
  • Direct Exchange,Topic Exchange进行Binding的时候,需要指定Routing key
  • Fanout Exchange,Headers Exchange进行Binding的时候,不需要指定Routing key。

Queue详解

Queue称为Message Queue,消息队列,保存消息并将它们转发给消费者。

 
RabbitMQ架构图

属性

  • Durability:是否持久化,Durable是,Transient是否。如果不持久化,那么在服务器宕机或重启之后Queue就会丢失。
  • Auto delete:如果选择yes,当最后一个消费者不在监听Queue的时候,该Queue就会自动删除,一般选择false。
  • Arguments:AMQP协议留给AMQP实现者扩展使用的。
    x-message-ttl:一个消息推送到队列中的存活时间。设置的值之后还没消费就会被删除。
    x-expires:在自动删除该队列的时候,可以使用该队列的时间。
    x-max-length:在队列头部删除元素之前,队列可以包含多少个(就绪)消息,如果再次向队列中发送消息,会删除最早的那条消息,用来控制队列中消息的数量。
    x-max-length-bytes:在队列头部删除元素之前,队列的总消息体的大小,用来控制队列中消息的总大小。
    x-dead-letter-exchange:当消息被拒绝或者消息过期,消息重新发送到的交换机(Exchange)的可选名称。
    x-dead-letter-routing-key:当消息被拒绝或者消息过期,消息重新发送到的交换机绑定的Route key的名称,如果没有设置则使用之前的Route key。
    x-max-priority:队列支持的最大优先级数,如果没有设置则不支持消息优先级
    x-queue-mode:将队列设置为延迟模式,在磁盘上保留尽可能多的消息以减少RAM使用; 如果未设置,队列将保持在内存中的缓存,以尽可能快地传递消息。
    x-queue-master-locator:将队列设置为主位置模式,确定在节点集群上声明队列主节点所在的规则。

可以在控制台上查看queue列表还可以通过rabbitmqctl list_queues命令进行查看。

[root@mqserver ~]# rabbitmqctl list_queues
Listing queues
weixin  0
duanxin 0
APP     0
email   0
WAP     0
zhihao.miao.order       0
PC      0
OA      1

指定某个vhost下的队列:

[root@mqserver ~]# rabbitmqctl list_queues -p /
Listing queues
weixin  0
duanxin 0
APP     0
email   0
WAP     0
zhihao.miao.order       0
PC      0
OA      1

可以通过restful api来查看:http://192.168.1.131:15672/api/queues输入用户名密码即可。

 
具体的api可以点击

 

Message详解

消息。服务器和应用程序之间传送的数据,本质上就是一段数据,由Properties和Payload(body)组成。

 
 

Delivery mode:是否持久化,如果未设置持久化,转发到queue中并未消费则重启服务或者服务宕机则消息丢失。
Headers:头信息,是由一个或多个健值对组成的,当固定的Properties不满足我们需要的时候,可以自己扩展。

Properties(属性)
content_type:传输协议
content_encoding:编码方式
priority:优先级
correlation_id:rpc属性,请求的唯一标识。
reply_to:rpc属性,
expiration:消息的过期时间
message_id:消息的id
timestamp:消息的时间戳
...

如何保证消息的不丢失,三个地方做到持久化。

  1. Exchange需要持久化。
  2. Queue需要持久化。
  3. Message需要持久化。

-------------------------------------------------------------------------------------------------

java client的使用

本篇博客介绍RabbitMQ java client的一些简单的api使用,如声明Exchange,Queue,发送消息,消费消息,一些高级api会在下面的章节详细的说明。

概述

首先加入RabbitMQ java client依赖:

<dependencies>
      <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>4.0.2</version>
      </dependency>
</dependencies>

RabbitMQ的java client使用com.rabbitmq.client作为其顶级包。关键的类和接口是:

com.rabbitmq.client.Channel
com.rabbitmq.client.Connection
com.rabbitmq.client.ConnectionFactory
com.rabbitmq.client.Consumer

通过Channel可以进行一系列的api操作。 Connection(连接)用于打开通道,注册连接生命周期事件处理程序,并关闭不再需要的连接。 Connection(连接)通过ConnectionFactory实例化,ConnectionFactory可以设置一些Collection(连接)的一些配置,比如说vhost或者说username等等。

Connections(连接)和Channels(管道)

核心的类是Connections(连接)和Channels(管道),分别代表着AMQP 0-9-1协议中的Connections(连接)和Channels(管道),一般被导入

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;

连接服务器

下面的代码时使用给定的参数(host name,端口等等)连接AMQP的服务器。

ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();

所有的这些参数RabbitMQ服务器都设置了默认值,可以在ConnectionFactory类中查看这些默认值。

另外,URI可以以下面的方法进行连接都有默认值。

ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection conn = factory.newConnection();

Connection(连接)接口可以被用作创建一个channel(管道):

Channel channel = conn.createChannel();

可以使用channel(管道)发送和接收消息,下面会有讲到。

关闭连接,只需要关闭channel(管道)和connection(连接):

channel.close();
conn.close();

注意,关闭管道是被认为是最佳实践,但是却不是严格意义的必要的。当底层的连接关闭时候,channel(管道)也就自动的被关闭了。

使用Exchanges和Queues

客户端应用必须应用在exchanges和queues,这些都是AMQP协议定义的。使用这些(exchanges和queues)首先必须“声明”它(就是创建的意思)。

下面的代码就是怎样去"声明"一个exchange和队列,并且将它们绑定在一起。

channel.exchangeDeclare(exchangeName, "direct", true);
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, exchangeName, routingKey);

可以通过参数去设置exchange和queue的一些属性,使用这些方法的一些重载方法进行相关设置。

channel.exchangeDeclare(exchangeName, "direct", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);

发送消息(Publishing messages)

使用Channel.basicPublish方法将消息发送给一个exchange:

byte[] messageBodyBytes = "Hello, world!".getBytes();
channel.basicPublish(exchangeName, routingKey, null, messageBodyBytes);

为了更好的控制,你可以使用重载的参数来设置消息的一些属性(比如说mandatory标志,关于mandatory标志,下面会讲到),或者在发送消息前设定一些消息属性。

channel.basicPublish(exchangeName, routingKey, mandatory,
                     MessageProperties.PERSISTENT_TEXT_PLAIN,
                     messageBodyBytes);

可以自己构建BasicProperties的对象,如下面的代码:

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .contentType("text/plain")
               .deliveryMode(2)
               .priority(1)
               .userId("bob")
               .build()),
               messageBodyBytes);

发送消息指定头信息:

Map<String, Object> headers = new HashMap<String, Object>();
headers.put("latitude",  51.5252949);
headers.put("longitude", -0.0905493);

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .headers(headers)
               .build()),
               messageBodyBytes);

发送一个有过期时间的消息,下面的博客也会讲到:

channel.basicPublish(exchangeName, routingKey,
             new AMQP.BasicProperties.Builder()
               .expiration("60000")
               .build()),
               messageBodyBytes);

通道和并发注意事项(线程安全)

根据经验,在线程间共享Channel(通道)是要避免的。应用应该优先使用每个线程自己的Channel(通道)实例,而不是多个线程共享这个Channel(通道)实例。

虽然有些在Channel(通道)上的操作是可以并发安全的调用,但是一些操作不行会导致一些边界交错,双重确认等等。

在共享(多线程)Channel(通道)上进行并发发布会导致一些边界交错,触发连接协议异常和连接关闭。因此需要严格在应用中同步调用(Channel#basicPublish必须在正确关键的地方调用)。线程之间的共享也会干扰生产者的消息确认。我们强烈的推荐不应该在通道上进行并发的发布消息。

在共享的Channel(通道)上一个线程生产(publish)消息,一个线程消费(consume)消息是线程安全的。

服务器推送可以同时发送,保证每通道的订阅被保留。 调度机制使用java.util.concurrent.ExecutorService。 可以使用单列的ConnectionFactory调用ConnectionFactory#setSharedExecutor去设置所有连接共用的executor

当我们手动确认manual acknowledgements 的时候,很重要的是考虑什么线程去做这个ack确认。如果接收传递的线程(例如,Consumer#handleDelivery委托给不同线程的传递处理)不同于手动确认的线程,则将多个线程参数设置为true是线程不安全的并导致双重确认,因此导致通道协议异常导致Channel关闭。一次确认一条消息可以确保安全的。

订阅消息("Push API")

import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;

最有效的接收消息的方法是使用Consumer接口去订阅。当消息到达消费端的时候会自动的传递消费(delivered),而不需要去请求。

当我们调用Consumers(消费者)有关的api的时候,会生成一个消费者标识符(consumer tag)。

不同的Consumer实例必须有不同的消费者标签。 强烈建议不要在连接上重复使用消费者标签,不然在监视消费者时可能导致自动连接恢复和混淆监控数据的问题。

实现Consumer的最简单的方法是将便利(convenience)类DefaultConsumer子类化。 该子类的对象可以在basicConsume方法调用中传递以设置订阅:

boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
     new DefaultConsumer(channel) {
         @Override
         public void handleDelivery(String consumerTag,
                                    Envelope envelope,
                                    AMQP.BasicProperties properties,
                                    byte[] body)
             throws IOException
         {
             String routingKey = envelope.getRoutingKey();
             String contentType = properties.getContentType();
             long deliveryTag = envelope.getDeliveryTag();
             // (process the message components here ...)
             channel.basicAck(deliveryTag, false);
         }
     });

在这里,因为我们设置了自动确认(autoAck)的值为false,所以有必要在传递给消费者的方法中进行自动确认(handleDelivery方法中)。

更复杂的消费者将会重写更多的方法。事实上,handleShutdownSignal方法被调用当Channel(通道)和连接关闭的时候。并且在调用该消费者的任何回调方法之前将consumer tag传递给handleConsumeOk(com.rabbitmq.client.Consumer接口中定义的方法)方法

消费者还可以分别实现handleCancelOk(com.rabbitmq.client.Consumer接口中定义的方法)和handleCancel(com.rabbitmq.client.Consumer接口中定义的方法)方法来通知显式和隐式取消。

你也可以使用Channel.basicCancel方法明确的取消一个特定的消费,传递consumer tag,

channel.basicCancel(consumerTag);

和生产者一样,对于消费者来说并发处理消息也要慎重考虑。

回调给消费者是在与实例化其Channel(管道)的线程分开的线程池中调度的。 这意味着消费者可以安全地在ConnectionChannel上调用阻塞方法,例如Channel#queueDeclareChannel#basicCancel

每一个Channel(管道)都有自己的调度线程。对于最常用的使用方式就是一个消费者一个Channel(管道),意味着一个消费者不会阻塞其他的消费。如果是一个Channel(管道)多消费者必须明白一个长时间的消费调用可能会阻塞其他消费者的回调调度。

翻译未完待续......

demo

通过ConnectionFactory获得Connection,Connection得到Channel

public class ExchangeTest {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //创建exchange,类型是direct类型
        channel.exchangeDeclare("zhihao.miao","direct");

        //创建exchange,类型是direct类型
        channel.exchangeDeclare("zhihao.miao.info", BuiltinExchangeType.DIRECT);

        //第三个参数表示是否持久化,同步操作,有返回值
        AMQP.Exchange.DeclareOk ok = channel.exchangeDeclare("zhihao.miao.debug",BuiltinExchangeType.DIRECT,true);
        System.out.println(ok);

        //设置属性
        Map<String,Object> argument = new HashMap<>();
        argument.put("alternate-exchange","log");
        channel.exchangeDeclare("zhihao.miao.warn",BuiltinExchangeType.TOPIC,true,false,argument);

        //异步创建exchange,没有返回值
        channel.exchangeDeclareNoWait("zhihao.miao.log",BuiltinExchangeType.TOPIC,true,false,false,argument);

        //判断exchange是否存在,存在的返回ok,不存在的exchange则报错
        /*
        AMQP.Exchange.DeclareOk declareOk = channel.exchangeDeclarePassive("zhihao.miao.info");
        System.out.println(declareOk);

        declareOk = channel.exchangeDeclarePassive("zhihao.miao.info2");
        System.out.println(declareOk);
        */

        //删除exchange(可重复执行),删除一个不存在的也不会报错
        channel.exchangeDelete("zhihao.miao");
        channel.exchangeDelete("zhihao.miao.debug");
        channel.exchangeDelete("zhihao.miao.info");
        channel.exchangeDelete("zhihao.miao.warn");


        //删除exchange
        channel.exchangeDelete("zhihao.miao.log");

        channel.close();
        connection.close();

    }
}

队列的api操作。

public class QueueTest {
    public static void main(String[] args) throws Exception{

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //第二个参数表示是否持久化,第三个参数是判断这个队列是否在连接是否生效,为true表示连接关闭队列删除。
        AMQP.Queue.DeclareOk ok = channel.queueDeclare("zhihao.info",true,false,false,null);
        System.out.println(ok);

        //异步没有返回值的方法api
        channel.queueDeclareNoWait("zhihao.info.miao",true,false,false,null);

        //判断queue是否存在,不存在会抛出异常
        //channel.exchangeDeclarePassive("zhihao.info");
        //抛出错误
        //channel.exchangeDeclarePassive("zhihao.info.miao2");

        //exchange和queue进行绑定(可重复执行,不会重复创建)
        channel.queueBind("zhihao.info","zhihao.miao.order","info");

        //异步进行绑定
        channel.queueBindNoWait("zhihao.info.miao","zhihao.miao.pay","info",null);

        //exchange与exchange进行绑定(可重复执行,不会重复创建)
        channel.exchangeBind("zhihao.miao.email","zhihao.miao.weixin","debug");

        //exchange和queue进行解绑(可重复执行)
        channel.queueUnbind("zhihao.info","zhihao.miao.order","info");

        //exchange和exchange进行解绑(可重复执行)
        channel.exchangeUnbind("zhihao.info.miao","zhihao.miao.pay","debug");

        //删除队列
        channel.queueDelete("zhihao.info");


        channel.close();
        connection.close();

    }
}

消息的发送:

public class Sender {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();

        //第一个参数是exchange参数,如果是为空字符串,那么就会发送到(AMQP default)默认的exchange,而且routingKey
        //便是所要发送到的队列名
        channel.basicPublish("","zhihao.info.miao",properties,"忘记密码,验证码是1234".getBytes());
        channel.basicPublish("","zhihao.miao",properties,"忘记密码,六位验证密码是343sdf".getBytes());

        //direct类型的exchange类型的exchange,zhihao.miao.order绑定zhihao.info.miao队列,route key是order
        channel.basicPublish("zhihao.miao.order","order",properties,"爱奇艺会员到期了".getBytes());
        //zhihao.miao.pay绑定zhihao.info.miao队列,route key是order
        channel.basicPublish("zhihao.miao.pay","pay",properties,"优酷会员到期了".getBytes());


        //topic类型的exchange
        channel.basicPublish("log","user.log",properties,"你的外卖已经送达".getBytes());
        channel.basicPublish("log","user.log.info",properties,"你的外卖正在配送中".getBytes());
        channel.basicPublish("log","user",properties,"你的投诉已经采纳".getBytes());

        channel.close();
        connection.close();
    }
}

消息消费:

public class Consumer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.1.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("zhihao.miao");
        connectionFactory.setPassword("123456");
        connectionFactory.setVirtualHost("/");

        //客户端的消费消息
        Map<String,Object> clientProperties = new HashMap<>();
        clientProperties.put("desc","支付系统2.0");
        clientProperties.put("author","zhihao.miao");
        clientProperties.put("user","zhihao.miao@xxx.com");

        connectionFactory.setClientProperties(clientProperties);

        //给客户端的connetction命名
        Connection connection = connectionFactory.newConnection("log队列的消费者");

        //给channel起个编号
        Channel channel = connection.createChannel(10);

        //返回consumerTag,也可以通过重载方法进行设置consumerTag
        String consumerTag = channel.basicConsume("user_log_queue",true,new SimpleConsumer(channel));
        System.out.println(consumerTag);

        TimeUnit.SECONDS.sleep(30);

        channel.close();
        connection.close();
    }
}

具体的消息逻辑,继承DefaultConsumer类重写handleDelivery方法,如果是手工确认消息,会在handleDelivery方法中进行相关的确认(调用相关api),下面会在确认消息博客中去详细讲解这个。

public class SimpleConsumer extends DefaultConsumer{

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println(consumerTag);
        System.out.println("-----收到消息了---------------");
        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));
    }
}
 
connection
 
连接的客户端属性

-------------------------------------------------------------------------------------------------

Spring AMQP

先对本片博客进行总结,可直接跳过总结看下面的博客正文。

总结:
spring-amqp二个核心类RabbitAdmin和RabbitTemplate类
1.RabbitAdmin类完成对Exchange,Queue,Binging的操作,在容器中管理了RabbitAdmin类的时候,可以对Exchange,Queue,Binging进行自动声明。
2.RabbitTemplate类是发送和接收消息的工具类。(下一篇博客具体讲解)

简介

Spring AMQP项目将核心Spring概念应用于基于AMQP的消息传递解决方案的开发。 它提供了一个“模板”(template)作为发送和接收消息的高级抽象。 它还通过“侦听器容器(listener container)”为消息驱动的POJO提供支持。 这些库促进AMQP资源的管理,同时促进使用依赖注入和声明式配置。 在所有这些情况下,您将看到与Spring框架中的JMS支持的相似之处。

Spring AMQP包括两个部分;spring-amqp是对amqp的一些概念的一些抽象。spring-rabbit是对AMQP的实现RabbitMQ的实现。

特征

  1. 异步处理消费消息的一个监听容器(Listener container
  2. 使用RabbitTemplate类的实例来发送和接收消息。
  3. 使用RabbitAdmin去自动声明队列(queues),交换机(exchanges),绑定(bindings

spring-amqp模块是对AMQP协议的一个抽象和封装。所以说对所有的AMQP的实现都进行的抽象和封装,比如
org.springframework.amqp.core.Binding:绑定的封装,类型有QUEUEEXCHANGE
org.springframework.amqp.core.Exchange:其有基本的四种实现

 
Exchange的实现

org.springframework.amqp.core.Message:消息是由属性和body构成,将属性也封装成一个对象MessageProperties。
org.springframework.amqp.core.MessageProperties:对消息属性进行了抽象。
org.springframework.amqp.core.Queue:队列的封装。

还有对消息的转换进行了封装,相关的类在org.springframework.amqp.support.converter包下面。(下面的博客会专门讲解消息转换converter的一些实现)。

spring-rabbit模块是建立在springspring-amqpamqp-client(rabbitmq java client)之上的,是具体操作RabbitMQ的,底层对Rabbitmq的操作是使用amqp-client的。

二个核心类,一个是org.springframework.amqp.rabbit.core.RabbitAdminorg.springframework.amqp.rabbit.core.RabbitTemplate

spring-rabbit对日志进行了扩展,可以将日志发送到mq中。

Demo

加入spring-amqp依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>1.7.3.RELEASE</version>
        </dependency>
    </dependencies>

RabbitmqAdmin使用

容器中纳入ConnectionFactory和RabbitAdmin管理

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        return new RabbitAdmin(connectionFactory);
    }
}

应用类,使用RabbitAdmin进行Exchange,Queue,Binding操作

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.HashMap;
import java.util.Map;

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitAdmin rabbitAdmin = context.getBean(RabbitAdmin.class);
        System.out.println(rabbitAdmin);

        //创建四种类型的Exchange,可重复执行
        rabbitAdmin.declareExchange(new DirectExchange("zhihao.direct.exchange",true,false));
        rabbitAdmin.declareExchange(new TopicExchange("zhihao.topic.exchange",true,false));
        rabbitAdmin.declareExchange(new FanoutExchange("zhihao.fanout.exchange",true,false));
        rabbitAdmin.declareExchange(new HeadersExchange("zhihao.header.exchange",true,false));

        //删除Exchange
        //rabbitAdmin.deleteExchange("zhihao.header.exchange");

        //定义队列
        rabbitAdmin.declareQueue(new Queue("zhihao.debug",true));
        rabbitAdmin.declareQueue(new Queue("zhihao.info",true));
        rabbitAdmin.declareQueue(new Queue("zhihao.error",true));

        //删除队列
        //rabbitAdmin.deleteQueue("zhihao.debug");

        //将队列中的消息全消费掉
        rabbitAdmin.purgeQueue("zhihao.info",false);

        //绑定,指定要绑定的Exchange和Route key
        rabbitAdmin.declareBinding(new Binding("zhihao.debug",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.hehe",new HashMap()));

        rabbitAdmin.declareBinding(new Binding("zhihao.info",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.haha",new HashMap()));

        rabbitAdmin.declareBinding(new Binding("zhihao.error",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.welcome",new HashMap()));


        //绑定header exchange
        Map<String,Object> headerValues = new HashMap<>();
        headerValues.put("type",1);
        headerValues.put("size",10);

        //whereAll指定了x-match:   all参数
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("zhihao.debug")).
                to(new HeadersExchange("zhihao.header.exchange")).whereAll(headerValues).match());

        //whereAll指定了x-match:   any参数
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("zhihao.info")).
                to(new HeadersExchange("zhihao.header.exchange")).whereAny(headerValues).match());


        //进行解绑
        rabbitAdmin.removeBinding(BindingBuilder.bind(new Queue("zhihao.info")).
              to(new TopicExchange("zhihao.direct.exchange")).with("zhihao.info"));

        //声明topic类型的exchange
        rabbitAdmin.declareExchange(new TopicExchange("zhihao.hehe.exchange",true,false));
        rabbitAdmin.declareExchange(new TopicExchange("zhihao.miao.exchange",true,false));

        //exchange与exchange绑定
        rabbitAdmin.declareBinding(new Binding("zhihao.hehe.exchange",Binding.DestinationType.EXCHANGE,
                "zhihao.miao.exchange","zhihao",new HashMap()));

        //使用BindingBuilder进行绑定
        rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("zhihao.debug")).
                to(new TopicExchange("zhihao.topic.exchange")).with("zhihao.miao"));

        //rabbitAdmin.declareBinding(new Binding("amq.rabbitmq.trace",Binding.DestinationType.EXCHANGE,
                //"amq.rabbitmq.log","zhihao",new HashMap()));

        context.close();

    }

}

Exchange ,Queue,Binding的自动声明

  1. 直接把要自动声明的组件Bean纳入到spring容器中管理即可。
    自动声明发生的rabbitmq第一次连接创建的时候。如果系统从启动到停止没有创建任何连接,则不会自动创建。

  2. 自定声明支持单个和多个。

自动声明Exchange

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeclareConfig {

    //声明direct类型的Exchange
    @Bean
    public Exchange directExchange(){
        return new DirectExchange("zhihao.direct.exchange",true,false);
    }

    //声明topic类型的Exchange
    @Bean
    public Exchange topicExchange(){
        return new TopicExchange("zhihao.topic.exchange",true,false);
    }

    //声明fanout类型的Exchange
    @Bean
    public Exchange fanoutExchange(){
        return new FanoutExchange("zhihao.fanout.exchange",true,false);
    }

    //声明headers类型的Exchange
    @Bean
    public Exchange headersExchange(){
        return new HeadersExchange("zhihao.header.exchange",true,false);
    }
}

配置类,在spring容器中纳入ConnectionFactory实例和RabbitAdmin实例

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        return new RabbitAdmin(connectionFactory);
    }
}

启动应用类,自动声明发生的rabbitmq第一次连接创建的时候。如果系统从启动到停止没有创建任何连接,则不会自动创建。

import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        //使得客户端第一次连接rabbitmq
        context.getBean(RabbitAdmin.class).getQueueProperties("**");
        context.close();
    }
}
 
控制台显示已经声明创建

队列的自动声明

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DeclareConfig {

    @Bean
    public Queue debugQueue(){
        return new Queue("zhihao.debug",true);
    }

    @Bean
    public Queue infoQueue(){
        return new Queue("zhihao.info",true);
    }

    @Bean
    public Queue errorQueue(){
        return new Queue("zhihao.error",true);
    }
}

上面的Application和DeclareConfig不列举出来了,执行Application应用启动类,查看web管控台的队列生成。

 
Queue自动创建声明

绑定的自动生成
DeclareConfig类中,

import org.springframework.amqp.core.Binding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

@Configuration
public class DeclareConfig {

    @Bean
    public Binding binding(){
        return new Binding("zhihao.debug",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.debug",new HashMap());
    }

    @Bean
    public Binding binding2(){
        return new Binding("zhihao.info",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.info",new HashMap());
    }

    @Bean
    public Binding binding3(){
        return new Binding("zhihao.error",Binding.DestinationType.QUEUE,
                "zhihao.direct.exchange","zhihao.error",new HashMap());
    }
}

上面的Application和DeclareConfig不列举出来了,执行Application应用启动类,查看web管控台的Binding生成。

 
Binding自动创建声明

一次性生成多个queue,exchange,binding

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

上面的Application和DeclareConfig不列举出来了,执行Application应用启动类,查看web管控台Exchange,Queue,Binding都已经生成。

注意
当声明队列是以amp开头的时候,队列是不能创建声明的。

@Bean
public Queue amqQueue(){
   return new Queue("amp.log",true);
}

总结

自动声明的一些条件

  • 要有连接(对rabbitmq的连接)
  • 容器中要有org.springframework.amqp.rabbit.core.RabbitAdmin的实例
  • RabbitAdminautoStartup属性必须为true。
  • 如果ConnectionFactory使用的是CachingConnectionFactory,则cacheMode必须是CachingConnectionFactory.CacheMode.CHANNEL(默认)。
  • 所要声明的组件(QueueExchangeBinding)的shouldDeclare必须是true(默认就是true
  • Queue队列的名字不能以amq.开头。

注意:QueueExchangeBinding都直接或者间接的继承Declarable,而Declarable中定义了shouldDeclare的方法。

自动声明源码分析

org.springframework.amqp.rabbit.core.RabbitAdmin实现InitializingBean接口,在BeanFactory设置完所有属性之后执行特定初始化(afterPropertiesSet方法)

RabbitAdminafterPropertiesSet方法,

@Override
    public void afterPropertiesSet() {

        synchronized (this.lifecycleMonitor) {

          //autoStartup属性的值为false的时候,直接return
            if (this.running || !this.autoStartup) {
                return;
            }

          //connectionFactory实例如果是CachingConnectionFactory,并且CacheMode是CacheMode.CONNECTION也会return下面不执行了。
            if (this.connectionFactory instanceof CachingConnectionFactory &&
                    ((CachingConnectionFactory) this.connectionFactory).getCacheMode() == CacheMode.CONNECTION) {
                this.logger.warn("RabbitAdmin auto declaration is not supported with CacheMode.CONNECTION");
                return;
            }

          //连接的监听器
            this.connectionFactory.addConnectionListener(new ConnectionListener() {

                // Prevent stack overflow...
                private final AtomicBoolean initializing = new AtomicBoolean(false);

                @Override
                public void onCreate(Connection connection) {
                    if (!initializing.compareAndSet(false, true)) {
                        // If we are already initializing, we don't need to do it again...
                        return;
                    }
                    try {
                        //执行这个方法
                        initialize();
                    }
                    finally {
                        initializing.compareAndSet(true, false);
                    }
                }

                @Override
                public void onClose(Connection connection) {
                }

            });

            this.running = true;

        }
    }

RabbitAdmininitialize方法,声明所有exchanges, queuesbindings

/**
     * Declares all the exchanges, queues and bindings in the enclosing application context, if any. It should be safe
     * (but unnecessary) to call this method more than once.
     */
    public void initialize() {

        if (this.applicationContext == null) {
            this.logger.debug("no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
            return;
        }

        this.logger.debug("Initializing declarations");
        //得到容器中所有的Exchange
        Collection<Exchange> contextExchanges = new LinkedList<Exchange>(
                this.applicationContext.getBeansOfType(Exchange.class).values());
        //得到容器中所有的Queue
        Collection<Queue> contextQueues = new LinkedList<Queue>(
                this.applicationContext.getBeansOfType(Queue.class).values());
       //得到容器中所有的Binding
        Collection<Binding> contextBindings = new LinkedList<Binding>(
                this.applicationContext.getBeansOfType(Binding.class).values());

       //获取容器中所有的Collection,如果容器中所有元素是Exchange,Queue或者Binding的时候将这些实例也加入到spring容器中。
        @SuppressWarnings("rawtypes")
        Collection<Collection> collections = this.applicationContext.getBeansOfType(Collection.class, false, false)
                .values();
        for (Collection<?> collection : collections) {
            if (collection.size() > 0 && collection.iterator().next() instanceof Declarable) {
                for (Object declarable : collection) {
                    if (declarable instanceof Exchange) {
                        contextExchanges.add((Exchange) declarable);
                    }
                    else if (declarable instanceof Queue) {
                        contextQueues.add((Queue) declarable);
                    }
                    else if (declarable instanceof Binding) {
                        contextBindings.add((Binding) declarable);
                    }
                }
            }
        }

       //进行了filter过滤,
        final Collection<Exchange> exchanges = filterDeclarables(contextExchanges);
        final Collection<Queue> queues = filterDeclarables(contextQueues);
        final Collection<Binding> bindings = filterDeclarables(contextBindings);

        for (Exchange exchange : exchanges) {
            if ((!exchange.isDurable() || exchange.isAutoDelete())  && this.logger.isInfoEnabled()) {
                this.logger.info("Auto-declaring a non-durable or auto-delete Exchange ("
                        + exchange.getName()
                        + ") durable:" + exchange.isDurable() + ", auto-delete:" + exchange.isAutoDelete() + ". "
                        + "It will be deleted by the broker if it shuts down, and can be redeclared by closing and "
                        + "reopening the connection.");
            }
        }

        for (Queue queue : queues) {
            if ((!queue.isDurable() || queue.isAutoDelete() || queue.isExclusive()) && this.logger.isInfoEnabled()) {
                this.logger.info("Auto-declaring a non-durable, auto-delete, or exclusive Queue ("
                        + queue.getName()
                        + ") durable:" + queue.isDurable() + ", auto-delete:" + queue.isAutoDelete() + ", exclusive:"
                        + queue.isExclusive() + ". "
                        + "It will be redeclared if the broker stops and is restarted while the connection factory is "
                        + "alive, but all messages will be lost.");
            }
        }

        this.rabbitTemplate.execute(new ChannelCallback<Object>() {
            @Override
            public Object doInRabbit(Channel channel) throws Exception {
               //声明exchange,如果exchange是默认的exchange那么也不会声明。
                declareExchanges(channel, exchanges.toArray(new Exchange[exchanges.size()]));
                //声明队列,如果队列名以amq.开头的也不会进行声明
                declareQueues(channel, queues.toArray(new Queue[queues.size()]));
                declareBindings(channel, bindings.toArray(new Binding[bindings.size()]));
                return null;
            }
        });
        this.logger.debug("Declarations finished");

    }

filterDeclarables方法过滤一些ExchangeQueueBinding,因为这三个类都是继承Declarable这个类


    private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables) {
        Collection<T> filtered = new ArrayList<T>();
        for (T declarable : declarables) {
            Collection<?> adminsWithWhichToDeclare = declarable.getDeclaringAdmins();
            //shouldDeclare属性必须是true,否则就会被过滤掉了
            if (declarable.shouldDeclare() &&
                (adminsWithWhichToDeclare.isEmpty() || adminsWithWhichToDeclare.contains(this))) {
                filtered.add(declarable);
            }
        }
        return filtered;
    }

声明Exchanges

    private void declareExchanges(final Channel channel, final Exchange... exchanges) throws IOException {
        for (final Exchange exchange : exchanges) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("declaring Exchange '" + exchange.getName() + "'");
            }

            //不是默认的Exchange
            if (!isDeclaringDefaultExchange(exchange)) {
                try {
                   //是否是delayed类型的Exchange
                    if (exchange.isDelayed()) {
                        Map<String, Object> arguments = exchange.getArguments();
                        if (arguments == null) {
                            arguments = new HashMap<String, Object>();
                        }
                        else {
                            arguments = new HashMap<String, Object>(arguments);
                        }
                        arguments.put("x-delayed-type", exchange.getType());
                       //调用exchangeDeclare进行声明
                        channel.exchangeDeclare(exchange.getName(), DELAYED_MESSAGE_EXCHANGE, exchange.isDurable(),
                                exchange.isAutoDelete(), exchange.isInternal(), arguments);
                    }
                    else {
                       //调用exchangeDeclare进行声明
                      channel.exchangeDeclare(exchange.getName(), exchange.getType(), exchange.isDurable(),
                                exchange.isAutoDelete(), exchange.isInternal(), exchange.getArguments());
                    }
                }
                catch (IOException e) {
                    logOrRethrowDeclarationException(exchange, "exchange", e);
                }
            }
        }
    }

声明Queue队列

private DeclareOk[] declareQueues(final Channel channel, final Queue... queues) throws IOException {
        List<DeclareOk> declareOks = new ArrayList<DeclareOk>(queues.length);
        for (int i = 0; i < queues.length; i++) {
            Queue queue = queues[i];
            //队列不以amq.开头的队列才能进行声明
            if (!queue.getName().startsWith("amq.")) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("declaring Queue '" + queue.getName() + "'");
                }
                try {
                    try {
                       //进行队列声明
                        DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
                                queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());
                        declareOks.add(declareOk);
                    }
                    catch (IllegalArgumentException e) {
                        if (this.logger.isDebugEnabled()) {
                            this.logger.error("Exception while declaring queue: '" + queue.getName() + "'");
                        }
                        try {
                            if (channel instanceof ChannelProxy) {
                                ((ChannelProxy) channel).getTargetChannel().close();
                            }
                        }
                        catch (TimeoutException e1) {
                        }
                        throw new IOException(e);
                    }
                }
                catch (IOException e) {
                    logOrRethrowDeclarationException(queue, "queue", e);
                }
            }
            this.logger.debug("Queue with name that starts with 'amq.' cannot be declared.");
        }
        return declareOks.toArray(new DeclareOk[declareOks.size()]);
}

binding声明:

    private void declareBindings(final Channel channel, final Binding... bindings) throws IOException {
        for (Binding binding : bindings) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Binding destination [" + binding.getDestination() + " (" + binding.getDestinationType()
                        + ")] to exchange [" + binding.getExchange() + "] with routing key [" + binding.getRoutingKey()
                        + "]");
            }

            try {
               //QUEUE类型的绑定
                if (binding.isDestinationQueue()) {
                   //并且不是绑定到默认的Default Exchange
                    if (!isDeclaringImplicitQueueBinding(binding)) {
                         //绑定队列
                        channel.queueBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
                                binding.getArguments());
                    }
                }
                else {
                    //Exchange类型的绑定
                    channel.exchangeBind(binding.getDestination(), binding.getExchange(), binding.getRoutingKey(),
                            binding.getArguments());
                }
            }
            catch (IOException e) {
                logOrRethrowDeclarationException(binding, "binding", e);
            }
        }
    }

-------------------------------------------------------------------------------------------------

使用RabbitTemplate进行消息发送

加入spring-amqp依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>1.7.3.RELEASE</version>
        </dependency>
    </dependencies>

配置类:
ConnectionFactoryRabbitTemplate纳入到spring容器中

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
       //设置Exchange默认操作的exchange和routingkey
        rabbitTemplate.setExchange("zhihao.direct.exchange");
        rabbitTemplate.setRoutingKey("zhihao.debug");
        return rabbitTemplate;
    }
}

测试类:
RabbitTemplate#send方法发送消息,

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.getHeaders().put("desc","消息发送");
        messageProperties.getHeaders().put("type",10);

        Message message = new Message("hello".getBytes(),messageProperties);

        /**
         * 调用rabbitTemplate的send方法发送消息,如果没有指定exchange,Routing,则使用声明Exchange指定的
         * exchange,Routing
         * 如果RabbitTemplate没有设置,则默认的exchange 是DEFAULT_EXCHANGE为"",
         * 默认的routkey是DEFAULT_ROUTING_KEY为""
         */
        //1.
        //rabbitTemplate.send(message);

        //2.指定Routingkey,而exchange是Rabbitmq默认指定的
        //rabbitTemplate.send("zhihao.error",message);

        //3.即指定exchange,又指定了routing_key
        //rabbitTemplate.send("zhihao.login","ulogin",message);

        /**
         * 4.使用默认的defaultExchange进行投递消息,route key就是队列名,指定correlation_id属性,correlation_id属性是rabbitmq 进行异步rpc进行标识每次请求的唯一
         * id,下面会讲到
         */
        rabbitTemplate.send("","zhihao.order.queue",message,new CorrelationData("spring.amqp"));

        context.close();
    }
}

查看web管控台发现消息都发送成功了。

使用RabbitTemplate#convertAndSend方法发送消息,

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application2 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        /**
         * 使用convertAndSend方法,接收的参数是Object对象,其实是将接收的对象转换成Message对象,不指定exchange和routing key,那么就
         * 使用RabbitTemplate中设置的exchange和routing key
         */
        //rabbitTemplate.convertAndSend("this is my message");

        //指定exchange或者指定exchage和routing key
        //rabbitTemplate.convertAndSend("zhihao.error","this is my message order111");
        //rabbitTemplate.convertAndSend("","zhihao.user.queue","this is my message order222");

        //发送消息的后置处理器,MessagePostProcessor类的postProcessMessage方法得到的Message就是将参数Object内容转换成Message对象
        /*
        rabbitTemplate.convertAndSend("", "zhihao.user.queue", "this is my message processor", new MessagePostProcessor() {
            //在后置处理器上加上order和count属性
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                System.out.println("-------处理前message-------------");
                System.out.println(message);
                message.getMessageProperties().getHeaders().put("order",10);
                message.getMessageProperties().getHeaders().put("count",1);
                return message;
            }
        });
        */


        rabbitTemplate.convertAndSend("", "zhihao.user.queue", "message before", message1 -> {
            //使用lamdba的语法
            MessageProperties properties = new MessageProperties();
            properties.getHeaders().put("desc","消息发送");
            properties.getHeaders().put("type",10);

            Message messageafter = new Message("message after".getBytes(),properties);
            return messageafter;
        });

        context.close();
    }
}

消息的消费

使用容器的方式进行消费
认识一个接口org.springframework.amqp.rabbit.listener.MessageListenerContainer
其默认实现类org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer

 
MessageListenerContainer接口及其实现

MessageListenerContainer#setMessageListener方法,接收的参数类型
org.springframework.amqp.core.MessageListener或者org.springframework.amqp.rabbit.core.ChannelAwareMessageListener接口

代码:
ConnectionFactoryRabbitTemplateSimpleMessageListenerContainer实例纳入到spring容器中进行管理。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    /*
    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //监听队列zhihao.user.queue,监听队列可以多个,参数类型是String[]
        container.setQueueNames("zhihao.user.queue");
        container.setMessageListener(new MessageListener() {
            //具体的消费逻辑
            @Override
            public void onMessage(Message message) {
                System.out.println("====接收到消息=====");
                System.out.println(message.getMessageProperties());
                System.out.println(new String(message.getBody()));
            }
        });
        return container;
    }
    */

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //队列可以是多个,参数是String的数组
        container.setQueueNames("zhihao.user.queue");
        container.setMessageListener(new ChannelAwareMessageListener(){
            @Override
            //得到了Channel参数,具体使用会在下面的博客详细讲解
            public void onMessage(Message message, Channel channel) throws Exception {
                System.out.println("====接收到消息=====");
                System.out.println(message.getMessageProperties());
                System.out.println(new String(message.getBody()));
            }
        });
        return container;
    }
}

关于setMessageListener接收的类型参数,

 

 
setMessageListener方法

当接收的参数不是MessageListener或者ChannelAwareMessageListener类型,则会抛出异常,具体的逻辑在checkMessageListener(messageListener);方法,

 

 
checkMessageListener方法

应用启动类,向zhihao.user.queue对象发送消息,并启动了spring容器,发现监听到队列并且消费了。

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        rabbitTemplate.convertAndSend("","zhihao.user.queue","hello spring amqp");

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

原理分析

稍微分析一下原理
org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer接口,它继承AbstractMessageListenerContainer类,实现SmartLifecycle接口然后继承Lifecycle接口,意味着一旦SimpleMessageListenerContainer实例被spring容器管理,其生命周期就托管与spring容器来管理了,意味着当spring容器运行起来的时候,SimpleMessageListenerContainer容器启动,spring容器关闭的时候,SimpleMessageListenerContainer容器也关闭了。

设置在spring容器初始化的时候设置SimpleMessageListenerContainer不启动,(container.setAutoStartup(false);

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //队列可以是多个,参数是String的数组
        container.setQueueNames("zhihao.miao.order");
        //设置autoStartUp为false表示SimpleMessageListenerContainer没有启动
        container.setAutoStartup(false);
        container.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                System.out.println("====接收到消息=====");
                System.out.println(message.getMessageProperties());
                System.out.println(new String(message.getBody()));
            }
        });
        return container;
    }
}

此时不能消费消息,也可以在应用启动类启动SimpleMessageListenerContainer容器,在应用启动类中启动

import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        //在spring容器中启动SimpleMessageListenerContainer
        context.getBean(SimpleMessageListenerContainer.class).start();
        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

以上说明只有org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer启动了,才会消费消息。

总结
SimpleMessageListenerContainer可以托管到spring容器中,由spring容器进行SimpleMessageListenerContainer的生命周期管理,默认情况下spring容器启动的时候,启动SimpleMessageListenerContainer,spring容器关闭,会stop掉SimpleMessageListenerContainer,也可以设置SimpleMessageListenerContainer手动启动(context.getBean(SimpleMessageListenerContainer.class).start();)。

-------------------------------------------------------------------------------------------------

SimpleMessageListenerContainer详解

同一个queue上有多个消费者的时候,只会有一个消费者收到消息,一般是多个消费者轮流收到消息。

 
消费者1
 
消费者2

SimpleMessageListenerContainer可以监听多个队列,
container.setQueueNames的api接收的是一个字符串数组对象。

@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.debug","zhihao.error","zhihao.info");
        container.setMessageListener((MessageListener) message -> {
            System.out.println("====接收到"+message.getMessageProperties().getConsumerQueue()+"队列的消息=====");
            System.out.println(message.getMessageProperties());
            System.out.println(new String(message.getBody()));
        });
        return container;
    }

SimpleMessageListenerContainer运行时动态的添加监听队列

@ComponentScan
public class Application {
   public static void main(String[] args) throws Exception{
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
       SimpleMessageListenerContainer container = context.getBean(SimpleMessageListenerContainer.class);
       TimeUnit.SECONDS.sleep(20);
       container.addQueueNames("zhihao.error");
       TimeUnit.SECONDS.sleep(20);
       container.addQueueNames("zhihao.debug");
       TimeUnit.SECONDS.sleep(20);

       context.close();
   }
}

SimpleMessageListenerContainer纳入容器

 @Bean
   public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
       SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
       container.setConnectionFactory(connectionFactory);
       container.setQueueNames("zhihao.debug");
       container.setMessageListener((MessageListener) message -> {
           if("zhihao.debug".equals(message.getMessageProperties().getConsumerQueue())){
               System.out.println("====接收到"+message.getMessageProperties().getConsumerQueue()+"队列的消息=====");
               System.out.println(message.getMessageProperties());
               System.out.println(new String(message.getBody()));
           }else if("zhihao.error".equals(message.getMessageProperties().getConsumerQueue())){
               System.out.println("====接收到"+message.getMessageProperties().getConsumerQueue()+"队列的消息=====");
               System.out.println(message.getMessageProperties());
               System.out.println(new String(message.getBody()));
           }else if("zhihao.info".equals(message.getMessageProperties().getConsumerQueue())){
               System.out.println("====接收到"+message.getMessageProperties().getConsumerQueue()+"队列的消息=====");
               System.out.println(message.getMessageProperties());
               System.out.println(new String(message.getBody()));
           }
       });

       return container;
   }

运行时动态的移除监听队列

SimpleMessageListenerContainer运行时后动态的移除监听队列

container.removeQueueNames("zhihao.debug");

后置处理器

SimpleMessageListenerContainer增加后置处理

      @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        //后置处理器,接收到的消息都添加了Header请求头
        container.setAfterReceivePostProcessors(message -> {
            message.getMessageProperties().getHeaders().put("desc",10);
            return message;
        });
        container.setMessageListener((MessageListener) message -> {
            System.out.println("====接收到消息=====");
            System.out.println(message.getMessageProperties());
            System.out.println(new String(message.getBody()));
        });
        return container;
    }

应用启动类:

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        SimpleMessageListenerContainer container = context.getBean(SimpleMessageListenerContainer.class);
        System.out.println(container.getQueueNames()[0]);
        TimeUnit.SECONDS.sleep(30);
        context.close();
    }
}

控制台打印:

====接收到消息=====
MessageProperties [headers={desc=10}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=null, contentEncoding=null, contentLength=0, deliveryMode=null, receivedDeliveryMode=NON_PERSISTENT, expiration=null, priority=null, redelivered=false, receivedExchange=, receivedRoutingKey=zhihao.miao.order, receivedDelay=null, deliveryTag=1, messageCount=0, consumerTag=amq.ctag-2xCE8upxgGgf-u1haCwt6A, consumerQueue=zhihao.miao.order]
消息2

setAfterReceivePostProcessors方法可以对消息进行后置处理。

设置消费者的Consumer_tag和Arguments

int count=0;

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        //设置消费者的consumerTag_tag
        container.setConsumerTagStrategy(queue -> "order_queue_"+(++count));
        //设置消费者的Arguments
        Map<String, Object> args = new HashMap<>();
        args.put("module","订单模块");
        args.put("fun","发送消息");
        container.setConsumerArguments(args);
        container.setMessageListener((MessageListener) message -> {
            System.out.println("====接收到消息=====");
            System.out.println(message.getMessageProperties());
            System.out.println(new String(message.getBody()));
        });
        return container;
    }
 
web控制面板

container.setConsumerTagStrategy可以设置消费者的 Consumer_tagcontainer.setConsumerArguments可以设置消费者的 Arguments

setConcurrentConsumers设置并发消费者

  @Bean
   public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
       SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
       container.setConnectionFactory(connectionFactory);
       container.setQueueNames("zhihao.miao.order");
       container.setConcurrentConsumers(5);
       container.setMaxConcurrentConsumers(10);
       container.setMessageListener((MessageListener) message -> {
           System.out.println("====接收到消息=====");
           System.out.println(message.getMessageProperties());
           System.out.println(new String(message.getBody()));
       });
       return container;
   }
 
并发消费数

应用启动类,

@ComponentScan
public class Application {

   public static void main(String[] args) throws Exception{
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
       SimpleMessageListenerContainer container = context.getBean(SimpleMessageListenerContainer.class);
       container.setConcurrentConsumers(7);
       TimeUnit.SECONDS.sleep(30);
       context.close();
   }
}
 
运行期间动态的修改并发消费者的数量

setConcurrentConsumers设置多个并发消费者一起消费,并支持运行时动态修改。 setMaxConcurrentConsumers设置最多的并发消费者。

-------------------------------------------------------------------------------------------------

MessageListenerAdapter详解

Message listener adapter that delegates the handling of messages to target listener methods via reflection, with flexible message type conversion. Allows listener methods to operate on message content types, completely independent from the Rabbit API.
消息监听适配器(adapter),通过反射将消息处理委托给目标监听器的处理方法,并进行灵活的消息类型转换。允许监听器方法对消息内容类型进行操作,完全独立于Rabbit API。

By default, the content of incoming Rabbit messages gets extracted before being passed into the target listener method, to let the target method operate on message content types such as String or byte array instead of the raw
Message. Message type conversion is delegated to a Spring AMQ MessageConverter. By default, a SimpleMessageConverter will be used. (If you do not want such automatic message conversion taking place, then be sure to set the #setMessageConverter MessageConverter to null.)
默认情况下,传入Rabbit消息的内容在被传递到目标监听器方法之前被提取,以使目标方法对消息内容类型进行操作以String或者byte类型进行操作,而不是原始Message类型。 (消息转换器)
消息类型转换委托给MessageConverter接口的实现类。 默认情况下,将使用SimpleMessageConverter。 (如果您不希望进行这样的自动消息转换,
那么请自己通过#setMessageConverter MessageConverter设置为null)

If a target listener method returns a non-null object (typically of a message content type such as String or byte array), it will get wrapped in a Rabbit Message and sent to the exchange of the incoming message with the routingKey that comes from the Rabbit ReplyTo property or via #setResponseRoutingKey(String) specified routingKey).
如果目标监听器方法返回一个非空对象(通常是消息内容类型,例如String或byte数组),它将被包装在一个Rabbit Message 中,并发送使用来自Rabbit ReplyTo属性或通过#setResponseRoutingKey(String)指定的routingKeyroutingKey来传送消息。(使用rabbitmq 来实现异步rpc功能时候会使用到这个属性)。

Note:The sending of response messages is only available when using the ChannelAwareMessageListener entry point (typically through a Spring message listener container). Usage as MessageListener does not support the generation of response messages.
注意:发送响应消息仅在使用ChannelAwareMessageListener入口点(通常通过Spring消息监听器容器)时可用。 用作MessageListener不支持生成响应消息。

Demo

配置类MQConfig:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setExchange("zhihao.direct.exchange");
        rabbitTemplate.setRoutingKey("zhihao.debug");
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("order","pay","zhihao.miao.order");

        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
        //设置处理器的消费消息的默认方法,如果没有设置,那么默认的处理器中的默认方式是handleMessage方法
        adapter.setDefaultListenerMethod("onMessage");
        Map<String, String> queueOrTagToMethodName = new HashMap<>();
        queueOrTagToMethodName.put("order","onorder");
        queueOrTagToMethodName.put("pay","onpay");
        queueOrTagToMethodName.put("zhihao.miao.order","oninfo");
        adapter.setQueueOrTagToMethodName(queueOrTagToMethodName);
        container.setMessageListener(adapter);

        return container;
    }
}

Handler类MessageHandlerMessageHandler类中定义的方法也就是上面翻译的目标监听器的处理方法:

public class MessageHandler {

    //没有设置默认的处理方法的时候,方法名是handleMessage
    public void handleMessage(byte[] message){
        System.out.println("---------handleMessage-------------");
        System.out.println(new String(message));
    }


    //通过设置setDefaultListenerMethod时候指定的方法名
    public void onMessage(byte[] message){
        System.out.println("---------onMessage-------------");
        System.out.println(new String(message));
    }

    //以下指定不同的队列不同的处理方法名
    public void onorder(byte[] message){
        System.out.println("---------onorder-------------");
        System.out.println(new String(message));
    }

    public void onpay(byte[] message){
        System.out.println("---------onpay-------------");
        System.out.println(new String(message));
    }

    public void oninfo(byte[] message){
        System.out.println("---------oninfo-------------");
        System.out.println(new String(message));
    }

}

启动应用类:

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("===start up======");
        TimeUnit.SECONDS.sleep(60);
        context.close();
    }
}

总结

使用MessageListenerAdapter处理器进行消息队列监听处理,如果容器没有设置setDefaultListenerMethod,则处理器中默认的处理方法名是handleMessage,如果设置了setDefaultListenerMethod,则处理器中处理消息的方法名就是setDefaultListenerMethod方法参数设置的值。也可以通过setQueueOrTagToMethodName方法为不同的队列设置不同的消息处理方法。

源码分析:

我们知道MessageListenerAdapter继承AbstractAdaptableMessageListener类,实现MessageListenerChannelAwareMessageListener接口,而我们知道MessageListenerChannelAwareMessageListener接口的onMessage方法就是具体容器监听队列处理队列消息的方法。

MessageListenerAdapteronMessage方法

@Override
public void onMessage(Message message, Channel channel) throws Exception {
    // Check whether the delegate is a MessageListener impl itself.
    // In that case, the adapter will simply act as a pass-through.
    Object delegate = getDelegate();
    if (delegate != this) {
        if (delegate instanceof ChannelAwareMessageListener) {
            if (channel != null) {
                ((ChannelAwareMessageListener) delegate).onMessage(message, channel);
                return;
            }
            else if (!(delegate instanceof MessageListener)) {
                throw new AmqpIllegalStateException("MessageListenerAdapter cannot handle a "
                        + "ChannelAwareMessageListener delegate if it hasn't been invoked with a Channel itself");
            }
        }
        if (delegate instanceof MessageListener) {
            ((MessageListener) delegate).onMessage(message);
            return;
        }
    }

    // Regular case: find a handler method reflectively.
    Object convertedMessage = extractMessage(message);
    //获取处理消息的方法名
    String methodName = getListenerMethodName(message, convertedMessage);
    if (methodName == null) {
        throw new AmqpIllegalStateException("No default listener method specified: "
                + "Either specify a non-null value for the 'defaultListenerMethod' property or "
                + "override the 'getListenerMethodName' method.");
    }

    // Invoke the handler method with appropriate arguments.
    Object[] listenerArguments = buildListenerArguments(convertedMessage);
    Object result = invokeListenerMethod(methodName, listenerArguments, message);
    if (result != null) {
        handleResult(result, message, channel);
    }
    else {
        logger.trace("No result object given - no result to handle");
    }
}

获取处理消息的方法名

protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws Exception {
    if (this.queueOrTagToMethodName.size() > 0) {
        MessageProperties props = originalMessage.getMessageProperties();
        String methodName = this.queueOrTagToMethodName.get(props.getConsumerQueue());
        if (methodName == null) {
            methodName = this.queueOrTagToMethodName.get(props.getConsumerTag());
        }
        if (methodName != null) {
            return methodName;
        }
    }
    return getDefaultListenerMethod();
}

结论

MessageListenerAdapter
1.可以把一个没有实现MessageListenerChannelAwareMessageListener接口的类适配成一个可以处理消息的处理器
2.默认的方法名称为:handleMessage,可以通过setDefaultListenerMethod设置新的消息处理方法
3.MessageListenerAdapter支持不同的队列交给不同的方法去执行。使用setQueueOrTagToMethodName方法设置,当根据queue名称没有找到匹配的方法的时候,就会交给默认的方法去处理。

-------------------------------------------------------------------------------------------------

MessageConverter详解

org.springframework.amqp.support.converter.MessageConverter

 
MessageConverter

Message toMessage(Object object, MessageProperties messageProperties);
将java对象和属性对象转换成Message对象。

Object fromMessage(Message message) throws MessageConversionException;
将消息对象转换成java对象。

Demo

定义Config类

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");

        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
        //指定消息转换器
        adapter.setMessageConverter(new TestMessageConverter());
        //设置处理器的消费消息的默认方法
        adapter.setDefaultListenerMethod("onMessage");
        container.setMessageListener(adapter);

        return container;
    }
}

MessageListenerAdapter中定义的消息转换器,消费端接收的消息就从Message类型转换成了String类型

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;

public class TestMessageConverter implements MessageConverter {


    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        System.out.println("=======toMessage=========");
        return new Message(object.toString().getBytes(),messageProperties);
    }

    //消息类型转换器中fromMessage方法返回的类型就是消费端处理器接收的类型
    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("=======fromMessage=========");
        return new String(message.getBody());
    }
}

消费者处理消息的Handler

public class MessageHandler {


    public void onMessage(String message){
        System.out.println("---------onMessage-------------");
        System.out.println(message);
    }
}

启动类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

/**
 * MessageConverter可以把java对象转换成Message对象,也可以把Message对象转换成java对象
 *
 * MessageListenerAdapter内部通过MessageConverter把Message转换成java对象,然后找到相应的处理方法,参数为转换成的java对象
 */
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("===start up======");
        TimeUnit.SECONDS.sleep(30);
        context.close();
    }
}

启动应用类,发送消息到zhihao.miao.order队列,控制台打印:

===start up======
=======fromMessage=========
---------onMessage-------------
String类型的消息

从控制台打印我们知道了在消费者处理消息之前会进行消息类型转换,调用TestMessageConverterfromMessage方法,然后执行消息处理器的onMessage方法,方法参数就是String类型。

扩展

自定义一个MyBody类型,将消息从Message转换成MyBody类型

public class MyBody {

    private byte[] bodys;

    public MyBody(byte[] bodys){
        this.bodys = bodys;
    }

    @Override
    public String toString() {
        return new String(bodys);
    }
}

然后修改TestMessageConverterfromMessage方法,返回了MyBody类型,那么消息处理器的消费方法也是MyBody参数的消费方法

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;

public class TestMessageConverter implements MessageConverter {


    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        System.out.println("=======toMessage=========");
        return new Message(object.toString().getBytes(),messageProperties);
    }

    //消息类型转换器中fromMessage方法返回的类型就是消费端处理器接收的类型
    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("=======fromMessage=========");
        return new MyBody(message.getBody());
    }
}

此时的消息处理器,处理器中的方法的入参就是MyBody类型了,

public class MessageHandler {

    public void onMessage(MyBody message){
        System.out.println("---------onMessage---MyBody-------------");
        System.out.println(message);
    }
}

此时控制台打印:

===start up======
=======fromMessage=========
---------onMessage---MyBody-------------
Mybody类型的消息

小结

我们还测试如下如果不使用自定义的Converter,那么当消息的属性中含有属性content_type的值为text,那么默认的转换成的java类型就是String类型,如果不指定那么默认的转换类型就是byte[]

源码分析

我们跟进去MessageListenerAdapter的setMessageConverter方法,

/**
 * Set the converter that will convert incoming Rabbit messages to listener method arguments, and objects returned
 * from listener methods back to Rabbit messages.
 * <p>
 * The default converter is a {@link SimpleMessageConverter}, which is able to handle "text" content-types.
 * @param messageConverter The message converter.
 */
public void setMessageConverter(MessageConverter messageConverter) {
    this.messageConverter = messageConverter;
}
private MessageConverter messageConverter = new SimpleMessageConverter();

我们发现默认的MessageConverterSimpleMessageConverter,我们进入SimpleMessageConverter类中看其默认的转换逻辑

@Override
public Object fromMessage(Message message) throws MessageConversionException {
    Object content = null;
    MessageProperties properties = message.getMessageProperties();
    if (properties != null) {
        String contentType = properties.getContentType();
        //contentType属性值是以text开头,那么就将Message类型转换成String类型
        if (contentType != null && contentType.startsWith("text")) {
            String encoding = properties.getContentEncoding();
            if (encoding == null) {
                encoding = this.defaultCharset;
            }
            try {
                content = new String(message.getBody(), encoding);
            }
            catch (UnsupportedEncodingException e) {
                throw new MessageConversionException(
                        "failed to convert text-based Message content", e);
            }
        }
        //如果content_type的值是application/x-java-serialized-object则把消息序列化为java对象
        else if (contentType != null &&
                contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)) {
            try {
                content = SerializationUtils.deserialize(
                        createObjectInputStream(new ByteArrayInputStream(message.getBody()), this.codebaseUrl));
            }
            catch (IOException e) {
                throw new MessageConversionException(
                        "failed to convert serialized Message content", e);
            }
            catch (IllegalArgumentException e) {
                throw new MessageConversionException(
                        "failed to convert serialized Message content", e);
            }
            catch (IllegalStateException e) {
                throw new MessageConversionException(
                        "failed to convert serialized Message content", e);
            }
        }
    }
    if (content == null) {
       //都没有符合,则转换成字节数组
        content = message.getBody();
    }
    return content;
}

源码分析总结:
1.MessageConverter可以把java对象转换成Message对象,也可以把Message对象转换成java对象
2.MessageListenerAdapter内部通过MessageConverterMessage转换成java对象,然后找到相应的处理方法,参数为转换成的java对象。
3.SimpleMessageConverter处理逻辑:
如果content_type是以text开头,则把消息转换成String类型
如果content_type的值是application/x-java-serialized-object则把消息序列化为java对象,否则,把消息转换成字节数组。

-------------------------------------------------------------------------------------------------

Json MessageConverter

先看一个demo

消费端:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");

        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
        //指定Json转换器
        adapter.setMessageConverter(new Jackson2JsonMessageConverter());
        //设置处理器的消费消息的默认方法
        adapter.setDefaultListenerMethod("onMessage");
        container.setMessageListener(adapter);

        return container;
    }
}

消息转换器使用了RabbitMQ自带的Jackson2JsonMessageConverter转换器,但是没有指定消息的contentType类型

处理器,定义了二个消息处理方法,参数不一样:

public class MessageHandler {

    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }

消费端应用启动类:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("===start up======");
        TimeUnit.SECONDS.sleep(60);
        context.close();
    }
}

生产端代码

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

消息的实体类型

public class Order {

    private Integer id;
    private Integer userId;
    private double amout;
    private String time;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public double getAmout() {
        return amout;
    }

    public void setAmout(double amout) {
        this.amout = amout;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", userId=" + userId +
                ", amout=" + amout +
                ", time='" + time + '\'' +
                '}';
    }
}

应用启动类,生产端传递的消息类型是Order类型,并且转换成JSON类型发送到队列中

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        rabbitTemplate.convertAndSend("","zhihao.miao.order",json);
        context.close();
    }
}

消费之后的控制台打印:

===start up======
---------onMessage----byte-------------
九月 08, 2017 10:25:20 下午 org.springframework.amqp.support.converter.Jackson2JsonMessageConverter fromMessage
警告: Could not convert incoming message with content-type [text/plain]
{"id":1,"userId":1000,"amout":88.0,"time":"2017-09-08T22:03:46.015"}

我们发现消费端还是将其当作字节数组来消费,转换器还是将其转换成byte[]

改造

此时是因为生产端没有指定contentType类型,生产者应用启动类重新指定了相应的contentType类型后,

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
        context.close();
    }
}

此时消费端的Jackson2JsonMessageConverter类型转换器将其转换成Map类型,指定消费的方法参数类型是Map即可。

public class MessageHandler {

    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }


    public void onMessage(Map order){
        System.out.println("---------onMessage---map-------------");
        System.out.println(order.toString());
    }

}

此时消费端控制台打印,我们知道生产者传递JSON类型数据,消费者将其作为Map类型的数据进行处理:

---------onMessage---map-------------
{id=1, userId=1000, amout=88.0, time=2017-10-15T22:47:03.500}

再次改造

如果消费端发送多条消息,发送List的json格式,那么在消费端也要使用参数是List的方法来消费,生产者启动应用类

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);

        Order order2 = new Order();
        order2.setId(2);
        order2.setUserId(2000);
        order2.setAmout(99d);
        order2.setTime(LocalDateTime.now().toString());

        List<Order> orderList = new ArrayList<>();
        orderList.add(order);
        orderList.add(order2);

        String jsonlist = mapper.writeValueAsString(orderList);
        Message message2 = new Message(jsonlist.getBytes(),messageProperties);
        rabbitTemplate.send("","zhihao.miao.order",message2);

        context.close();
    }
}

消费端的Handler:

public class MessageHandler {

    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }


    public void onMessage(Map order){
        System.out.println("---------onMessage---map-------------");
        System.out.println(order.toString());
    }

    public void onMessage(List orders){
        System.out.println("---------onMessage---List-------------");
        System.out.println(orders.toString());
    }

}

消费者控制台打印,此时发现消费端将消息转换成List类型的消息:

---------onMessage---map-------------
{id=1, userId=1000, amout=88.0, time=2017-10-15T22:52:46.739}
---------onMessage---List-------------
[{id=1, userId=1000, amout=88.0, time=2017-10-15T22:52:46.739}, {id=2, userId=2000, amout=99.0, time=2017-10-15T22:52:47.882}]

总结

  • 使用Jackson2JsonMessageConverter处理器,客户端发送JSON类型数据,但是没有指定消息的contentType类型,那么Jackson2JsonMessageConverter就会将消息转换成byte[]类型的消息进行消费。
  • 如果指定了contentType为application/json,那么消费端就会将消息转换成Map类型的消息进行消费。
  • 如果指定了contentType为application/json,并且生产端是List类型的JSON格式,那么消费端就会将消息转换成List类型的消息进行消费。

Jackson2JsonMessageConverter类的源码分析:

@Override
public Object fromMessage(Message message)
        throws MessageConversionException {
    Object content = null;
    MessageProperties properties = message.getMessageProperties();
    if (properties != null) {
        String contentType = properties.getContentType();
        //contentType中包含有json的都是用指定的格式来转换消息
        if (contentType != null && contentType.contains("json")) {
            String encoding = properties.getContentEncoding();
            if (encoding == null) {
                encoding = getDefaultCharset();
            }
            try {

                if (getClassMapper() == null) {
                    JavaType targetJavaType = getJavaTypeMapper()
                            .toJavaType(message.getMessageProperties());
                    content = convertBytesToObject(message.getBody(),
                            encoding, targetJavaType);
                }
                else {
                    Class<?> targetClass = getClassMapper().toClass(
                            message.getMessageProperties());
                    content = convertBytesToObject(message.getBody(),
                            encoding, targetClass);
                }
            }
            catch (IOException e) {
                throw new MessageConversionException(
                        "Failed to convert Message content", e);
            }
        }
        else {
            if (log.isWarnEnabled()) {
                log.warn("Could not convert incoming message with content-type ["
                        + contentType + "]");
            }
        }
    }
    //其余的使用
    if (content == null) {
        content = message.getBody();
    }
    return content;
}

结论:
Jackson2JsonMessageConverter如果接收到的消息属性里面没有content_type属性,或者content_type值不包含json,则转换后的结果是byte[]

Jackson2JsonMessageConverter详解续

上面我们提到的是将实体类型转换成Map或者List类型,这样转换没有多大意义,我们需要消费者将生产者的消息对象格式转换成对应的消息格式,而不是Map或者List对象,解决方案,看代码:
生成端代码:

/**
 * 生产者在发送json数据的时候,需要指定这个json是哪个对象,否则消费者收到消息之后,不知道要转换成哪个java对象
 *
 * 指定方法:
 * 在消息header中,增加一个_TypeId_,value就是具体的java对象(全类名),一定是消费者所在系统的java对象全称
 */
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;

@ComponentScan
public class Application {

    public static void sendOrder( RabbitTemplate rabbitTemplate) throws Exception{
        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        //指定的__TypeId__属性值必须是消费端的Order的全类名,如果不匹配则会报错。
        messageProperties.getHeaders().put("__TypeId__","com.zhihao.miao.test.day10.Sender.Order");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);
        sendOrder(rabbitTemplate);
        context.close();
    }
}

消费端的Handler改造:

import java.util.List;
import java.util.Map;
import com.zhihao.miao.test.day10.Sender.Order;

public class MessageHandler {

    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }


    public void onMessage(Map order){
        System.out.println("---------onMessage---map-------------");
        System.out.println(order.toString());
    }

    public void onMessage(Order order){
        System.out.println("---------onMessage---Order-------------");
        System.out.println(order);
    }

    public void onMessage(List orders){
        System.out.println("---------onMessage---List-------------");
        System.out.println(orders.toString());
    }

}

测试之后发现消费端调用的是onMessage(Order order)这个方法,消费端控制台打印:

---------onMessage---Order-------------
Order{id=1, userId=1000, amout=88.0, time='2017-10-15T23:23:31.977'}

总结

  • 生产者在发送json数据的时候,需要指定这个json是哪个对象,否则消费者收到消息之后,不知道要转换成哪个java对象

指定方法

  • 在消息header中,增加一个TypeId,value就是具体的java对象(全类名),一定是消费者所在系统的java对象全称

优化

我们发现生产者和消费者的耦合度太高,生产者需要知道消费者相应对应的全类名,如何去改造呢?

在消费端配置映射:

 @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");

        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
        //指定Json转换器
        Jackson2JsonMessageConverter jackson2JsonMessageConverter =new Jackson2JsonMessageConverter();

        //消费端配置映射
        Map<String, Class<?>> idClassMapping = new HashMap<>();
        idClassMapping.put("order",Order.class);
        idClassMapping.put("user",User.class);

        DefaultJackson2JavaTypeMapper jackson2JavaTypeMapper = new DefaultJackson2JavaTypeMapper();
        jackson2JavaTypeMapper.setIdClassMapping(idClassMapping);

        System.out.println("在jackson2JsonMessageConverter转换器中指定映射配置");
        jackson2JsonMessageConverter.setJavaTypeMapper(jackson2JavaTypeMapper);
        adapter.setMessageConverter(jackson2JsonMessageConverter);

        //设置处理器的消费消息的默认方法
        adapter.setDefaultListenerMethod("onMessage");
        container.setMessageListener(adapter);

        return container;
    }

消费者处理器Handler中增加入参数是User的方法:

import java.util.List;
import java.util.Map;
import com.zhihao.miao.test.day10.Sender.Order;
import com.zhihao.miao.test.day10.Sender.User;

public class MessageHandler {

    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }


    public void onMessage(Map order){
        System.out.println("---------onMessage---map-------------");
        System.out.println(order.toString());
    }

    public void onMessage(Order order){
        System.out.println("---------onMessage---Order-------------");
        System.out.println(order);
    }

    public void onMessage(User user){
        System.out.println("---------onMessage---user-------------");
        System.out.println(user.toString());
    }

    public void onMessage(List orders){
        System.out.println("---------onMessage---List-------------");
        System.out.println(orders.toString());
    }

}

然后在生产端就可以指定对应的key,而不需要再去指定全类名了,

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;

@ComponentScan
public class Application {

    public static void sendOrder( RabbitTemplate rabbitTemplate) throws Exception{
        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());
        System.out.println(order);

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        messageProperties.getHeaders().put("__TypeId__","order");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void sendUser( RabbitTemplate rabbitTemplate) throws Exception{
        User user = new User();
        user.setUserId(1000);
        user.setAge(50);
        user.setUsername("zhihao.miao");
        user.setPassword("123343");
        System.out.println(user);

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(user);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        //指定消费端配置的key值就行了
        messageProperties.getHeaders().put("__TypeId__","user");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        //sendOrder(rabbitTemplate);
        sendUser(rabbitTemplate);
        context.close();
    }
}

进行测试发现结果符合我们预期。

结论

发送消息的时候,TypeId的值可以是java对象全称,也可以是映射的key
当消费者有配置映射key的时候,生产者既可以指定java对象全称,又可以是映射的key。如果消费者没有配置映射key,则只能指定java对象全称

Jackson2JsonMessageConverter详解续

如果消息类型是List或者Map类型的时候,

生产端:

public static void sendOrderList(RabbitTemplate rabbitTemplate) throws Exception{
    Order order = new Order();
    order.setId(1);
    order.setUserId(1000);
    order.setAmout(88d);
    order.setTime(LocalDateTime.now().toString());

    Order order2 = new Order();
    order2.setId(2);
    order2.setUserId(2000);
    order2.setAmout(99d);
    order2.setTime(LocalDateTime.now().toString());

    List<Order> orderList = Arrays.asList(order,order2);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(orderList);

    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("application/json");
    messageProperties.getHeaders().put("__TypeId__","java.util.List");
    messageProperties.getHeaders().put("__ContentTypeId__","order");


    Message message = new Message(json.getBytes(),messageProperties);
    rabbitTemplate.send("","zhihao.miao.order",message);
}


public static void sendOrderMap(RabbitTemplate rabbitTemplate) throws Exception{
    Order order = new Order();
    order.setId(1);
    order.setUserId(1000);
    order.setAmout(88d);
    order.setTime(LocalDateTime.now().toString());

    Order order2 = new Order();
    order2.setId(2);
    order2.setUserId(2000);
    order2.setAmout(99d);
    order2.setTime(LocalDateTime.now().toString());

    Map<String,Object> orderMaps = new HashMap<>();
    orderMaps.put("10",order);
    orderMaps.put("20",order2);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(orderMaps);

    MessageProperties messageProperties = new MessageProperties();
    messageProperties.setContentType("application/json");
    messageProperties.getHeaders().put("__TypeId__","java.util.Map");
    messageProperties.getHeaders().put("__KeyTypeId__","java.lang.String");
    messageProperties.getHeaders().put("__ContentTypeId__","order");


    Message message = new Message(json.getBytes(),messageProperties);
    rabbitTemplate.send("","zhihao.miao.order",message);
}

消费端:

public void onMessage(List<Order> orders){
    System.out.println("---------onMessage---List<Order>-------------");
    orders.stream().forEach(order -> System.out.println(order));
}

public void onMessage(Map<String,Object> orderMaps){
    System.out.println("-------onMessage---Map<String,Object>------------");
    orderMaps.keySet().forEach(key -> System.out.println(orderMaps.get(key)));
}

结论
如果生产者发送的是list的json数据,则还需要增加一个__ContentTypeId__的header,用于指明List里面的具体对象。

如果生产者发送的是map的json数据,则需要指定__KeyTypeId____ContentTypeId__的header,用于指明map里面的key,value的具体对象。

ContentTypeDelegatingMessageConverter详解

 
MessageConverter接口继承体系

生产端:

public class Application {

    public static void sendOrder( RabbitTemplate rabbitTemplate) throws Exception{
        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(order);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        messageProperties.getHeaders().put("__TypeId__","order");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void sendUser( RabbitTemplate rabbitTemplate) throws Exception{
        User user = new User();
        user.setUserId(1000);
        user.setAge(50);
        user.setUsername("zhihao.miao");
        user.setPassword("123343");

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(user);
        System.out.println(json);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        messageProperties.getHeaders().put("__TypeId__","user");
        Message message = new Message(json.getBytes(),messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void sendOrderList(RabbitTemplate rabbitTemplate) throws Exception{
        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        Order order2 = new Order();
        order2.setId(2);
        order2.setUserId(2000);
        order2.setAmout(99d);
        order2.setTime(LocalDateTime.now().toString());

        List<Order> orderList = Arrays.asList(order,order2);

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(orderList);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        messageProperties.getHeaders().put("__TypeId__","java.util.List");
        messageProperties.getHeaders().put("__ContentTypeId__","order");


        Message message = new Message(json.getBytes(),messageProperties);
        rabbitTemplate.send("","zhihao.miao.order",message);
    }


    public static void sendOrderMap(RabbitTemplate rabbitTemplate) throws Exception{
        Order order = new Order();
        order.setId(1);
        order.setUserId(1000);
        order.setAmout(88d);
        order.setTime(LocalDateTime.now().toString());

        Order order2 = new Order();
        order2.setId(2);
        order2.setUserId(2000);
        order2.setAmout(99d);
        order2.setTime(LocalDateTime.now().toString());

        Map<String,Object> orderMaps = new HashMap<>();
        orderMaps.put("10",order);
        orderMaps.put("20",order2);

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(orderMaps);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("application/json");
        messageProperties.getHeaders().put("__TypeId__","java.util.Map");
        messageProperties.getHeaders().put("__KeyTypeId__","java.lang.String");
        messageProperties.getHeaders().put("__ContentTypeId__","order");


        Message message = new Message(json.getBytes(),messageProperties);
        rabbitTemplate.send("","zhihao.miao.order",message);
    }


    public static void sendJepg(RabbitTemplate rabbitTemplate) throws Exception{
        byte[] body = Files.readAllBytes(Paths.get("/Users/naeshihiroshi/Desktop/file/file","aisi.jpeg"));

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("image/jepg");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("","zhihao.miao.order",message);
    }

    public static void sendJson( RabbitTemplate rabbitTemplate) throws Exception{


        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text/plain");
        Message message = new Message("hello".getBytes(),messageProperties);
        rabbitTemplate.send("","zhihao.miao.order",message);
    }



    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(com.zhihao.miao.day03.Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        //sendOrder(rabbitTemplate);
        //sendUser(rabbitTemplate);
        //sendOrderList(rabbitTemplate);
        //sendOrderMap(rabbitTemplate);
        sendJepg(rabbitTemplate);

        context.close();
    }
}

消费端:

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");

        MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageHandler());
        //指定Json转换器
        Jackson2JsonMessageConverter jackson2JsonMessageConverter =new Jackson2JsonMessageConverter();


        Map<String, Class<?>> idClassMapping = new HashMap<>();
        idClassMapping.put("order",Order.class);
        idClassMapping.put("user",User.class);

        DefaultJackson2JavaTypeMapper jackson2JavaTypeMapper = new DefaultJackson2JavaTypeMapper();
        jackson2JavaTypeMapper.setIdClassMapping(idClassMapping);

        jackson2JsonMessageConverter.setJavaTypeMapper(jackson2JavaTypeMapper);
        adapter.setMessageConverter(jackson2JsonMessageConverter);

        TextMessageConverter textMessageConverter = new TextMessageConverter();

        ContentTypeDelegatingMessageConverter contentTypeDelegatingMessageConverter = new ContentTypeDelegatingMessageConverter();
        contentTypeDelegatingMessageConverter.addDelegate("text",textMessageConverter);
        contentTypeDelegatingMessageConverter.addDelegate("html/text",textMessageConverter);
        contentTypeDelegatingMessageConverter.addDelegate("xml/text",textMessageConverter);
        contentTypeDelegatingMessageConverter.addDelegate("text/plain",textMessageConverter);

        contentTypeDelegatingMessageConverter.addDelegate("json",jackson2JsonMessageConverter);
        contentTypeDelegatingMessageConverter.addDelegate("application/json",jackson2JsonMessageConverter);

        contentTypeDelegatingMessageConverter.addDelegate("image/jpg",new JPGMessageConverter());
        contentTypeDelegatingMessageConverter.addDelegate("image/jepg",new JPGMessageConverter());
        contentTypeDelegatingMessageConverter.addDelegate("image/png",new JPGMessageConverter());


        adapter.setMessageConverter(contentTypeDelegatingMessageConverter);
        //设置处理器的消费消息的默认方法
        adapter.setDefaultListenerMethod("onMessage");
        container.setMessageListener(adapter);

        return container;
    }
}

指定的TextMessageConverter消息转换器

public class TextMessageConverter implements MessageConverter {


    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        System.out.println("=======toMessage=========");
        return new Message(object.toString().getBytes(),messageProperties);
    }

    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("=======fromMessage=========");
        return new String(message.getBody());
    }
}

指定的JPGMessageConverter消息转换器

public class JPGMessageConverter implements MessageConverter{
    @Override
    public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        return null;
    }

    @Override
    public Object fromMessage(Message message) throws MessageConversionException {
        System.out.println("====JPGMessageConverter====");
        byte[] body = message.getBody();
        String fileName = UUID.randomUUID().toString();
        String path = "/Users/naeshihiroshi/Desktop/file/"+fileName+".jpg";
        File file = new File(path);
        try{
            Files.copy(new ByteArrayInputStream(body),file.toPath());
        }catch (IOException e){
            e.printStackTrace();
        }
        return file;
    }
}

客户端消息处理器

public class MessageHandler {


    public void onMessage(byte[] message){
        System.out.println("---------onMessage----byte-------------");
        System.out.println(new String(message));
    }


    public void onMessage(String message){
        System.out.println("---------onMessage---String-------------");
        System.out.println(message);
    }

    public void onMessage(Order order){
        System.out.println("---------onMessage---Order-------------");
        System.out.println(order);
    }

    public void onMessage(User user){
        System.out.println("---------onMessage---user-------------");
        System.out.println(user);
    }

    public void onMessage(List<Order> orders){
        System.out.println("---------onMessage---List<Order>-------------");
        orders.stream().forEach(order -> System.out.println(order));
    }

    public void onMessage(Map<String,Object> orderMaps){
        System.out.println("-------onMessage---Map<String,Object>------------");
        orderMaps.keySet().forEach(key -> System.out.println(orderMaps.get(key)));
    }

    public void onMessage(File message){
        System.out.println("-------onMessage---File message------------");
        System.out.println(message.getName());
    }
}

服务器端应用类,

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        System.out.println("===start up ing======");
        TimeUnit.SECONDS.sleep(60);
        context.close();
    }
}

结论

  • ContentTypeDelegatingMessageConverter是一个代理的MessageConverter。
  • ContentTypeDelegatingMessageConverter本身不做消息转换的具体动作,而是将消息转换委托给具体的MessageConverter。我们可以设置COntentType和MessageConverter的映射关系。
  • ContentTypeDelegatingMessageConverter还有一个默认的MessageConverter,也就是说当根据ContentType没有找到映射的MessageConverter的时候,就会使用默认的MessageConverter。

-------------------------------------------------------------------------------------------------

RabbitListenerConfigurer详解

 
RabbitListenerConfigurer源码分析
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerEndpoint;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConsumerConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        return factory;
    }

    @Bean
    public RabbitListenerConfigurer rabbitListenerConfigurer(){

        return new RabbitListenerConfigurer() {
            @Override
            public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {

                //endpoint设置zhihao.miao.order队列的消息处理逻辑
                SimpleRabbitListenerEndpoint endpoint = new SimpleRabbitListenerEndpoint();
                endpoint.setId("10");
                endpoint.setQueueNames("zhihao.miao.order");
                endpoint.setMessageListener(message -> {
                    System.out.println("endpoint1处理消息的逻辑");
                    System.out.println(new String(message.getBody()));
                });


                //使用适配器来处理消息,设置了order,pay队列的消息处理逻辑
                SimpleRabbitListenerEndpoint endpoint2 = new SimpleRabbitListenerEndpoint();
                endpoint2.setId("11");
                endpoint2.setQueueNames("order","pay");
                System.out.println("endpoint2处理消息的逻辑");
                endpoint2.setMessageListener(new MessageListenerAdapter(new MessageHandler()));

                //注册二个endpoint
                registrar.registerEndpoint(endpoint);
                registrar.registerEndpoint(endpoint2);
            }
        };
    }
}

消费端消息处理器

public class MessageHandler {

    public void handleMessage(byte[] message){
        System.out.println("消费消息");
        System.out.println(new String(message));
    }
}

消费端应用启动类

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@EnableRabbit
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(60);
        context.close();
    }
}

使用总结

  • 实现RabbitListenerConfigurer接口,并把实现类托管到spring容器中
  • 在spring容器中,托管一个RabbitListenerContainerFactory的bean(SimpleRabbitListenerContainerFactory
  • 在启动类上加上@EnableRabbit注解

-------------------------------------------------------------------------------------------------

之前的博客中我们可以在spring容器中构建SimpleMessageListenerContainer来消费消息,我们也可以使用@RabbitListener来消费消息。

@RabbitListener注解指定目标方法来作为消费消息的方法,通过注解参数指定所监听的队列或者Binding。使用@RabbitListener可以设置一个自己明确默认值的RabbitListenerContainerFactory对象。
可以在配置文件中设置RabbitListenerAnnotationBeanPostProcessor并通过<rabbit:annotation-driven/>来设置@RabbitListener的执行,当然也可以通过@EnableRabbit注解来启用@RabbitListener

示列

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

定义消息处理器,@RabbitListener注解标记的方法

@Component
public class MessageHandler {

    @RabbitListener(queues = "zhihao.miao.order")
    public void handleMessage(byte[] message){
        System.out.println("消费消息");
        System.out.println(new String(message));
    }
}

应用启动类,@EnableRabbit启用@RabbitListener

@EnableRabbit
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(60);
        context.close();
    }
}

测试:


 
发送消息

控制台打印:

消费消息
你的订单已经生成。

如果发送的消息content_type的属性是text,那么接收的消息处理方法的参数就必须是String类型,如果是byte[]类型就会报错。

 
指定content_type类型为text

控制台报错

 
image.png
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageHandler {

    //此时如果去掉content_type为text,那么会将消息转换成其每个字符的int类型
    //@RabbitListener(queues = "zhihao.miao.order")
    public void handleMessage(String message){
        System.out.println("消费消息");
        System.out.println(new String(message));
    }

    //此时不管属性中有没有content_type属性都能接收到数据
    @RabbitListener(queues = "zhihao.miao.order")
    public void handleMessage(Message message){
        System.out.println("====消费消息===handleMessage(message)");
        System.out.println(message.getMessageProperties());
        System.out.println(new String(message.getBody()));
    }
}

总结
如果消息属性中没有指定content_type,则接收消息的处理方法接收类型是byte[],如果消息属性中指定content_type为text,则接收消息的处理方法的参数类型是String类型。不管有没有指定content_type,处理消息方法的参数类型是Message都不会报错。

步骤

  • 在启动入口增加@EnableRabbit注解
  • 在spring容器中托管一个RabbitListenerContainerFactory的bean(默认的实现是org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory)
  • 写一个消息处理类托管到spring容器中,然后在具体的消息处理方法上增加@RabbitListener注解

具体的消息处理方法的参数是跟MessageConverter转换后的java对象有关。
如果想要设置MessageConverter,则需要在RabbitListenerContainerFactory的实例中去设置,(setMessageConverter方法)

使用@Payload和@Headers注解

@Component
public class MessageHandler {

    //获取消息的头属性和body属性
    @RabbitListener(queues = "zhihao.miao.order")
    public void handleMessage(@Payload String body, @Headers Map<String,Object> headers){
        System.out.println("====消费消息===handleMessage");
        System.out.println(headers);
        System.out.println(body);
    }
}

获取单一个Header的属性,Header还有其他的一些属性,比如requireddefaultvalue等属性,顾名思义:

@Component
public class MessageHandler {

    //获取特定的消息
    @RabbitListener(queues = "zhihao.miao.order")
    public void handleMessage(@Payload String body,@Header String token){
        System.out.println("====消费消息===handleMessage");
        System.out.println(token);
        System.out.println(body);
    }
}
 
测试

指定向多个队列中发送消息

@Component
public class MessageHandler {
    @RabbitListener(queues ={"zhihao.miao.order","zhihao.info"})
    public void handleMessage(Message message){
        System.out.println("====消费消息"+message.getMessageProperties().getConsumerQueue()+"===handleMessage");
        System.out.println(message.getMessageProperties());
        System.out.println(new String(message.getBody()));
    }
}

通过配置文件发送消息

@Component
public class MessageHandler {

    //通过配置文件发送消息
    @RabbitListener(queues ={"${zhihao.queue1}","${zhihao.queue2}"})
    public void handleMessage(Message message){
        System.out.println("====消费消息"+message.getMessageProperties().getConsumerQueue()+"===handleMessage");
        System.out.println(message.getMessageProperties());
        System.out.println(new String(message.getBody()));
    }

}

配置文件:

zhihao.queue1=zhihao.miao.order
zhihao.queue2=zhihao.info

启动类:

@EnableRabbit
@ComponentScan
@PropertySource(value = "classpath:mq.properties")
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(200);
        context.close();
    }
}

@RabbitListener注解进行声明binding

定义mq中不存在的Queueexchangeroute key

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageHandler {

    //支持自动声明绑定,声明之后自动监听队列的队列,此时@RabbitListener注解的queue和bindings不能同时指定,否则报错
    @RabbitListener(bindings ={@QueueBinding(value = @Queue(value = "q5",durable = "true"),
            exchange =@Exchange(value = "zhihao.miao.exchange",durable = "true"),key = "welcome")})
    public void handleMessage(Message message){
        System.out.println("====消费消息"+message.getMessageProperties().getConsumerQueue()+"===handleMessage");
        System.out.println(message.getMessageProperties());
        System.out.println(new String(message.getBody()));
    }


}

从上面的我们知道声明必须容器中要有RabbitAdminRabbitTemplate实例

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConsumerConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        //SimpleRabbitListenerContainerFactory发现消息中有content_type有text就会默认将其转换成string类型的
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

}

应用启动类

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@EnableRabbit
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(200);
        context.close();
    }
}

测试验证

 
自动声明成功
 
消息发送

控制台打印:

rabbit service startup
====消费消息q5===handleMessage
MessageProperties [headers={}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=null, contentEncoding=null, contentLength=0, deliveryMode=null, receivedDeliveryMode=NON_PERSISTENT, expiration=null, priority=null, redelivered=false, receivedExchange=zhihao.miao.exchange, receivedRoutingKey=welcome, receivedDelay=null, deliveryTag=1, messageCount=0, consumerTag=amq.ctag-RBqtzCiTxMg6knwQJX7B3A, consumerQueue=q5]
hello,自动注册成了吗

说明自动声明的绑定中的队列被自动默认监听。@RabbitListener注解中的bindingsqueues参数不能同时指定,否则会报错。

@RabbitListener和@RabbitHandler搭配使用

@RabbitListener可以标注在类上面,当使用在类上面的时候,需要配合@RabbitHandler注解一起使用,@RabbitListener标注在类上面表示当有收到消息的时候,就交给带有@RabbitHandler的方法处理,具体找哪个方法处理,需要跟进MessageConverter转换后的java对象。
配置:

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

处理器方法

@Component
@RabbitListener(queues ="zhihao.miao.order")
public class MessageHandler {


    @RabbitHandler
    public void handleMessage(byte[] message){
        System.out.println("====消费消息handleMessage");
        System.out.println(new String(message));
    }

    @RabbitHandler
    public void handleMessage2(String message){
        System.out.println("====消费消息===handleMessage2");
        System.out.println(message);
    }
}

应用启动类:

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@EnableRabbit
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(3000);
        context.close();
    }
}

发送不包含content_type属性的消息和content_type属性为text的消息,控制台打印:

rabbit service startup
====消费消息handleMessage
订单已经生成,请到订单-详情页面确认。
====消费消息===handleMessage2
订单已经生成,请到订单-详情页面确认。

@RabbitListener注解的containerFactory属性

@RabbitListener注解的containerFactory属性可以指定一个RabbitListenerContainerFactory的bean,默认是找名字为rabbitListenerContainerFactory的实例。

当我们将ConsumerConfig类中的RabbitListenerContainerFactory实例的对象名改掉的时候,发现就会报错。

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

此时控制台上报错,

 
报错信息

此时如果配置一下@RabbitListener注解的containerFactory属性便不会报错。

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues ="zhihao.miao.order",containerFactory = "rabbitListenerContainerFactory2")
public class MessageHandler {

    @RabbitHandler
    public void handleMessage(byte[] message){
        System.out.println("====消费消息handleMessage");
        System.out.println(new String(message));
    }

    @RabbitHandler
    public void handleMessage2(String message){
        System.out.println("====消费消息===handleMessage2");
        System.out.println(message);
    }
}

我们再去改造一下在RabbitListenerContainerFactory实例中定义消息类型转换器

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

User对象:

public class User {

    private int age;

    private String name;
    
   ...get set

    public User(int age,String name){
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

在处理器中增加参数是User的方法:

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues ="zhihao.miao.order",containerFactory = "rabbitListenerContainerFactory2")
public class MessageHandler {

    @RabbitHandler
    public void handleMessage3(User user){
        System.out.println("====消费消息===handleMessage3");
        System.out.println(user);
    }


}
 
 

-------------------------------------------------------------------------------------------------

ReturnListener

mandatory

This flag tells the server how to react if the message cannot be routed to a queue.if this flag is set,the server will return an unroutable message with a return method.if this flag is zero,the server silently drops the message
如果mandatory有设置,则当消息不能路由到队列中去的时候,会触发return method。如果mandatory没有设置,则当消息不能路由到队列的时候,server会删除该消息。

immediate

This flag tell the server how to react if the message cannot be routed to a queue consumer immediately.if the flag is set ,the server will return an undeliverable message with a return method.if this flag is zero,the server will queue the message ,but with no guarantee that it will ever be consumed.
如果有设置immediate,则当消息路由到队列的时候,没有消费者的时候,会触发return method。如果immediate标识是0,则服务器就会将消息加入队列,但是不能保证这个消息能被消费。

注意
immediate属性在Rabbitmq3.x的时候,被废弃了。
因为这个关键字违背了生产者和消费者之间解耦的特性,因为生产者不关心消息是否被消费者消费掉,所以这个字段被drop掉了。

RabbitMQ java client 相关API

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class Send {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //或者
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int replyCode,
                                     String replyText,
                                     String exchange,
                                     String routingKey,
                                     AMQP.BasicProperties properties,
                                     byte[] body)
                    throws IOException {
                System.out.println("=========handleReturn===method============");
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);
                System.out.println("message:"+new String(body));
            }
        });

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();

        //第三个参数是设置的mandatory
        channel.basicPublish("zhihao.direct.exchange","log.aaa",true,properties,"注册验证码".getBytes());

        TimeUnit.SECONDS.sleep(10);

        channel.close();
        connection.close();
    }
}
 
zhihao.direct.exchange的路由信息

因为路由不到正确的队列所以触发了ReturnListener方法的回调,控制台上打印:

replyText:NO_ROUTE
exchange:zhihao.direct.exchange
routingKey:log.aaa
message:注册验证码

spring-amqp的相关api

配置:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        factory.setPublisherReturns(true);
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        //rabbitTemplate.setMandatoryExpression(new SpelExpressionParser().parseExpression("(1+2) > 3")); //表达式的值为false
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
            @Override
            public void returnedMessage(Message message,
                                        int replyCode,
                                        String replyText,
                                        String exchange,
                                        String routingKey){
                System.out.println("============returnedMessage==method=========");
                System.out.println("replyCode: "+replyCode);
                System.out.println("replyText: "+replyText);
                System.out.println("exchange: "+exchange);
                System.out.println("routingKey: "+routingKey);
            }
        });
        return rabbitTemplate;
    }
}

发送消息

import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.getHeaders().put("desc","消息发送");
        messageProperties.getHeaders().put("token","234sdfsdf3r342dsfd1232");

        //路由不到指定的队列
        rabbitTemplate.convertAndSend("zhihao.direct.exchange","log.aaa","hello welcome");

        TimeUnit.SECONDS.sleep(10);

        context.close();
    }
}
 
zhihao.direct.exchange的路由信息

rabbitTemplate.setReturnCallback的方法会被执行,控制台打印:

============returnedMessage==method=========
replyCode: 312
replyText: NO_ROUTE
exchange: zhihao.direct.exchange
routingKey: log.aaa

总结

  • 设置factory.setPublisherReturns(true);
  • rabbitTemplate.setMandatory(true);或rabbitTemplate.setMandatoryExpression(new SpelExpressionParser().parseExpression("(1+2) > 2")); //表达式的值为true
    才会触发returnCallback回调方法的执行。

-------------------------------------------------------------------------------------------------

问题

企业中使用消息中间件面临的常见问题:
1.消息莫名其妙的没了,也不知道什么情况,有丢消息的问题。
2.发送者没法确认是否发送成功,消费者处理失败也无法反馈。

消息可靠性的二种方式
1.事务,利用AMQP协议的一部分,发送消息前设置channel为tx模式(channel.txSelect();),如果txCommit提交成功了,则消息一定到达了broker了,如果在txCommit执行之前broker异常崩溃或者由于其他原因抛出异常,这个时候我们便可以捕获异常通过txRollback回滚事务了。(大大得削弱消息中间件的性能)
2.消息确认(publish confirms),设置管道为confirmSelect模式(channel.confirmSelect();)

 
publisher confirms,consumer Acknowledgements

生产者与broker之间的消息确认称为public confirms,public confirms机制用于解决生产者与Rabbitmq服务器之间消息可靠传输,它在消息服务器持久化消息后通知消息生产者发送成功。

发送确认(publisher confirms)

RabbitMQ java Client实现发送确认

deliveryTag(投递的标识),当Channel设置成confirm模式时,发布的每一条消息都会获得一个唯一的deliveryTag,任何channel上发布的第一条消息的deliveryTag为1,此后的每一条消息都会加1,deliveryTag在channel范围内是唯一的。

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

public class Send {

    static Long id = 0L;

    static TreeSet<Long> tags = new TreeSet<>();

    public static Long send(Channel channel,byte[] bytes) throws Exception{
        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();
        channel.basicPublish("zhihao.direct.exchange","zhihao.miao.order",properties,bytes);
        return ++id;
    }


    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //是当前的channel处于确认模式
        channel.confirmSelect();

        //使当前的channel处于事务模式,与上面的使channel处于确认模式使互斥的
        //channel.txSelect();

        /**
         * deliveryTag 消息id
         * multiple 是否批量
         *      如果是true,就意味着,小于等于deliveryTag的消息都处理成功了
         *      如果是false,只是成功了deliveryTag这一条消息
         */
        channel.addConfirmListener(new ConfirmListener() {
            //消息发送成功并且在broker落地,deliveryTag是唯一标志符,在channek上发布的消息的deliveryTag都会比之前加1
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("=========deliveryTag==========");
                System.out.println("deliveryTag: "+deliveryTag);
                System.out.println("multiple: "+multiple);
                //处理成功发送的消息
                if(multiple){
                    //批量操作
                    for(Long _id:new TreeSet<>(tags.headSet(deliveryTag+1))){
                        tags.remove(_id);
                    }
                }else{
                    //单个确认
                    tags.remove(deliveryTag);
                }

                System.out.println("未处理的消息: "+tags);
            }

            /**
             * deliveryTag 消息id
             * multiple 是否批量
             *      如果是true,就意味着,小于等于deliveryTag的消息都处理失败了
             *      如果是false,只是失败了deliveryTag这一条消息
             */
            //消息发送失败或者落地失败
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("===========handleNack===========");
                System.out.println("deliveryTag: "+deliveryTag);
                System.out.println("multiple: "+multiple);
            }
        });

        /**
         * 当Channel设置成confirm模式时,发布的每一条消息都会获得一个唯一的deliveryTag
         * deliveryTag在basicPublish执行的时候加1
         */


        Long id = send(channel,"你的外卖已经送达".getBytes());
        tags.add(id);
        //channel.waitForConfirms();

        id =send(channel,"你的外卖已经送达".getBytes());
        tags.add(id);
        //channel.waitForConfirms();

        id = send(channel,"呵呵,不接电话".getBytes());
        tags.add(id);
        //channel.waitForConfirms();  

        TimeUnit.SECONDS.sleep(10);

        channel.close();
        connection.close();
    }
}

channel.waitForConfirms():表示等待已经发送给broker的消息act或者nack之后才会继续执行。
channel.waitForConfirmsOrDie():表示等待已经发送给broker的消息act或者nack之后才会继续执行,如果有任何一个消息触发了nack则抛出IOException。

总结
生产者与broker之间的消息可靠性保证的基本思路就是

  • 当消息发送到broker的时候,会执行监听的回调函数,其中deliveryTag是消息id(在同一个channel中这个数值是递增的,而multiple表示是否批量确认消息。
  • 在生产端要维护一个消息发送的表,消息发送的时候记录消息id,在消息成功落地broker磁盘并且进行回调确认(ack)的时候,根据本地消息表和回调确认的消息id进行对比,这样可以确保生产端的消息表中的没有进行回调确认(或者回调确认时网络问题)的消息进行补救式的重发,当然不可避免的就会在消息端可能会造成消息的重复消息。针对消费端重复消息,在消费端进行幂等处理。(丢消息和重复消息是不可避免的二个极端,比起丢消息,重复消息还有补救措施,而消息丢失就真的丢失了。

Spring AMQP实现实现发送确认

示列
定义消息内容

public class Order {

    private String orderId;

    private String createTime;

    private double price;

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getCreateTime() {
        return createTime;
    }

    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

配置项:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        factory.setPublisherConfirms(true);
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            /**
             * @param correlationData 唯一标识,有了这个唯一标识,我们就知道可以确认(失败)哪一条消息了
             * @param ack
             * @param cause
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("=====消息进行消费了======");
                if(ack){
                    System.out.println("消息id为: "+correlationData+"的消息,已经被ack成功");
                }else{
                    System.out.println("消息id为: "+correlationData+"的消息,消息nack,失败原因是:"+cause);
                }
            }
        });
        return rabbitTemplate;
    }

}

启动应用类:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.time.LocalDateTime;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static Order createOrder(){
        Order order = new Order();
        order.setOrderId(UUID.randomUUID().toString());
        order.setCreateTime(LocalDateTime.now().toString());
        order.setPrice(100L);
        return order;
    }

    public static void saveOrder(Order order){
        //入库操作
        System.out.println("入库操作");
    }

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        Order order  = createOrder();

        saveOrder(order);

        ObjectMapper objectMapper = new ObjectMapper();
        byte[] body = objectMapper.writeValueAsBytes(order);

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        System.out.println("id: "+order.getOrderId());

        //指定correlationData的值
        rabbitTemplate.send("zhihao.direct.exchange","zhihao.miao.order",message,new CorrelationData(order.getOrderId().toString()));

        TimeUnit.SECONDS.sleep(10);

        context.close();
    }
}

控制台打印:

入库操作
id: 11bc9eb3-fbcb-4777-9596-b6f6db81cafc
十月 22, 2017 7:14:14 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory createBareConnection
信息: Created new connection: connectionFactory#50ad3bc1:0/SimpleConnection@4efc180e [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 61095]
=====消息进行消费了======
消息id为: CorrelationData [id=11bc9eb3-fbcb-4777-9596-b6f6db81cafc]的消息,已经被ack成功

原理其实和java client是一样的,我们在发送消息的时候落地本地的消息表(有表示confirm字段),然后进行回调确认的方法中进行状态的更新,最后轮询表中状态不正确的消息进行轮询重发。

步骤

  • 在容器中的ConnectionFactory实例中加上setPublisherConfirms属性
    factory.setPublisherConfirms(true);
  • 在RabbitTemplate实例中增加setConfirmCallback回调方法。
  • 发送消息的时候,需要指定CorrelationData,用于标识该发送的唯一id。

对比与java client的publisher confirm:
1.spring amqp不支持批量确认,底层的rabbitmq java client方式支持批量确认。
2.spring amqp提供的方式更加的简单明了。

-------------------------------------------------------------------------------------------------

消费确认(comsumer acknowledgements)

broker与消费者之间的消息确认称为comsumer acknowledgements,comsumer acknowledgements机制用于解决消费者与Rabbitmq服务器之间消息可靠传输,它是在消费端消费成功之后通知broker消费端消费消息成功从而broker删除这个消息。

RabbitMQ Java Client 实现消息确认

自动确认

zhihao.miao.order队列中发送一条消息

 
web管控台查看

 

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

public class Consumer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        /**
         * basicConsume方法的第二个参数是boolean类型,true表示消息一旦投递出去就自动确认,而false表示需要自己手动去确认
         * 自动确认有丢消息的可能,因为如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
         * 设置了false,表示需要人为手动的去确定消息,只有消费者将消息消费成功之后给与broker人为确定才进行消息确认
         * 这边也有个问题就是如果由于程序员自己的代码的原因造成人为的抛出异常,人工确认那么消息就会一直重新入队列,一直重发?
         */

        String consumerTag = channel.basicConsume("zhihao.miao.order",true,new SimpleConsumer(channel));
        System.out.println(consumerTag);

        TimeUnit.SECONDS.sleep(30);

        channel.close();
        connection.close();
    }
}

消费具体逻辑

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println(consumerTag);
        System.out.println("-----收到消息了---------------");
        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));
        try
        {
            int i = 1/0;
            System.out.println(i);
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

控制台打印:

amq.ctag-6_GQmh1tMeooWSiuqUmz0Q
java.lang.ArithmeticException: / by zero
-----收到消息了---------------
    at com.zhihao.test.day04.SimpleConsumer.handleDelivery(SimpleConsumer.java:29)
amq.ctag-6_GQmh1tMeooWSiuqUmz0Q
    at com.rabbitmq.client.impl.ConsumerDispatcher$5.run(ConsumerDispatcher.java:149)
    at com.rabbitmq.client.impl.ConsumerWorkService$WorkPoolRunnable.run(ConsumerWorkService.java:100)
消息属性为:#contentHeader<basic>(content-type=json, content-encoding=null, headers={}, delivery-mode=2, priority=0, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
消息内容为:{"orderId":"abba05db-050e-4b1a-97f1-c469b23ca27b","createTime":"2017-10-22T21:02:41.861","price":100.0}
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

此时可以看到消费端抛出了异常,但是我们发现这条消息也已经消费掉了,此时如果消费端消费逻辑使用spring进行管理的话消费端业务逻辑会进行回滚,这也就造成了实际意义的消息丢失。

 
web管控台

手动确认

自动确认会造成实际意义上的消息丢失。

将basicConsume方法的第二个参数改为false,表示人工的进行消息确认,如果消费者正在监听队列,那么此时消息进入Unacked,而如果消费者停掉服务,那么消息的状态又变成Ready了。这个机制表明了消息必须是ack确认之后才会在server中删除掉。

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

public class Consumer {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        //手动确认
        String consumerTag = channel.basicConsume("zhihao.miao.order",false,new SimpleConsumer(channel));
        System.out.println(consumerTag);

        TimeUnit.SECONDS.sleep(30);

        channel.close();
        connection.close();
    }
}

消费具体逻辑

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
        System.out.println(consumerTag);
        System.out.println("-----收到消息了--------------");

        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.getChannel().basicAck(envelope.getDeliveryTag(),false);
        System.out.println("消息消费成功");
    }
}
 
此时消息已经发送给消费者,但是消费者还没有进行手动确认

发送一个header中包含error属性的消息,

 
发送一个header中包含error属性的消息

改造消费逻辑

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;
import java.util.concurrent.TimeUnit;


public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
        System.out.println(consumerTag);
        System.out.println("-----收到消息了--------------");

        if(properties.getHeaders().get("error") != null){
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
          
            System.out.println("nack");
            this.getChannel().basicNack(envelope.getDeliveryTag(),false,true);

            return;
        }
        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));


        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.getChannel().basicAck(envelope.getDeliveryTag(),false);
        System.out.println("消息消费成功");
    }
}

控制台打印,说明该消息一直重新入队列然后一直重新消费

amq.ctag-U5cHBcnxa5dhkYXjd1LFgQ
amq.ctag-U5cHBcnxa5dhkYXjd1LFgQ
-----收到消息了--------------
nack
amq.ctag-U5cHBcnxa5dhkYXjd1LFgQ
-----收到消息了--------------
nack
amq.ctag-U5cHBcnxa5dhkYXjd1LFgQ
-----收到消息了--------------
nack
amq.ctag-U5cHBcnxa5dhkYXjd1LFgQ
-----收到消息了--------------
nack

消费端也可以拒绝消息,

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException{
        System.out.println(consumerTag);
        System.out.println("-----收到消息了--------------");

        if(properties.getHeaders().get("error") != null){
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
            //这个api也支持拒绝消息消费,第二个参数表示是否重新入队列
            this.getChannel().basicReject(envelope.getDeliveryTag(),false);
            System.out.println("消息无法消费,拒绝消息");
            return;
        }
        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));


        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.getChannel().basicAck(envelope.getDeliveryTag(),false);
        System.out.println("消息消费成功");
    }
}

控制台打印,因为设置了不重新入队列,所以不再重新发消息了:

amq.ctag-kiy_49AkC3f4qRkqCMujrw
amq.ctag-kiy_49AkC3f4qRkqCMujrw
-----收到消息了--------------
消息无法消费,拒绝消息

总结
消费端的消息确认分为二个步骤,

  • 在channel.basicConsume指定为手动确认。
  • 具体根据业务逻辑来进行判断什么是ack什么时候nack(又分为要不要重新requeue)

这边有个问题就是nack时候或者reject时候重新入队列如果业务端因为代码逻辑问题一直重发怎样去设置一个次数值?
我的设想就是设置一个重新发送的递增值,这个值与消息id对应,去处理解决它。或者在redis或者memcache等其他保存方式然后记录这个重发次数。
How do I set a number of retry attempts in RabbitMQ?

Spring AMQP消费端实现消息确认

自动确认

配置类

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        /**
         * 自动确认涉及到一个问题就是如果在消息消息的时候抛出异常,消息处理失败,但是因为自动确认而server将该消息删除了。
         * NONE表示自动确认
         */
        container.setAcknowledgeMode(AcknowledgeMode.NONE);
        container.setMessageListener((MessageListener) message -> {
            System.out.println("====接收到消息=====");
            System.out.println(new String(message.getBody()));

            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //相当于自己的一些消费逻辑抛错误
            throw new NullPointerException("consumer fail");

        });
        return container;
    }
}

应用启动类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        TimeUnit.SECONDS.sleep(100);
        System.out.println("message container startup");

        context.close();
    }
}

控制台打印:

====接收到消息=====
{"orderId":"d232eea5-35ae-4534-80f4-cfb31f49178f","createTime":"2017-10-22T22:11:34.239","price":100.0}
十月 22, 2017 10:11:58 下午 org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler handleError
警告: Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener threw exception

Web控制台上显示消息消费确认也成功。问题还是自动确认会造成事实上的消息丢失。

手动确认

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");

        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
            System.out.println("====接收到消息=====");
            System.out.println(new String(message.getBody()));
            TimeUnit.SECONDS.sleep(10);
            if(message.getMessageProperties().getHeaders().get("error") == null){
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
                System.out.println("消息已经确认");
            }else {
                //channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
                System.out.println("消息拒绝");
            }

        });
        return container;
    }
}

总结

AcknowledgeMode.NONE:自动确认,等效于autoAck=true
AcknowledgeMode.MANUAL:手动确认,等效于autoAck=false,此时如果要实现ack和nack回执的话,使用ChannelAwareMessageListener监听器处理。

AcknowledgeMode.AUTO的使用

我们发现AcknowledgeMode除了AcknowledgeMode.NONEAcknowledgeMode.MANUAL常量值之外还有一个AcknowledgeMode.AUTO的常量。

 
 

配置类

import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.ImmediateAcknowledgeAmqpException;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.MessageListener;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        container.setAcknowledgeMode(AcknowledgeMode.AUTO);
        container.setMessageListener((MessageListener) (message) -> {
            System.out.println("====接收到消息=====");
            System.out.println(new String(message.getBody()));
            //抛出NullPointerException异常则重新入队列
            //throw new NullPointerException("消息消费失败");
            //当抛出的异常是AmqpRejectAndDontRequeueException异常的时候,则消息会被拒绝,且requeue=false
            //throw new AmqpRejectAndDontRequeueException("消息消费失败");
            //当抛出ImmediateAcknowledgeAmqpException异常,则消费者会被确认
            throw new ImmediateAcknowledgeAmqpException("消息消费失败");

        });
        return container;
    }
}

应用启动类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        TimeUnit.SECONDS.sleep(100);
        System.out.println("message container startup");

        context.close();
    }
}

AcknowledgeMode.AUTO 根据方法的执行情况来决定是否确认还是拒绝(是否重新入queue)

  • 如果消息成功被消费(成功的意思就是在消费的过程中没有抛出异常),则自动确认。

1)当抛出AmqpRejectAndDontRequeueException异常的时候,则消息会被拒绝,且requeue=false(不重新入队列)
2)当抛出ImmediateAcknowledgeAmqpException异常,则消费者会被确认
3)其他的异常,则消息会被拒绝,且requeue=true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过setDefaultRequeueRejected(默认是true)去设置,

源码分析

org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainerdoReceiveAndExecute方法,

private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Throwable { //NOSONAR

    Channel channel = consumer.getChannel();

    for (int i = 0; i < this.txSize; i++) {

        logger.trace("Waiting for message from consumer.");
        Message message = consumer.nextMessage(this.receiveTimeout);
        if (message == null) {
            break;
        }
        try {
           //具体的逻辑,具体执行Listener
            executeListener(channel, message);
        }
        //当ImmediateAcknowledgeAmqpException异常的时候打印日志然后直接break
        catch (ImmediateAcknowledgeAmqpException e) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("User requested ack for failed delivery: "
                        + message.getMessageProperties().getDeliveryTag());
            }
            break;
        }
        catch (Throwable ex) { //NOSONAR
            if (causeChainHasImmediateAcknowledgeAmqpException(ex)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("User requested ack for failed delivery: "
                            + message.getMessageProperties().getDeliveryTag());
                }
                break;
            }
            if (this.transactionManager != null) {
                if (this.transactionAttribute.rollbackOn(ex)) {
                    RabbitResourceHolder resourceHolder = (RabbitResourceHolder) TransactionSynchronizationManager
                            .getResource(getConnectionFactory());
                    if (resourceHolder != null) {
                        consumer.clearDeliveryTags();
                    }
                    else {
                        /*
                         * If we don't actually have a transaction, we have to roll back
                         * manually. See prepareHolderForRollback().
                         */
                        consumer.rollbackOnExceptionIfNecessary(ex);
                    }
                    throw ex; // encompassing transaction will handle the rollback.
                }
                else {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("No rollback for " + ex);
                    }
                    break;
                }
            }
            else {
               //进入这边
                consumer.rollbackOnExceptionIfNecessary(ex);
                throw ex;
            }
        }
    }

    return consumer.commitIfNecessary(isChannelLocallyTransacted(channel));

}

进入rollbackOnExceptionIfNecessary方法

public void rollbackOnExceptionIfNecessary(Throwable ex) throws Exception {

  //当ack机制为AUTO的时候
    boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
    try {
        if (this.transactional) {
            if (logger.isDebugEnabled()) {
                logger.debug("Initiating transaction rollback on application exception: " + ex);
            }
            RabbitUtils.rollbackIfNecessary(this.channel);
        }
        if (ackRequired) {
           //是否入队列,shouldRequeue就是具体的入队列和不入队列的判断
            boolean shouldRequeue = RabbitUtils.shouldRequeue(this.defaultRequeuRejected, ex, logger);
            for (Long deliveryTag : this.deliveryTags) {
                // With newer RabbitMQ brokers could use basicNack here...
                //执行拒绝策略
                this.channel.basicReject(deliveryTag, shouldRequeue);
            }
            if (this.transactional) {
                // Need to commit the reject (=nack)
                RabbitUtils.commitIfNecessary(this.channel);
            }
        }
    }
    catch (Exception e) {
        logger.error("Application exception overridden by rollback exception", ex);
        throw e;
    }
    finally {
        this.deliveryTags.clear();
    }
}

是否入队列的判断(shouldRequeue

public static boolean shouldRequeue(boolean defaultRequeueRejected, Throwable throwable, Log logger) {
    boolean shouldRequeue = defaultRequeueRejected ||
            throwable instanceof MessageRejectedWhileStoppingException;
    Throwable t = throwable;
    while (shouldRequeue && t != null) {
       //如果抛出的异常是AmqpRejectAndDontRequeueException的时候,不入队列
        if (t instanceof AmqpRejectAndDontRequeueException) {
            shouldRequeue = false;
        }
        t = t.getCause();
    }
    if (logger.isDebugEnabled()) {
        logger.debug("Rejecting messages (requeue=" + shouldRequeue + ")");
    }
    return shouldRequeue;
}

container.setDefaultRequeueRejected(false);,那么消息就不会重新入队列,只会拒绝一次。

@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames("zhihao.miao.order");
    //自动确认涉及到一个问题就是如果在消息消息的时候抛出异常,消息处理失败,但是因为自动确认而server将该消息删除了。
    //NONE表示自动确认
    container.setAcknowledgeMode(AcknowledgeMode.AUTO);
    container.setDefaultRequeueRejected(false);
    container.setMessageListener((MessageListener) (message) -> {
        System.out.println("====接收到消息=====");
        System.out.println(new String(message.getBody()));
        throw new NullPointerException("消息消费失败");
        //throw new AmqpRejectAndDontRequeueException("消息消费失败");
        //throw new ImmediateAcknowledgeAmqpException("消息消费失败");

    });
    return container;
}

使用@RabbitListener注解监听队列

设置确认模式是通过在容器中设置RabbitListenerContainerFactory实例的setAcknowledgeMode方法来设定。

配置:

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConsumerConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        //默认的确认模式是AcknowledgeMode.AUTO
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        return factory;
    }
}

处理器:

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

@Component
public class MessageHandler {

    @RabbitListener(queues ="zhihao.miao.order")
    public void handleMessage(byte[] bytes, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        System.out.println("====消费消息===handleMessage");
        System.out.println(new String(bytes));
        channel.basicAck(tag,false);
    }
}

应用启动类:

import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@EnableRabbit
@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("rabbit service startup");
        TimeUnit.SECONDS.sleep(3000);
        context.close();
    }
}

可靠消息总结

实际使用mq的实例,每段时间定期的给经常订早餐的推送短信(上新品)。
登录短信(也是使用消息中间件)
下单的时候,使用消息中间件发送到配送系统(消息不能丢失)。

做到消息不能丢失,我们就要实现可靠消息,做到这一点,我们要做到下面二点:

一:持久化
1: exchange要持久化
2: queue要持久化
3: message要持久化
二:消息确认
1: 启动消费返回(@ReturnList注解,生产者就可以知道哪些消息没有发出去)
2:生产者和Server(broker)之间的消息确认。
3: 消费者和Server(broker)之间的消息确认。

对于重要的消息,要结合本地的消息表才能上生产。

-------------------------------------------------------------------------------------------------

Alternate Exchange

Rabbitmq自己扩展的功能,不是AMQP协议定义的。
Alternate Exchange属性的作用,创建Exchange指定该ExchangeAlternate Exchange属性,发送消息的时候根据route key并没有把消息路由到队列中去,这就会将此消息路由到Alternate Exchange属性指定的Exchange上了。

 
创建一个Fanout类型的Exchange

自动声明带有Alternate Exchange的Exchange,

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public Exchange exchange(){
        Map<String,Object> argsMap = new HashMap<>();
        argsMap.put("alternate-exchange","zhihao.miao.exchange.order");
        return new TopicExchange("zhihao.miao.exchange.pay",true,false,argsMap);
    }


     @Bean
    public Binding binding(){
        return new Binding("zhihao.miao.pay",Binding.DestinationType.QUEUE,"zhihao.miao.exchange.pay","zhihao.miao.pay.*",new HashMap<>());
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

应用启动类

import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        //使得客户端第一次连接rabbitmq
        context.getBean(RabbitAdmin.class).getQueueProperties("**");
        context.close();
    }
}

启动应用启动类之后生成一个带有alternate-exchange属性的Exchange

 
生成了一个带有alternate-exchange属性的Exchange

zhihao.miao.exchange.pay是个包含alternate-exchange属性的topic类型的exchange(route key是zhihao.miao.pay.*,队列名是zhihao.miao.pay),alternate-exchange属性指定的是fanout类型的exchange,exchange的名称是zhihao.miao.exchange.order(绑定到zhihao.miao.order队列)

如果正确的路由(符合zhihao.miao.pay.*)规则,则zhihao.miao.pay队列接收到消息。如果是不正确的路由(不符合zhihao.miao.pay.*)规则,则路由到zhihao.miao.exchange.pay Exchange指定的alternate-exchange属性的Exchange中。

测试

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

启动应用类:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("zhihao.miao.exchange.pay","zhihao.miao.pay.aaa",message);

        TimeUnit.SECONDS.sleep(3);

        context.close();
    }
}

此时发送消息到名为zhihao.miao.exchange.payExchange,而Route keyzhihao.miao.pay.aaa,所以能正确地路由到zhihao.miao.pay队列中。

 
 

当指定的Route key不能正确的路由的时候,则直接发送到名为zhihao.miao.exchange.orderExchange,而因为我们定义的Exchange类型是fanout类型,所以就能路由到zhihao.miao.order队列中了。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        //此时route不到,那么就路由到alternate-exchange的属性配置的exchage
        rabbitTemplate.send("zhihao.miao.exchange.pay","hehe.zhihao.miao",message);

        TimeUnit.SECONDS.sleep(3);

        context.close();
    }
}

一般alternate-exchange属性的值最好是fanout类型的exchange,否则还会根据route keyalternate-exchange属性的exchange进行匹配再去路由。而如果指定了fanout类型的exchange,不需要去匹配routekey

alternate-exchange配置的Exchange也不能正确路由

示列说明

创建了一个topic类型的Exchange带有alternate-exchange属性,其alternate-exchangeexchange也是topic类型的exchange,如果消息的route key既不能,这个消息就会丢失。可以触发publish confirm机制,表示这个消息没有确认。

 
创建Exchange
 
binding关系
 
binding关系

配置:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

正常路由到Exchange名为head.info路由的队列中。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        //正确路由到header.info队列
        rabbitTemplate.send("head.info","head.info.a",message);

        TimeUnit.SECONDS.sleep(3);

        context.close();
    }
}

路由到Exchange名为head.info指定的alternate-exchange配置的head.error所路由的队列中。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        //正确路由到header.info队列
        rabbitTemplate.send("head.info","head.error.a",message);

        TimeUnit.SECONDS.sleep(3);

        context.close();
    }
}

二者都不符合则消息丢失,可以使用publish confirm来做生产端的消息确认,因为消息没有正确路由到队列,所以触发了return method。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        System.out.println(rabbitTemplate);

        byte[] body = "hello".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        //正确路由到header.info队列
        rabbitTemplate.send("head.info","header.debug.a",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

配置:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        factory.setPublisherReturns(true);
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            System.out.println("===========消息无法被路由=========");
            System.out.println("replyCode: "+replyCode);
            System.out.println("replyText: "+replyText);
            System.out.println("exchange: "+exchange);
            System.out.println("routingKey: "+routingKey);
        });
        return rabbitTemplate;
    }
}

总结

  • 建议Alternate Exchange的类型是fanout,防止出现路由失败。
    fanout exchange一般不需要指定Alternate Exchange属性。
  • 如果一个Exchange指定了Alternate Exchange,那就意味着,当ExchangeAlternate Exchange都无法路由的时候,才会触发return method

-------------------------------------------------------------------------------------------------

TTL

RabbitMQ allows you to set TTL (time to live) for both messages and queues. This can be done using optional queue arguments or policies (the latter option is recommended). Message TTL can be enforced for a single queue, a group of queues or applied for individual messages.
RabbitMQ允许您为消息和队列设置TTL(生存时间)。 可以使用可选的队列参数或策略完成(推荐使用后一个选项)。 可以为单个队列,一组队列或单个消息应用消息TTL。

Message TTL

 
往默认的AMQP default Exchange中发送消息
 
存活时期
 
30s之后,没做任何操作该消息被删除了

代码实现
应用启动类:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");
        //设置消息的过期时间
        messageProperties.setExpiration("30000");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("","weixin",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

配置

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://wangchao:123456@120.27.200.171:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

Queue TTL

 
创建ttl属性的队列
 
发送消息
 
 
 
30s没消费,消息删除

代码实现
自动声明一个队列名为email的,x-message-ttl属性为30000的队列。

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://wangchao:123456@120.27.200.171:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public Queue queue(){
        Map<String, Object> arguments = new HashMap<>();
        arguments.put("x-message-ttl",30000);
        return new Queue("email",true,false,false,arguments);
    }
}

应用启动类:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("","email",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

如果同时制定了Message TTL,Queue TTL,则小的那个时间生效。

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");
        //设置消息的过期时间
        messageProperties.setExpiration("10000");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("","email",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

我指定的消息过期时间为10s,Queue指定的为30s,时间短的10s生效。

-------------------------------------------------------------------------------------------------

Queue Length Limit

 
创建一个x-max-length参数值为5的队列

测试队列中最多只有5个消息,当第六条消息发送过来的时候,会删除最早的那条消息。队列中永远只有5条消息。

 
创建x-max-length-bytes属性的queue

往这个队列发送消息,第一条消息为11,第二条为2222,第三条市3333,然后再发送的话就会将最先入队列的第一条消息删除,如果删除之后还是不够存储新的消息,依次删除第二个消息,循环如此。

使用代码声明含有x-max-lengthx-max-length-bytes属性的队列

配置类

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public Queue queue1(){
       Map<String, Object> arguments = new HashMap<>();
       //表示队列中最多存放三条消息
       arguments.put("x-max-length",3);
        return new Queue("weixin",true,false,false,arguments);
    }

    @Bean
    public Queue queue2(){
        Map<String, Object> arguments = new HashMap<>();
        //表示队列中最多存放三条消息
        arguments.put("x-max-length-bytes",10);
        return new Queue("eamil",true,false,false,arguments);
    }
}

应用启动类

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("","weixin",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

启动程序发现管控台生成了具有这二种属性的队列。

 
管控台

总结

Max length(x-max-length) 用来控制队列中消息的数量。
如果超出数量,则先到达的消息将会被删除掉。

Max length bytes(x-max-length-bytes) 用来控制队列中消息总的大小。
如果超过总大小,则最先到达的消息将会被删除,直到总大小不超过x-max-length-byte为止。

-------------------------------------------------------------------------------------------------

Dead Letter Exchange

在队列上指定一个Exchange,则在该队列上发生如下情况,
1.消息被拒绝(basic.reject or basic.nack),且requeue=false
2.消息过期而被删除(TTL)
3.消息数量超过队列最大限制而被删除
4.消息总大小超过队列最大限制而被删除

就会把该消息转发到指定的这个exchange
同时也可以指定一个可选的x-dead-letter-routing-key,表示默认的routing-key,如果没有指定,则使用消息的routeing-key(也跟指定的exchange有关,
如果是Fanout类型的exchange,则会转发到所有绑定到该exchange的所有队列)。

拒绝消息或者nack

示列

定义一个队列zhihao.miao.order,其有属性x-dead-letter-exchangezhihao.miao.exchange.pay,往Exchange名为zhihao.miao.exchange.order中发送消息。

zhihao.miao.order队列中发送消息,

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello,zhihao.miao".getBytes();

        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("json");

        Message message = new Message(body,messageProperties);

        rabbitTemplate.send("zhihao.miao.exchange.order","zhihao.miao.order",message);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

配置类:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

}

此时消息端拒绝消费这个消息

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        System.out.println(rabbitTemplate);

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

配置类

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }


    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        container.setDefaultRequeueRejected(false);
        //手动确认
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setMessageListener(new ChannelAwareMessageListener(){
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                System.out.println("=====rece msg======");
                System.out.println(new String(message.getBody()));
                //执行拒绝消息
                channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
            }
        });
        return container;
    }
}

因为其指定了x-dead-letter-exchangezhihao.miao.exchange.pay,所以会将消息转发到zhihao.miao.exchange.pay,而因为没有指定x-dead-letter-routing-key,所以会使用默认的发送的消息的route key(zhihao.miao.order)进行路由,而我们zhihao.miao.exchange.pay的路由信息如下,所以会将消息转发到zhihao.miao.auto队列中去。

 
zhihao.miao.exchange.pay的路由信息
 
发送二条消息,执行了拒绝策略,所以消息转发到了zhihao.miao.auto队列中

示列2

定义了队列zhihao.miao.order,不仅定义了x-dead-letter-exchange属性,也指定了x-dead-letter-routing-key属性

 
队列zhihao.miao.order定义
 
zhihao.miao.exchange.pay的路由信息

显而易见当拒绝了该消息的时候就会转发到了zhihao.miao.exchange.pay,而应该该队列指定了route key为zhihao.miao.pay,所以转发到了zhihao.miao.pay队列中去了。

代码很上面的一样。

总结

上面的示列展示了当定义队列时指定了x-dead-letter-exchangex-dead-letter-routing-key视情况而定),并且消费端执行拒绝策略的时候将消息路由到指定的Exchange中去。我们知道还有二种情况会造成消息转发到死信队列。
一种是消息过期而被删除,可以使用这个方式使的rabbitmq实现延迟队列的作用。还有一种就是消息数量超过队列最大限制而被删除或者消息总大小超过队列最大限制而被删除

-------------------------------------------------------------------------------------------------

优先级队列(priority queue)

 
创建具有优先级属性的队列

示列

生产端:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    private static MessageProperties getmessageProperties(){
        int priority = new Random().nextInt(5);
        System.out.println("====优先级==="+priority);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text");
        messageProperties.setPriority(priority);
        return messageProperties;

    }
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello world".getBytes();

        //一次性发送10条消息,优先级分别是1到10
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.send("","zhihao.miao.user",new Message(body,getmessageProperties()));
        }

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

配置:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

}

控制台打印:

====优先级===1
十月 29, 2017 2:12:52 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory createBareConnection
信息: Created new connection: connectionFactory#1184ab05:0/SimpleConnection@6a400542 [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 52105]
====优先级===1
====优先级===4
====优先级===3
====优先级===3
====优先级===1
====优先级===3
====优先级===2
====优先级===1
====优先级===4

消费端进行消费,首先看管控台,


 
控制台中get是按照顺序进行获取到的

代码消费呢?
应用启动类:

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        System.out.println(rabbitTemplate);
        TimeUnit.SECONDS.sleep(40);
        context.close();
    }
}

配置

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }


    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.user");
        container.setDefaultRequeueRejected(false);
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        container.setMessageListener(new ChannelAwareMessageListener(){
            @Override
            public void onMessage(Message message, Channel channel) throws Exception {
                System.out.println("=====消费消息======");
                System.out.println("消息的优先级是:"+message.getMessageProperties().getPriority()+
                        " 消息内容是:"+new String(message.getBody()));
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            }
        });
        return container;
    }
}
信息: Created new connection: connectionFactory#687681be:0/SimpleConnection@61b4ee6c [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 52155]
=====消费消息======
消息的优先级是:4消息内容是:hello world
org.springframework.amqp.rabbit.core.RabbitTemplate@7193666c
=====消费消息======
消息的优先级是:4消息内容是:hello world
=====消费消息======
消息的优先级是:3消息内容是:hello world
=====消费消息======
消息的优先级是:3消息内容是:hello world
=====消费消息======
消息的优先级是:3消息内容是:hello world
=====消费消息======
消息的优先级是:2消息内容是:hello world
=====消费消息======
消息的优先级是:1消息内容是:hello world
=====消费消息======
消息的优先级是:1消息内容是:hello world
=====消费消息======
消息的优先级是:1消息内容是:hello world
=====消费消息======
消息的优先级是:1消息内容是:hello world

很明显也是按照优先级顺序来消费的。

示列2

如果我们设置的发送消息的优先级都高于队列zhihao.miao.order设置的x-max-priority属性呢?

@ComponentScan
public class Application {

    private static MessageProperties getmessageProperties(){
        int priority = new Random().nextInt(5)+10;
        System.out.println("=====优先级==="+priority);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text");
        messageProperties.setPriority(priority);
        return messageProperties;

    }
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello world".getBytes();

        //一次性发送10条消息,优先级分别是1到10
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.send("","zhihao.miao.order",new Message(body,getmessageProperties()));
        }

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

客户端发送的消息的优先级,控制台打印出:

====优先级===13
十月 29, 2017 2:25:36 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory createBareConnection
信息: Created new connection: connectionFactory#1184ab05:0/SimpleConnection@6a400542 [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 52235]
====优先级===14
====优先级===14
====优先级===13
====优先级===11
====优先级===10
====优先级===10
====优先级===12
====优先级===14
====优先级===12

消费端代码和上面一样,执行程序,验证消费消息顺序

=====消费消息======
消息的优先级是:13 消息内容是:hello world
=====消费消息======
消息的优先级是:14 消息内容是:hello world
org.springframework.amqp.rabbit.core.RabbitTemplate@7193666c
=====消费消息======
消息的优先级是:14 消息内容是:hello world
=====消费消息======
消息的优先级是:13 消息内容是:hello world
=====消费消息======
消息的优先级是:11 消息内容是:hello world
=====消费消息======
消息的优先级是:10 消息内容是:hello world
=====消费消息======
消息的优先级是:10 消息内容是:hello world
=====消费消息======
消息的优先级是:12 消息内容是:hello world
=====消费消息======
消息的优先级是:14 消息内容是:hello world
=====消费消息======
消息的优先级是:12 消息内容是:hello world

发现没有严格的顺序,验证了如果设置的优先级大于队列设置的x-max-priority属性,则优先级失效。

发送消息之后可以通过http监控可以看到消息的详情:

http://192.168.1.131:15672/api/queues/%2F/zhihao.miao.user(/api/queues/vhost/name)
 
队列的信息详情

示列3

如果生产端发送很慢,消费者消息很快,则有可能不会严格的按照优先级来进行消费。

生产端每隔3s钟发送一条消息,很明显消费端消费也是按照发送的顺序。

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {

    private static MessageProperties getmessageProperties(){
        int priority = new Random().nextInt(5);
        System.out.println("====优先级==="+priority);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text");
        messageProperties.setPriority(priority);
        return messageProperties;

    }
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        byte[] body = "hello world".getBytes();

        //一次性发送10条消息,优先级分别是1到10
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.send("","zhihao.miao.user",new Message(body,getmessageProperties()));
            TimeUnit.SECONDS.sleep(3);
        }

        TimeUnit.SECONDS.sleep(30);

        context.close();
    }
}

我们发现生产端生产的顺序和消费端消费的消息都是一致的。

总结:

  • 创建优先级队列,需要增加x-max-priority参数,指定一个数字。表示最大的优先级,建议优先级设置为1~10之间。
  • 发送消息的时候,需要设置priority属性,最好不要超过上面指定的最大的优先级。
  • 如果生产端发送很慢,消费者消息很快,则有可能不会严格的按照优先级来进行消费。

第一,如果发送的消息的优先级属性小于设置的队列属性x-max-priority值,则按优先级的高低进行消费,数字越高则优先级越高。
第二,如果发送的消息的优先级属性都大于设置的队列属性x-max-priority值,则设置的优先级失效,按照入队列的顺序进行消费。
第三,如果消费端一直进行监听,而发送端一条条的发送消息,优先级属性也会失效。

RabbitMQ不能保证消息的严格的顺序消费。

-------------------------------------------------------------------------------------------------

异步RPC(Remote procedure call)

 
模型图

Server:提供服务的服务,即RPC模型中的Server。
Client:调用服务的服务,即RPC模型中的client。

Client发送消息给服务端,消息中包含二个属性,一个是reply_to属性(value是一个队列名,客户端服务一直监听这个队列),一个是correkation_id(本次请求的唯一标识),发送消息调用Server服务之后,服务端将调用结果发送到reply_to指定的队列中。当前也会根据客户端得到correlation_id,并指明这次结果与correlation_id对应。

java client实现RPC功能

服务端代码,监听sms队列(客户端请求消息发送到的队列)

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

public class Consume {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        System.out.println(channel.queueDeclare().getQueue());

        channel.basicConsume("sms",true,new SimpleConsumer(channel));
        System.out.println("短信服务已经启动");
        TimeUnit.SECONDS.sleep(60);

        channel.close();
        connection.close();
    }
}

接收到客户端的消息之后,调用服务接口sendSMS方法,然后得到请求消息中的reply_tocorrelation_id属性,将调用接口的结果发送到reply_to属性的队列中,指定correlation_id属性。

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.zhihao.miao.rpc.server.SendSMSTool;

import java.io.IOException;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("进入RPC方法调用");
        String phone =properties.getHeaders().get("phone").toString();
        String content = new String(body);
        //调用服务
        boolean result = SendSMSTool.sendSMS(phone,content);
        System.out.println("消息处理成功");

        String reply = properties.getReplyTo();
        String id = properties.getCorrelationId();

        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().correlationId(id).build();
        this.getChannel().basicPublish("",reply,props,(result+"").getBytes());
        System.out.println("消息回复成功");
    }
}

服务接口:

public class SendSMSTool {

    public static boolean sendSMS(String phone,String content){
        System.out.println("发送短信内容:【"+content+"】到手机号:"+phone);
        return phone.length() > 6;
    }
}

总结
RPC Server步骤
1.创建服务
2.监听一个队列(sms),监听客户端发送的消息
3.收到消息之后,调用服务,得到调用结果
4.从消息属性中,获取reply_to, correlation_id属性,把调用结果发送给reply_to指定的队列中,发送的消息属性要带上reply_to。
5.一次调用处理成功

 
Exchange绑定sms队列

客户端代码
消息端先监听一个队列sms.reply,这个队列是客户端返回结果的队列,然后发送消息指定请求参数(参数头和参数内容),指定correlationIdreplyTo属性。

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class Send {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        String correlationId = UUID.randomUUID().toString();
        String replyTo = "sms.reply";
        //自动删除属性为true,当connection关闭后会自动删除该队列
        //channel.queueDeclare(replyTo,true,true,true,new HashMap<>());

        channel.basicConsume(replyTo,true,new SimpleConsumer(channel));

        Map<String,Object> headers = new HashMap<>();
        headers.put("phone","15790934342");

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().headers(headers).replyTo(replyTo).correlationId(correlationId).deliveryMode(2).
                contentEncoding("UTF-8").build();

        channel.basicPublish("send","sms",true,properties,"周年庆6折大促销,只剩三天。详情登录****,去了解详情。".getBytes());

        TimeUnit.SECONDS.sleep(20);

        channel.close();
        connection.close();
    }
}

接收到rpc调用返回接口的处理逻辑,

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;


public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("====收到RPC调用 回复了=====");
        System.out.println(properties.getCorrelationId()+",短信发送结果:"+new String(body));


    }
}

总结
RPC Client步骤:
1.监听reply_to对应的队列(RPC调用结果发送指定的队列)
2.发送消息,消息属性需要带上reply_to,correlation_id属性
3.服务端处理完成之后,reply_to对应的队列就会收到异步处理结果消息
4.收到消息之后,进行处理,根据消息属性的correlation_id找到对应的请求
5.一次客户端调用就完成了。

说一下逻辑:
就是客户端往一个队列sms发送消息,服务端监听这个队列,接收到消息之后服务端在消息处理方法中调用服务,并把调用的服务结果返回给客户端,怎么返回呢(通过往一个队列里发送(sms.reply),并把这次调用的id也返回给客户端,这个队列名是reply_to属性,当前的返回id是correlation_id,都是在客户端往sms队列发送消息的时候带到服务端的),服务端将返回的结果发送到指定的队列,而客户端一直在监听这个队列拿到返回值,即完成了一次异步的rpc调用。

Direct reply-to

Improve performance and simplicity of RPC clients by sending replies direct to a waiting channel.
通过直接将RPC的回复发送到等待的Channel中而提供RPC客户端的性能。

动机(原因)
RPC is a popular pattern to implement with a messaging broker like RabbitMQ. The typical way to do this is for RPC clients to send requests to a long lived server queue. The RPC server(s) read requests from this queue and then send replies to each client using the queue named by the client in the reply-to header.

But where does the client's queue come from? The client can declare a single-use queue for each request-response pair. But this is inefficient; even a transient unmirrored queue can be expensive to create and then delete (compared with the cost of sending a message). This is especially true in a cluster as all cluster nodes need to agree that the queue has been created, even if it is unmirrored.

So alternatively the client can create a long-lived queue for its replies. But this can be fiddly to manage, especially if the client itself is not long-lived.

The direct reply-to feature allows RPC clients to receive replies directly from their RPC server, without going through a reply queue. ("Directly" here still means going through AMQP and the RabbitMQ server; there is no separate network connection between RPC client and RPC server.)

大意就是使用RabbitMQ实现异步RPC,通常的做法就是客户端将请求发送到长期存在的服务器队列。RPC服务器读取接收此队列的请求,然后将RPC调用结果返回到客户端在发送请求时请求头中的reply_to属性中的队列。完成一次异步的RPC调用。
那么请求头中的队列哪里来呢?客户端可以声明一个针对每一对请求的临时队列。但是开销太大(相比于发送消息本身),在集群环境中更是如此。
如果创建一个长期的队列呢?如果客户端本身并不是长期的那么这个长期的队列就很难管理。

Direct reply-to就是解决这个问题的,不需要创建这样一个回复的队列。

示列

服务端
服务启动类,监听sms队列

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.util.concurrent.TimeUnit;

public class Consume {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        System.out.println(channel.queueDeclare().getQueue());

        channel.basicConsume("sms",true,new SimpleConsumer(channel));
        System.out.println("短信服务已经启动");
        TimeUnit.SECONDS.sleep(60);

        channel.close();
        connection.close();
    }
}

服务端监听消息处理器,接收到请求参数,调用服务接口,返回参数

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body) throws IOException {
        System.out.println("进入RPC方法调用");
        String phone =properties.getHeaders().get("phone").toString();
        String content = new String(body);
        //调用服务
        boolean result = SendSMSTool.sendSMS(phone,content);
        System.out.println("消息处理成功");

        String reply = properties.getReplyTo();
        String id = properties.getCorrelationId();

        BasicProperties props = new BasicProperties.Builder().correlationId(id).build();
        this.getChannel().basicPublish("",reply,props,(result+"").getBytes());
        System.out.println("消息回复成功");
    }
}

服务接口

public class SendSMSTool {

    public static boolean sendSMS(String phone,String content){
        System.out.println("发送短信内容:【"+content+"】到手机号:"+phone);
        return phone.length() > 6;
    }
}

客户端
改变的是客户端代码,直接定义replyToamq.rabbitmq.reply-to

import com.rabbitmq.client.*;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class Send {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();

        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        String correlationId = UUID.randomUUID().toString();

        String replyTo ="amq.rabbitmq.reply-to";

        channel.basicConsume(replyTo,true,new SimpleConsumer(channel));

        Map<String,Object> headers = new HashMap<>();
        headers.put("phone","15790934342");

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().headers(headers).replyTo(replyTo).correlationId(correlationId).deliveryMode(2).
                contentEncoding("UTF-8").build();

        channel.basicPublish("send","sms",true,properties,"周年庆6折大促销,只剩三天。详情登录****,去了解详情。".getBytes());

        TimeUnit.SECONDS.sleep(20);

        channel.close();
        connection.close();
    }
}

定义消息监听消费者

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class SimpleConsumer extends DefaultConsumer {

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("====收到RPC调用恢复了=====");
        System.out.println(properties.getCorrelationId()+",短信发送结果:"+new String(body));


    }
}

管控台中也不会创建amq.rabbitmq.reply-to这个队列,而是直接将返回的消息发到等到的Channel中,提升异步rpc调用的性能,amq.rabbitmq.reply-to也叫做伪队列。

-------------------------------------------------------------------------------------------------

使用Spring AMQP实现RPC异步调用

示列

服务器端

应用启动类代码,

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("===server startup======");
        TimeUnit.SECONDS.sleep(120);
        context.close();
    }
}

配置类:
监听了sms队列,这个队列将会是客户端请求消息发送到的队列,配置了适配器,适配器中去调用服务,适配器返回的值就是服务端返回给客户端的RPC调用的结果

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("sms");
        container.setAcknowledgeMode(AcknowledgeMode.NONE);
        //使用适配器的方式
        container.setMessageListener(new MessageListenerAdapter(new SendSMSHandler()));
        return container;
    }
}

处理器,处理器中调用具体的服务,我们此列子中处理器方法返回的值是boolean类型

import java.util.concurrent.TimeUnit;

public class SendSMSHandler {

    public boolean handleMessage(byte[] body){
        String _body = new String(body);
        System.out.println(_body);
        String[] sms = _body.split(":");
        String phone = sms[0];
        String content = sms[1];

        boolean is = SendSMSTool.sendSMS(phone,content);

        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return is;
    }
}

服务接口

public class SendSMSTool {

    public static boolean sendSMS(String phone,String content){
        System.out.println("发送短信内容:【"+content+"】到手机号:"+phone);
        return phone.length() > 6;
    }
}

服务端步骤

  1. 消息处理方法,一定要有返回值,这个返回值就是就是server回复客户端的结果。比如我们SendSMSHandler.handleMessage方法返回的值。

客户端
应用启动类:

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        //设置超时时间,单位是ms
        rabbitTemplate.setReplyTimeout(10000);

        String phone = "15634344321";
        String content ="周年庆,五折优惠";

        MessageProperties messageProperties = new MessageProperties();
        Message message = new Message((phone+":"+content).getBytes(),messageProperties);

        //rabbitTemplate.send("","sms",message);

        Message reply = rabbitTemplate.sendAndReceive("","sms",message,
                new CorrelationData(UUID.randomUUID().toString()));

        System.out.println(reply);
        System.out.println("message,body:"+new String(reply.getBody()));
        System.out.println("message,properties:"+reply.getMessageProperties());

        TimeUnit.SECONDS.sleep(30);
        context.close();
    }
}

配置类代码:

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

如果服务端睡眠6s,则客户端通过sendAndReceive方法接收到的Message对象为空,怎样设置呢?
客户端通过设置rabbitTemplate.setReplyTimeout(10000);就可以了。

客户端步骤

  1. 使用sendAndReceive方法发送消息,该方法返回一个Message对象,该对象就是server返回的结果
  2. sendAndReceive如果超过5s还没有收到结果,则返回null,这个超时时间可以通过rabbitTemplate.setReplyTimeout()来进行设置
  3. server端返回的结果一定要注意,和MessageConverter有关,默认的org.springframework.amqp.support.converter.SimpleMessageConverter会把基本的数据类型转换成Serializable对象,这样的话,client端接收的也是序列化的java对象,所以,需要合理设置MessageConverter

示列代码中服务端返回给客户端的是Boolean类型,

启动服务端客户端代码:
服务器打印控制台打印:

15634344321:周年庆,五折优惠
发送短信内容:【周年庆,五折优惠】到手机号:15634344321

客户端控制台打印:

message,body:����sr��java.lang.Boolean� r�՜�����Z��valuexp�
message,properties:MessageProperties [headers={}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=application/x-java-serialized-object, contentEncoding=null, contentLength=0, deliveryMode=null, receivedDeliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=amq.rabbitmq.reply-to.g2dkAB5yYWJiaXRAaVpicDFqY3d4N3NmYjFud3pyZWh5NloAAFu0AAAABwI=.kHL9zxtdQmtcxl0mQF8zrg==, receivedDelay=null, deliveryTag=1, messageCount=null, consumerTag=null, consumerQueue=null]

我们发现客户端接收到的数据乱码,将服务端的处理器的返回值改写成String类型的,

import java.util.concurrent.TimeUnit;

public class SendSMSHandler {

    public String handleMessage(byte[] body){
        String _body = new String(body);
        System.out.println(_body);
        String[] sms = _body.split(":");
        String phone = sms[0];
        String content = sms[1];

        boolean is = SendSMSTool.sendSMS(phone,content);

        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return is ? "success":"false";
    }
}

此时发现客户端接收的消息数据没有乱码,原因何在?我们总结一下就是服务器端处理器返回给客户端boolean类型,那么返回的消息数据就乱码,如果返回的是String类型,那么返回的消息数据就不会乱码。

之前我们学习了org.springframework.amqp.support.converter.MessageConverter接口,当客户端向服务端发送消息的时候会进行消息类型转换,调用了fromMessage方法,而当服务器返回给客户端的时候会将服务端的对象转换成Message对象,很明显调用的是toMessage方法。

我们知道org.springframework.amqp.support.converter.MessageConverter接口的默认实现是org.springframework.amqp.support.converter.SimpleMessageConverter,而toMessage方法的实现是在其继承的对象AbstractMessageConverter中,

我们看到其AbstractMessageConverter.toMessage方法的实现逻辑是:

    @Override
    public final Message toMessage(Object object, MessageProperties messageProperties)
            throws MessageConversionException {
        if (messageProperties == null) {
            messageProperties = new MessageProperties();
        }
        //将对象转换成Message对象
        Message message = createMessage(object, messageProperties);
        messageProperties = message.getMessageProperties();
        if (this.createMessageIds && messageProperties.getMessageId() == null) {
            messageProperties.setMessageId(UUID.randomUUID().toString());
        }
        return message;
    }

createMessage方法就是将对象转换成Message对象,

    /**
     * Creates an AMQP Message from the provided Object.
     */
    @Override
    protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
        byte[] bytes = null;
        if (object instanceof byte[]) {
            bytes = (byte[]) object;
            messageProperties.setContentType(MessageProperties.CONTENT_TYPE_BYTES);
        }
        else if (object instanceof String) {
            try {
                bytes = ((String) object).getBytes(this.defaultCharset);
            }
            catch (UnsupportedEncodingException e) {
                throw new MessageConversionException(
                        "failed to convert to Message content", e);
            }
            messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
            messageProperties.setContentEncoding(this.defaultCharset);
        }
        //因为boolean类型实现Serializable接口,所以会将其序列化
        else if (object instanceof Serializable) {
            try {
                bytes = SerializationUtils.serialize(object);
            }
            catch (IllegalArgumentException e) {
                throw new MessageConversionException(
                        "failed to convert to serialized Message content", e);
            }
            messageProperties.setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT);
        }
        if (bytes != null) {
            messageProperties.setContentLength(bytes.length);
        }
        return new Message(bytes, messageProperties);
    }

我们在程序中将序列化对象直接转换成字符串所以乱码,而返回的是String类型的情形的时候先将字符串转换成相应的字节数组,然后返回new Message(bytes, messageProperties);就不会乱码。

继续探讨,当我们服务端返回的是一个对象的时候,客户端会返回空
返回的对象:

public class SendStatus {

    private String phone;

    private String result;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
}

将该对象返回:

public class SendSMSHandler {

    public SendStatus handleMessage(byte[] body){
        String _body = new String(body);
        System.out.println(_body);
        String[] sms = _body.split(":");
        String phone = sms[0];
        String content = sms[1];

        boolean is = SendSMSTool.sendSMS(phone,content);
        SendStatus sendStatus = new SendStatus();
        sendStatus.setPhone(phone);
        sendStatus.setResult(is ? "SUCCESS":"FAILURE");
        return sendStatus;
    }
}

服务端控制台:

15634344321:周年庆,五折优惠
发送短信内容:【周年庆,五折优惠】到手机号:15634344321

客户端:

message,body:
message,properties:MessageProperties [headers={}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=application/octet-stream, contentEncoding=null, contentLength=0, deliveryMode=null, receivedDeliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=amq.rabbitmq.reply-to.g2dkAB5yYWJiaXRAaVpicDFqY3d4N3NmYjFud3pyZWh5NloAAFxBAAAABwI=.01fOGW/nvS2nz6gKza+cjg==, receivedDelay=null, deliveryTag=1, messageCount=null, consumerTag=null, consumerQueue=null]

原因何在,因为我们定义的SendStatus不走createMessage中的所有if分支,最后返回的是null,怎么解决呢,要么自己去定义一个org.springframework.amqp.support.converter.MessageConverter实现,要么换一个默认的org.springframework.amqp.support.converter.MessageConverter实现。

改造后的示列

使用AMQP自带的消息类型转换器Jackson2JsonMessageConverter
服务端

应用启动类,

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        System.out.println("===server startup======");
        TimeUnit.SECONDS.sleep(120);
        context.close();
    }
}

配置类,添加自定义的消息转换器

import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("sms");
        container.setAcknowledgeMode(AcknowledgeMode.NONE);
        //使用适配器的方式
        container.setMessageListener(new MessageListenerAdapter(new SendSMSHandler(),new Jackson2JsonMessageConverter()));
        return container;
    }
}

处理器handler,返回自定义的SendStatus类型

import java.util.concurrent.TimeUnit;

public class SendSMSHandler {

    public SendStatus handleMessage(byte[] body){
        String _body = new String(body);
        System.out.println(_body);
        String[] sms = _body.split(":");
        String phone = sms[0];
        String content = sms[1];

        boolean is = SendSMSTool.sendSMS(phone,content);
        SendStatus sendStatus = new SendStatus();
        sendStatus.setPhone(phone);
        sendStatus.setResult(is ? "SUCCESS":"FAILURE");

        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return sendStatus;
    }
}

接口服务,

public class SendSMSTool {

    public static boolean sendSMS(String phone,String content){
        System.out.println("发送短信内容:【"+content+"】到手机号:"+phone);
        return phone.length() > 6;
    }
}

服务端返回的对象,

public class SendStatus {
    private String phone;

    private String result;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
}

客户端
应用启动类,

import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@ComponentScan
public class Application {
    public static void main(String[] args) throws Exception{
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);

        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);

        //设置超时时间,单位是ms
        rabbitTemplate.setReplyTimeout(10000);

        String phone = "15634344321";
        String content ="周年庆,五折优惠";

        MessageProperties messageProperties = new MessageProperties();
        Message message = new Message((phone+":"+content).getBytes(),messageProperties);

        //rabbitTemplate.send("","sms",message);

        Message reply = rabbitTemplate.sendAndReceive("","sms",message,
                new CorrelationData(UUID.randomUUID().toString()));

        System.out.println(reply);
        System.out.println("message,body:"+new String(reply.getBody()));
        System.out.println("message,properties:"+reply.getMessageProperties());

        TimeUnit.SECONDS.sleep(30);
        context.close();
    }
}

配置类

import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory(){
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        return rabbitAdmin;
    }

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        return rabbitTemplate;
    }
}

启动服务器客户端,客户端返回

message,body:{"phone":"15634344321","result":"SUCCESS"}
message,properties:MessageProperties [headers={__TypeId__=rpc.server.SendStatus}, timestamp=null, messageId=null, userId=null, receivedUserId=null, appId=null, clusterId=null, type=null, correlationId=null, correlationIdString=null, replyTo=null, contentType=application/json, contentEncoding=UTF-8, contentLength=0, deliveryMode=null, receivedDeliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=false, receivedExchange=, receivedRoutingKey=amq.rabbitmq.reply-to

返回了SendStatus的JSON格式,因为使用了Jackson2JsonMessageConverter消息类型转换器。

-------------------------------------------------------------------------------------------------

异常处理

RabbitMQ java client中的异常处理

消费消息,在消费消息的时候抛出异常,

消费启动类:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.impl.DefaultExceptionHandler;

import java.util.concurrent.TimeUnit;

public class Consumer {
    public static void main(String[] args) throws Exception{

        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        Connection connection = connectionFactory.newConnection();


        Channel channel = connection.createChannel();

        System.out.println(channel.isOpen());

        channel.basicConsume("sms",true,new SimpleConsumer(channel));

        TimeUnit.SECONDS.sleep(20);
        System.out.println(channel.isOpen());

        channel.close();
        connection.close();

    }
}

消费逻辑

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;

import java.io.IOException;

public class SimpleConsumer extends DefaultConsumer{

    public SimpleConsumer(Channel channel){
        super(channel);
    }

    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println(consumerTag);
        System.out.println("-----收到消息了-----------");
        System.out.println("消息属性为:"+properties);
        System.out.println("消息内容为:"+new String(body));

        throw new NullPointerException("空指针异常");
    }
}

然后我们发现20s过后,发现控制台打印了

true
amq.ctag-fJ45VlTAV2aKO97-zztDNQ
-----收到消息了-----------
消息属性为:#contentHeader<basic>(content-type=null, content-encoding=null, headers={}, delivery-mode=1, priority=null, correlation-id=null, reply-to=null, expiration=null, message-id=null, timestamp=null, type=null, user-id=null, app-id=null, cluster-id=null)
消息内容为:第一条消息
Exception in thread "main" com.rabbitmq.client.AlreadyClosedException: channel is already closed due to clean channel shutdown; protocol method: #method<channel.close>(reply-code=200, reply-text=Closed due to exception from Consumer com.zhihao.test.day10.SimpleConsumer@2462e0fd (amq.ctag-fJ45VlTAV2aKO97-zztDNQ) method handleDelivery for channel AMQChannel(amqp://zhihao.miao@192.168.1.131:5672/,1), class-id=0, method-id=0)
    at com.rabbitmq.client.impl.AMQChannel.processShutdownSignal(AMQChannel.java:286)
false
    at com.rabbitmq.client.impl.ChannelN.startProcessShutdownSignal(ChannelN.java:282)
    at com.rabbitmq.client.impl.ChannelN.close(ChannelN.java:596)
    at com.rabbitmq.client.impl.ChannelN.close(ChannelN.java:530)
    at com.rabbitmq.client.impl.ChannelN.close(ChannelN.java:523)
    at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.close(AutorecoveringChannel.java:68)
    at com.zhihao.test.day10.Consumer.main(Consumer.java:31)

我们发现当消费端抛出异常的时候,channel会关闭,然后channel.close()会报错。原因是什么呢?

我们知道com.rabbitmq.client.ExceptionHandler这个接口,中定义了各个阶段异常的捕获方法。其默认实现com.rabbitmq.client.impl.DefaultExceptionHandler,继承com.rabbitmq.client.impl.StrictExceptionHandler,发现当消费失败的时候会kill掉channel。

源码如下:

@Override
    public void handleConsumerException(Channel channel, Throwable exception,
                                        Consumer consumer, String consumerTag,
                                        String methodName)
    {
        handleChannelKiller(channel, exception, "Consumer " + consumer
                                                        + " (" + consumerTag + ")"
                                                        + " method " + methodName
                                                        + " for channel " + channel);
    }

调用handleChannelKiller方法

  @Override
    protected void handleChannelKiller(Channel channel, Throwable exception, String what) {
        log(what + " threw an exception for channel " + channel, exception);
        try {
            channel.close(AMQP.REPLY_SUCCESS, "Closed due to exception from " + what);
        } catch (AlreadyClosedException ace) {
            // noop
        } catch (TimeoutException ace) {
            // noop
        } catch (IOException ioe) {
            log("Failure during close of channel " + channel + " after " + exception, ioe);
            channel.getConnection().abort(AMQP.INTERNAL_ERROR, "Internal error closing channel for " + what);
        }
    }

消息发送确认异常捕获:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.impl.DefaultExceptionHandler;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class Send {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");

        connectionFactory.setExceptionHandler(new DefaultExceptionHandler(){
            @Override
            public void handleConfirmListenerException(Channel channel, Throwable exception) {
                System.out.println("=====消息确认发生异常=======");
                exception.printStackTrace();
            }
        });


        Connection connection = connectionFactory.newConnection();

        Channel channel = connection.createChannel();

        channel.confirmSelect();

        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("收到消息确认,:"+deliveryTag);
                throw new IOException("数据库异常,确认失败");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {

            }
        });

        channel.basicPublish("","sms",null,"发送消息".getBytes());


        TimeUnit.SECONDS.sleep(20);

        channel.close();
        connection.close();


    }
}

Spring AMQP异常处理

设置AUTO确认的时候已经讲解了异常处理,这边主要讲解一下自动声明的时候的异常处理。

正常的情况下我们声明队列的代码:

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        return new RabbitAdmin(connectionFactory);
    }

    @Bean
    public Queue infoQueue(){
        Map<String,Object> arguments = new HashMap<>();
        return new Queue("info",true,false,false,arguments);
    }

    @Bean
    public Queue errorQueue(){
        Map<String,Object> arguments = new HashMap<>();
        return new Queue("error",true,false,false,arguments);
    }
}

应用启动类:

import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        //使得客户端第一次连接rabbitmq
        context.getBean(RabbitAdmin.class).getQueueProperties("**");
        context.close();
    }
}

执行声明成功,但是假如我们的info队列已经存在,并且属性和自动声明的不一致,那么就会抛出异常造成info声明不了,糟糕的是error队列也声明不了。

 
已经声明了info队列

此时执行发现抛出异常,并且声明不了队列。

控制台打印出异常堆栈信息:
信息: Starting beans in phase -2147482648
九月 29, 2017 11:49:30 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory createBareConnection
信息: Created new connection: connectionFactory#163e4e87:0/SimpleConnection@ef9296d [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 63190]
九月 29, 2017 11:49:30 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory log
严重: Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'info' in vhost '/': received none but current is the value 'zhihao.topic.exchange' of type 'longstr', class-id=50, method-id=10)
Exception in thread "main" org.springframework.amqp.AmqpIOException: java.io.IOException
    at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:71)
    at org.springframework.amqp.rabbit.connection.RabbitAccessor.convertRabbitAccessException(RabbitAccessor.java:113)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:1461)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1411)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1387)
    at org.springframework.amqp.rabbit.core.RabbitAdmin.initialize(RabbitAdmin.java:500)
    at org.springframework.amqp.rabbit.core.RabbitAdmin$11.onCreate(RabbitAdmin.java:419)
    at org.springframework.amqp.rabbit.connection.CompositeConnectionListener.onCreate(CompositeConnectionListener.java:33)
    at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:571)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:1430)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1411)
    at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1387)
    at org.springframework.amqp.rabbit.core.RabbitAdmin.getQueueProperties(RabbitAdmin.java:336)
    at com.zhihao.miao.exception.Application.main(Application.java:18)

怎样去解决呢?

修改配置类,设置忽略声明异常

import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class MQConfig {

    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setUri("amqp://zhihao.miao:123456@192.168.1.131:5672");
        return factory;
    }

    @Bean
    public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        //设置忽略声明异常
        rabbitAdmin.setIgnoreDeclarationExceptions(true);
        return rabbitAdmin;
    }

    @Bean
    public Queue infoQueue(){
        Map<String,Object> arguments = new HashMap<>();
        return new Queue("info",true,false,false,arguments);
    }

    @Bean
    public Queue errorQueue(){
        Map<String,Object> arguments = new HashMap<>();
        return new Queue("error",true,false,false,arguments);
    }
}

重新启动应用启动类,

import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class Application {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        //使得客户端第一次连接rabbitmq
        context.getBean(RabbitAdmin.class).getQueueProperties("**");
        context.close();
    }
}

此时发现队列error声明成功,info声明失败,控制台打印:

九月 29, 2017 11:51:23 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@446cdf90: startup date [Fri Sep 29 23:51:23 CST 2017]; root of context hierarchy
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
九月 29, 2017 11:51:24 下午 org.springframework.context.support.DefaultLifecycleProcessor start
信息: Starting beans in phase -2147482648
九月 29, 2017 11:51:24 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory createBareConnection
信息: Created new connection: connectionFactory#163e4e87:0/SimpleConnection@ef9296d [delegate=amqp://zhihao.miao@192.168.1.131:5672/, localPort= 63244]
九月 29, 2017 11:51:24 下午 org.springframework.amqp.rabbit.connection.CachingConnectionFactory log
严重: Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'info' in vhost '/': received none but current is the value 'zhihao.topic.exchange' of type 'longstr', class-id=50, method-id=10)
九月 29, 2017 11:51:24 下午 org.springframework.amqp.rabbit.core.RabbitAdmin logOrRethrowDeclarationException
警告: Failed to declare queue: Queue [name=info, durable=true, autoDelete=false, exclusive=false, arguments={}], continuing... com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'info' in vhost '/': received none but current is the value 'zhihao.topic.exchange' of type 'longstr', class-id=50, method-id=10)
九月 29, 2017 11:51:24 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@446cdf90: startup date [Fri Sep 29 23:51:23 CST 2017]; root of context hierarchy
九月 29, 2017 11:51:24 下午 org.springframework.context.support.DefaultLifecycleProcessor stop
信息: Stopping beans in phase -2147482648

此时也就达到了我们的需求。

-------------------------------------------------------------------------------------------------

springboot整合rabbitmq

加入依赖:

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.5.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

配置文件配置,查看org.springframework.boot.autoconfigure.amqp.RabbitProperties这个类,

具体的配置如下,在application.properties中定义如下

 spring.rabbitmq.addresses=amqp://zhihao.miao:123456@192.168.1.131:5672

当然下面的方式也可以

spring.rabbitmq.host=192.168.1.131
spring.rabbitmq.port=5672
spring.rabbitmq.username=zhihao.miao
spring.rabbitmq.password=123456

还有一些其他的配置,具体的情况可以去设置,比如:

requestedHeartbeat
publisherConfirms
publisherReturns
connectionTimeout
.....

自动声明

配置类,

@Configuration
public class MQCOnfiguration {

    @Bean
    public Queue pay(){
        return new Queue("pay",true);
    }

    @Bean
    public Queue order(){
        return new Queue("order",true);
    }

 }

应用启动类,

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

启动应用启动类,发现队列没有自动声明,原因是自动声明必须要和rabbitmq服务进行连接。

改造成下面的:

 @Configuration
public class MQCOnfiguration {

    @Bean
    public Queue pay(){
        return new Queue("pay",true);
    }

    @Bean
    public Queue order(){
        return new Queue("order",true);
    }

    @Bean
    public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames("zhihao.miao.order");
        container.setAcknowledgeMode(AcknowledgeMode.AUTO);
        container.setMessageListener((MessageListener) message -> {
            System.out.println("====接收到消息=====");
            System.out.println(new String(message.getBody()));
        });
        return container;
    }
}

启动启动类Application类,发现二个队列自动声明了。

总结
自动声明的一些条件:
1.不需要在容器中去声明ConnectionFactory,RabbitAdmin,RabbitTemplate了,sprngboot自动帮我们管理了。

发送消息

@RestController
public class SendController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send/pay")
    public String send(){
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType("text");
        rabbitTemplate.send("","pay", MessageBuilder.withBody("支付了1023.00".getBytes()).
                andProperties(messageProperties).build());
        return "Success";
    }
}

应用启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

发送消息:http://localhost:8080/send/pay

从控制台上发现消息已经发送成功

从发送消息的列子我们知道spring已经托管了RabbitmqTemplate这个对象

消费消息,使用注解的方式监听队列

@Component
public class MessageHandle {

    @RabbitListener(queues = "pay")
    public void handle(String body){
        System.out.println("=====handle==========");
        System.out.println(body);
    }
}

应用启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

我们之前学习的时候,使用spring-amqp发现使用@RabbitListener注解的时候,必须声明org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory对象,而如果使用springboot的方式则不需要自己在容器中声明org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory

总结
使用springboot之后,很多前置的Bean都已经被装配好了,我们直接使用就行了,之前在spring-amqp中怎么使用的,现在还是怎么用。

-------------------------------------------------------------------------------------------------

RabbitMQ博客列表

posted @ 2022-12-05 09:27  hanease  阅读(281)  评论(0)    收藏  举报