## RabbitMQ简介,安装

高性能消息中间件RabbitMQ

学习目标:

  • 能够说出消息队列的应用场景以及RabbitMQ的主要概念
  • 完成RabbitMQ安装以及RabbitMQ七种模式的入门案例

RabbitMQ简介

消息队列中间件简介

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量 削锋等问题实现高性能,高可用,可伸缩和终一致性[架构] 使用较多的消息队列有 ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ
以下介绍消息队列在实际应用中常用的使用场景:异步处理,应用解耦,流量削锋,日志处理和消息通讯五个场景

直接进入正题。

一.异步处理

场景:发送手机验证码,邮件

传统古老处理方式如下图

img

这个流程,全部在主线程完成,注册-》入库-》发送邮件-》发送短信,由于都在主线程,所以要等待每一步完成才能继续执行。由于每一步的操作时间响应时间不固定,所以主线程的请求耗时可能会非常长,如果请求过多,会导致IIS站点巨慢,排队请求,甚至宕机,严重影响用户体验。(在一个主线程中,代码时从上往下以此执行的)

现在大多数的处理方式如下图

img

这个做法是主线程只做耗时非常短的入库操作,发送邮件和发送短信,会开启2个异步线程,扔进去并行执行,主线程不管,继续执行后续的操作,这种处理方式要远远好过第一种处理方式,极大的增强了请求响应速度,用户体验良好。缺点是,由于异步线程里的操作都是很耗时间的操作,一个请求要开启2个线程,而一台标准配置的ECS服务器支撑的并发线程数大概在800左右,假设一个线程在10秒做完,这个单个服务器最多能支持400个请求的并发,后面的就要排队。出现这种情况,就要考虑增加服务器做负载,尴尬的增加成本。

消息队列RabbitMq的处理方式

img

这个流程是,主线程依旧处理耗时低的入库操作,然后把需要处理的消息写进消息队列中,这个写入耗时可以忽略不计,非常快,然后,独立的发邮件子系统,和独立的发短信子系统,同时订阅消息队列,进行单独处理。处理好之后,向队列发送ACK确认,消息队列整条数据删除。这个流程也是现在各大公司都在用的方式,以SOA服务化各个系统,把耗时操作,单独交给独立的业务系统,通过消息队列作为中间件,达到应用解耦的目的,并且消耗的资源很低,单台服务器能承受更大的并发请求。

二.应用解耦

场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.
这里写图片描述
这种做法有一个缺点:

  • 当库存系统出现故障时,订单就会失败。(这样马云将少赚好多好多钱^ ^)
  • 订单系统和库存系统高耦合.
    引入消息队列
    这里写图片描述
  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
  • 库存系统:订阅下单的消息,获取下单消息,进行库操作。
    就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失(马云这下高兴了).

三.流量削峰

流量削峰一般在秒杀活动中应用广泛
场景:秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用:
1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
这里写图片描述
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.
2.秒杀业务根据消息队列中的请求信息,再做后续处理.

四.日志处理

这个场景应该都很熟悉,一个系统有大量的业务需要各种日志来保证后续的分析工作,而且实时性要求不高,用队列处理再好不过了

五.消息通讯

现在上线的各大社交通讯项目中,实际上是没有用消息队列做即时通讯的,但是它确实可以用来做,有兴趣的不妨去试试吧

img

这个是点对点通信,消费者A和B同时订阅消息队列,又同时是制造者,就能实现点对点通信

img

群聊的做法,所有客户端同时订阅队列,又同时是发送,制造者。

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

上述大致的5种RabbitMq的应用场景,下面来介绍几个消息队列的区别

Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?

特性ActiveMQRabbitMQRocketMQKafka
单机吞吐量万级,比 RocketMQ、Kafka 低一个数量级同 ActiveMQ10 万级,支撑高吞吐10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景
topic 数量对吞吐量的影响topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topictopic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源
时效性ms 级微秒级,这是 RabbitMQ 的一大特点,延迟最低ms 级延迟在 ms 级以内
可用性高,基于主从架构实现高可用同 ActiveMQ非常高,分布式架构非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用
消息可靠性有较低的概率丢失数据基本不丢经过参数优化配置,可以做到 0 丢失同 RocketMQ
功能支持MQ 领域的功能极其完备基于 erlang 开发,并发能力很强,性能极好,延时很低MQ 功能较为完善,还是分布式的,扩展性好功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用

什么是RabbitMQ

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。
AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放 标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不 受产品、开发语言等条件的限制。
RabbitMQ 初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展 性、高可用性等方面表现不俗。具体特点包括:

1.可靠性(Reliability)
RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。

2.灵活的路由(Flexible Routing)
在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。

3.消息集群(Clustering)
多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。

4.高可用(Highly Available Queues)
队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。

5.多种协议(Multi-protocol)
RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。

6.多语言客户端(Many Clients) RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。

7.管理界面(Management UI)
RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方 面。

8.跟踪机制(Tracing)
如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。

9.插件机制(Plugin System)
RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。

RabbitMQ的优点:

  • 开源、性能优秀、稳定性保障
  • 提供可靠性消息投递模式(confirm)、返回模式(return)
  • 与SpringAMQP完美的整合、API丰富
  • 集群模式丰富,表达式配置,HA模式,镜像队列模型
  • 保证数据不丢失的前提下做到高可靠性、可用性

RabbitMQ架构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9cglfBY-1574129862698)(D:\TinkingCat\RabbitMQ\assets\timg.jpg)]

RabbitMQ Server: 也叫broker server,它是一种传输服务。 他的角色就是维护一条 从Producer到Consumer的路线,保证数据能够按照指定的方式进行传输。

Producer: 消息生产者,如图A、B、C,数据的发送方。消息生产者连接RabbitMQ服 务器然后将消息投递到Exchange。

Consumer:消息消费者,如图1、2、3,数据的接收方。消息消费者订阅队列, RabbitMQ将Queue中的消息发送到消息消费者。 Exchange:生产者将消息发送到Exchange(交换器),由Exchange将消息路由到一个 或多个Queue中(或者丢弃)。Exchange并不存储消息。RabbitMQ中的Exchange有 direct、fanout、topic、headers四种类型,每种类型对应不同的路由规则。

Queue:(队列)是RabbitMQ的内部对象,用于存储消息。消息消费者就是通过订阅 队列来获取消息的,RabbitMQ中的消息都只能存储在Queue中,生产者生产消息并终 投递到Queue中,消费者可以从Queue中获取消息并消费。多个消费者可以订阅同一个 Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者 都收到所有的消息并处理。

RoutingKey:生产者在将消息发送给Exchange的时候,一般会指定一个routing key, 来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能终生效。在Exchange Type与binding key固定的情况下(在正常使用时一 般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过 指定routing key来决定消息流向哪里。RabbitMQ为routing key设定的长度限制为255 bytes。

Connection: (连接):Producer和Consumer都是通过TCP连接到RabbitMQ Server 的。以后我们可以看到,程序的起始处就是建立这个TCP连接。

Channels: (信道):它建立在上述的TCP连接中。数据流动都是在Channel中进行 的。也就是说,一般情况是程序起始建立TCP连接,第二步就是建立这个Channel。

VirtualHost:权限控制的基本单位,一个VirtualHost里面有若干Exchange和 MessageQueue,以及指定被哪些user使用

RabbitMQ的消息流转:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7I6MIqa-1574129862698)(D:\TinkingCat\RabbitMQ\assets\1348730-20190606002655965-1977548174.png)]

AMQP

AMQP全称: Advanced Message Queuing Protocol

AMQP翻译: 高级消息队列协议

AMQP定义: 是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bq077Cw6-1574129862699)(D:\TinkingCat\RabbitMQ\assets\1348730-20190606002906491-408602073.png)]

Centos7中安装RabbitMQ

rabbitmq下载】rabbitmq下载官网地址:http://www.rabbitmq.com/

具体的安装包的下载【这里安装的版本是3.7.5】:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.7.5

提供一个百度网盘地址:链接:https://pan.baidu.com/s/1K_dWn2u-NqSnZ1r8xR-5bw 提取码:1c19

注意事项:

erlang的版本会影响到rabbitmq的安装,两者有个版本对照

查看对照的地址:http://www.rabbitmq.com/which-erlang.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ksnfVZR-1574129862699)(D:\TinkingCat\RabbitMQ\assets\1574066880038.png)]

【erlang下载】

具体的安装包的下载【这里安装的版本是19.3】:http://www.erlang.org/downloads/19.3

提供一个百度网盘地址:链接:https://pan.baidu.com/s/1F6dEThHbf2jRmJdOL_OiXQ 提取码:378x

安装步骤

上传这三个要安装的文件:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bi2Sdh5L-1574129862700)(D:\TinkingCat\RabbitMQ\assets\1574067014487.png)]

一.配置jdk环境

JAVA_HOME=/usr/jdk8
CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
PATH= P A T H : PATH: PATH:JAVA_HOME/bin:$JAVA_HOME/jre/bin
export PATH CLASSPATH JAVA_HOME

二.安装erlang

方法一:

[root@localhost ~]# tar -zxvf otp_src_19.3.tar.gz

[root@localhost ~]# mv otp_src_19.3 /usr/local/src/java/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3JPWv9eH-1574129862700)(D:\TinkingCat\RabbitMQ\assets\1574067567962.png)]

#切换到目录文件执行安装,报错
[root@localhost java]# cd otp_src_19.3/
[root@localhost otp_src_19.3]# ./configure --prefix=/opt/erlang
Ignoring the --cache-file argument since it can cause the system to be erroneously configured
Disabling caching
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for gcc... no
checking for cc... no
checking for cc... no
checking for cl... no
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details.

#安装gcc
yum install -y gcc
#yum search libtool
#yum search libtool-ltdl-devel
#yum install libtool
#yum install libtool-ltdl-devel
#yum install gcc-c++ 
#yum install erlang-doc                 
#yum install erlang-jinterface

#执行make
[root@localhost otp_src_19.3]# make
Makefile:248: /usr/local/src/java/otp_src_19.3/make/x86_64-unknown-linux-gnu/otp_ded.mk: 没有那个文件或目录
make: *** 没有规则可以创建目标“/usr/local/src/java/otp_src_19.3/make/x86_64-unknown-linux-gnu/otp_ded.mk”。 停止。

#解决方案
sudo yum install ncurses-devel.x86_64

#执行相关配置
./configure --prefix=/opt/erlang

#安装
make
make install

#配置erlang环境变量
ERLANG_HOME=/opt/erlang
JAVA_HOME=/usr/local/src/java/jdk8
CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$ERLANG_HOME/bin
export PATH CLASSPATH JAVA_HOME ERLANG_HOME

#刷新修改的资源文件
source /etc/profile

#测试是否配置成功
erl

方法二:

#下载Erlang
y
#报错
[root@localhost jdk8]# wget http://www.rabbitmq.com/releases/erlang/erlang-19.0.4-1.el7.centos.x86_64.rpm
-bash: wget: 未找到命令
#安装wget
yum install wget
#安装Erlang
rpm -ivh erlang-19.0.4-1.el7.centos.x86_64.rpm
#测试
erl
#成功
[root@localhost jdk8]# erl
Erlang/OTP 19 [erts-8.0.3] [source] [64-bit] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V8.0.3  (abort with ^G)
1> 

三.安装RabbitMQ

注意: 我给的是以tar.zx结尾的文件,需要解压一次才能得到上面的

[root@localhost ~]# xz -d rabbitmq-server-generic-unix-3.7.5.tar.xz

[root@localhost ~]# tar -xvf rabbitmq-server-generic-unix-3.7.5.tar -C /opt

#我给的是以tar.zx结尾的文件,需要解压一次才能得到tar文件
xz -d rabbitmq-server-generic-unix-3.7.5.tar.xz
#解压文件并且指定加压位置 /opt
tar -xvf rabbitmq-server-generic-unix-3.7.5.tar -C /opt
#移动到java文件夹下
mv rabbitmq_server-3.7.5 /usr/local/src/java/
#添加环境变量
ERLANG_HOME=/opt/erlang
RABBITMQ_HOME=/opt/rabbitmq_server-3.7.5
JAVA_HOME=/usr/local/src/java/jdk8
CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$ERLANG_HOME/bin:$RABBITMQ_HOME/sbin
export PATH CLASSPATH JAVA_HOME ERLANG_HOME RABBITMQ_HOME

方法二:

#下载RabbitMQ
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.15/rabbitmq-server-3.6.15-1.el7.noarch.rpm
#安装RabbitMQ
rpm -ivh rabbitmq-server-3.6.15-1.el7.noarch.rpm
#报错
[root@localhost ~]# rpm -ivh rabbitmq-server-3.6.15-1.el7.noarch.rpm
警告:rabbitmq-server-3.6.15-1.el7.noarch.rpm: 头V4 RSA/SHA1 Signature, 密钥 ID 6026dfca: NOKEY
错误:依赖检测失败:
	socat 被 rabbitmq-server-3.6.15-1.el7.noarch 需要
#安装socat
yum install socat
#检查安装
[root@localhost jdk8]# rpm -qa | grep rabbitmq
rabbitmq-server-3.6.15-1.el7.noarch
#可以查到说明安装成功!
#启动rabbitmq-server
systemctl start rabbitmq-server
#关闭rabbitmq-server
systemctl stop rabbitmq-server
#查看rabbitmq状态
systemctl status rabbitmq-server
#出现running说明成功
● rabbitmq-server.service - RabbitMQ broker
   Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
   Active: active (running) since 一 2019-11-18 21:31:53 CST; 4min 19s ago
 Main PID: 1759 (beam)
   Status: "Initialized"
   CGroup: /system.slice/rabbitmq-server.service
           ├─1759 /usr/lib64/erlang/erts-8.0.3/bin/beam -W w -A 64 -P 1048576 -t 5000000 -stbt db -zdbbl 12...
           ├─1902 /usr/lib64/erlang/erts-8.0.3/bin/epmd -daemon
           ├─2000 erl_child_setup 1024
           ├─2008 inet_gethost 4
           └─2009 inet_gethost 4
#列出角色
rabbitmqctl list_users
#虽启动服务,但不能连接。尚未配置维护插件和开启远程连接
查看端口:netstat -ntlp
安装:yum -y install net-tools
 查看进程:ps -ef |grep rabbitmq

四. 配置网页插件

#创建目录
mkdir /etc/rabbitmq
#我的已经存在,无需创建!
#启动插件
rabbitmq-plugins enable rabbitmq_management
#开放15672端口
#添加指定需要开放的端口:
firewall-cmd --add-port=15672/tcp --permanent
#重载入添加的端口:
firewall-cmd --reload
#查询指定端口是否开启成功:
firewall-cmd --query-port=15672/tcp
#访问web管理界面(http://192.168.126.129:15672/)
#登录遇到问题:User can only log in via localhost
#找到这个文件rabbit.app  /usr/lib/rabbitmq/lib/rabbitmq_server-3.7.7/ebin/rabbit.app
find / -name rabbit.app
#将:{loopback_users, [<<”guest”>>]},
#改为:{loopback_users, []},
#原因:rabbitmq从3.3.0开始禁止使用guest/guest权限通过除localhost外的访问
#重启服务
systemctl restart rabbitmq-server.service
#user:guest
#pwd:guest

Docker环境下安装RabbitMQ

下载镜像:

docker pull rabbitmq:management

创建容器,rabbitmq需要有映射以下端口: 5671 5672 4369 15671 15672 25672

  • 15672 (if management plugin is enabled) :MQ本身的QMQP的协议端口
  • 15671 management监听端口
  • 5672, 5671 (AMQP 0-9-1 without and with TLS)
  • 4369 (epmd) epmd 代表 Erlang 端口映射守护进程
  • 25672 (Erlang distribution):创建MQ集群的端口

启动容器

docker run -di --name=rabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p15672:15672 -p 25672:25672 rabbitmq:management

访问:

http://192.168.126.129:15672/

rabbitmq 几种应用模式

1、Hello Word模式

python-one-overall.png

Hello Word模式他会发送数据到空字符的交换机,这种交换机很特殊,它允许我们准确地指定消息应该去哪个队列。需要在routing_key参数中指定队列名称。

2、Work Queues模式

prefetch-count.png

Work Queues模式其实就是在Hello Word模式变成了多个消费者,不会消费同一条消息。

3、Publish/Subscribe模式(fanout模式)

python-three-overall.png

Publish/Subscribe模式其实就是将队列绑定到交换机,数据进入交换机然后在决定进入哪个队列,绑定2个就会进入2个队列,类似广播性质。此模式也就是Exchange模式中的fanout模式。

上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

4、Routing模式

python-four.png

Routing模式它会把消息路由到那些binding key与routing key完全匹配的Queue中,此模式也就是Exchange模式中的direct模式。

以上图的配置为例,我们以routingKey="error"发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…)。如果我们以routingKey="info"或routingKey="warning"来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

5、Topics模式

python-five.png

Topics模式与Routing模式比较相近,routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),binding key与routing key一样也是句点号“. ”分隔的字符串。binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。

以上图中的配置为例,routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配);routingKey=”quick.brown.fox”、routingKey=”orange”、routingKey=”quick.orange.male.rabbit”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

6、RPC模式

python-six.png

这个模式很少用,也不多做解释了,就是一个请求队列一个回调队列,具体参考官方网站或者其他博客吧。

交换机4种模式中还有一种模式没叙述,就是headers模式:

headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。

在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。

fanout,direct,topicexchange的routingKey都需要要字符串形式的,而headers exchange则没有这个要求,因为键值对的值可以是任何类型 ,匹配有两种方式all和any。这两种方式是在接收端必须要用键值"x-mactch"来定义。all代表定义的多个键值对都要满足,而any则代码只要满足一个就可以了

整合SpringBoot

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.amqp</groupId>
        <artifactId>spring-rabbit-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

ProduceerTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerTest {

    //springboot整合rabbitmq提供的一个类
    //RedisTemplate
    //JdbcTemplate...
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void sendMsg(){
        rabbitTemplate.convertAndSend("ange","欢迎来到叮当猫教育进行学习");
    }
}

Cunsumer.java

@Component
@RabbitListener(queues = "ange")
public class Customer1 {

    /**
     *
     * @param message 获取的消息内容
     */
    @RabbitHandler
    public void getMsg(String message){
        System.out.println("ange收到的消息是:"+message);
    }
}

RabbitMQ生产消费快速入门:

环境: springboot+jdk1.7+rabbitmq3.6.5 (Maven依赖配置)

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
  </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>3.6.5</version>
        </dependency>
    </dependencies>
public class Procuder {
    public static void main(String[] args) throws Exception {
        
        //1.创建一个ConnectionFactory 并进行配置
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        //2.通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        
        //3.通过Connection 创建一个 Channel
        Channel channel = connection.createChannel();
    
        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * exchange:指定交换机 不指定 则默认 (AMQP default交换机) 通过routingkey进行匹配 
         * props 消息属性
         * body 消息体
         */
        //4.通过Channel发送数据
        for(int i = 0; i < 5; i++){
          System.out.println("生产消息:" + i);
          String msg = "Hello RabbitMQ" + i;
          channel.basicPublish("", "test", null, msg.getBytes());
        }
        
        
        //5.记得关闭相关的连接
        channel.close();
        connection.close();
    }
}
public class Consumer {
    public static void main(String[] args) throws Exception{
                //1.创建一个ConnectionFactory 并进行配置
                ConnectionFactory connectionFactory = new ConnectionFactory();
                connectionFactory.setHost("192.168.244.11");
                connectionFactory.setPort(5672);
                connectionFactory.setVirtualHost("/");
                connectionFactory.setHandshakeTimeout(20000);
                //2.通过连接工厂创建连接
                Connection connection = connectionFactory.newConnection();
                
                //3.通过Connection 创建一个 Channel
                Channel channel = connection.createChannel();
                
                //4. 声明创建一个队列
                String queueName = "test";
                /**
                 * durable 是否持久化
                 * exclusive 独占的  相当于加了一把锁
                 */
                channel.queueDeclare(queueName,true,false,false,null);
                
                //5.创建消费者
                QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
                
                //6.设置channel
                /**
                 * ACK: 当一条消息从生产端发到消费端,消费端接收到消息后会马上回送一个ACK信息给broker,告诉它这条消息收到了
                 * autoack: 
                 * true  自动签收 当消费者一收到消息就表示消费者收到了消息,消费者收到了消息就会立即从队列中删除。
                 * false 手动签收 当消费者收到消息在合适的时候来显示的进行确认,说我已经接收到了该消息了,RabbitMQ可以从队列中删除该消息了
                 * 
                 */
                channel.basicConsume(queueName, true, queueingConsumer);
                
                //7.获取消息
                while(true){
                    Delivery delivery = queueingConsumer.nextDelivery();
                    String msg = new String(delivery.getBody());
                    System.err.println("消费端:" + msg);
                    //Envelope envelope = delivery.getEnvelope();
                }
    }
}

4. Exchange(交换机)详解

Exchange: 接收消息,并根据路由键转发消息所绑定的队列

img

交换机属性:

  • Name: 交换机名称
  • Type: 交换机类型 diect、topic、fanout、headers
  • Durability: 是否需要持久化,true为持久化
  • AutoDelete: 当最后一个绑定到Exchange的队列删除后,自动删除该Exchange
  • Internal: 当前Exchange是否用于RabbitMQ内部使用,默认为false (百分之99的情况默认为false 除非对Erlang语言较了解,做一些扩展)
  • Arguments: 扩展参数, 用于扩展AMQP协议可自定化使用

4.1 Direct Exchange

所有发送到Direct Exchange的消息被转发到RouteKey指定的Queue

**注意:**Direct模式可以使用RabbitMQ自带的Exchange: default Exchange,所以不需要将Exchange进行任何绑定(binding)操作,消息传递时,RoutingKey必须完全匹配才会被队列接收,否则该消息会被抛弃

img

public class ProducerDirectExchange {
    public static void main(String[] args) throws Exception {
        //1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        
        //2.创建Connection
        Connection connection = connectionFactory.newConnection();
        //3.创建Channel
        Channel channel = connection.createChannel();
        //4.声明
        String exchangeName = "test_direct_exchange";
        String routingKey = "test.direct";
        //5.发送
        String msg = "Hello World RabbitMQ4 Direct Exchange Message";
        channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
    }
}
public class ConsumerDirectExchange {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(3000);
        
        Connection connection = connectionFactory.newConnection();
        
        Channel channel = connection.createChannel();
        //声明
        String exchangeName = "test_direct_exchange";
        String exchangeType = "direct";
        String queueName = "test_direct_queue";
        String routingKey = "test.direct";
        //表示声明了一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType,true,false,false,null);
        //表示声明了一个队列
        channel.queueDeclare(queueName,false,false,false,null);
        //建立一个绑定关系
        channel.queueBind(queueName, exchangeName, routingKey);
        
        //durable 是否持久化消息
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //参数:队列名称,是否自动ACK,Consumer
        channel.basicConsume(queueName, true, consumer);
        
        //循环获取消息
        while(true){
            //获取消息,如果没有消息,这一步将会一直阻塞
            Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息:" + msg);
        }
    }
}

4.2 Topic Exchange

所有发送到Topic Exchange的消息被转发到所有关心RouteKey中指定Topic的Queue上

Exchange将RouteKey和某Topic进行模糊匹配,此时队列需要绑定一个Topic

img

注意:可以使用通配符进行匹配

符号 # 匹配一个或多个词

符号 * 匹配不多不少一个词

例如: “log.#” 能够匹配到 “log.info.oa”

“log.*” 只会匹配到 “log.err”

public class ProducerTopicExchange {
    public static void main(String[] args) throws Exception {
        //1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);

        //2.创建Connection
        Connection connection = connectionFactory.newConnection();
        //3.创建Channel
        Channel channel = connection.createChannel();
        //4.声明
        String exchangeName = "test_topic_exchange";
        String routingKey1 = "user.save";
        String routingKey2 = "user.update";
        String routingKey3 = "user.delete.abc";
        //5.发送
        String msg = "Hello World RabbitMQ4 Direct Exchange Message";
        channel.basicPublish(exchangeName, routingKey1, null, msg.getBytes());
        channel.basicPublish(exchangeName, routingKey2, null, msg.getBytes());
        channel.basicPublish(exchangeName, routingKey3, null, msg.getBytes());
    }
}
public class ConsumerTopicExchange {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(3000);
        
        Connection connection = connectionFactory.newConnection();
        
        Channel channel = connection.createChannel();
        //声明
        String exchangeName = "test_topic_exchange";
        String exchangeType = "topic";
        String queueName = "test_topic_queue";
        String routingKey = "user.#";
        //表示声明了一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType,true,false,false,null);
        //表示声明了一个队列
        channel.queueDeclare(queueName,false,false,false,null);
        //建立一个绑定关系
        channel.queueBind(queueName, exchangeName, routingKey);
        
        //durable 是否持久化消息
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //参数:队列名称,是否自动ACK,Consumer
        channel.basicConsume(queueName, true, consumer);
        
        //循环获取消息
        while(true){
            //获取消息,如果没有消息,这一步将会一直阻塞
            Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息:" + msg);
        }
    }
}

4.3 Fanout Exchange

不处理路由键,只需要简单的将队列绑定到交换机上
发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
所以Fanout交换机转发消息是最快的

img

public class ProducerFanoutExchange {
    public static void main(String[] args) throws Exception {
        //1.创建ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);

        //2.创建Connection
        Connection connection = connectionFactory.newConnection();
        //3.创建Channel
        Channel channel = connection.createChannel();
        //4.声明
        String exchangeName = "test_fanout_exchange";
        //5.发送
        for(int i = 0; i < 10 ; i++){
            String msg = "Hello World RabbitMQ4 Direct Exchange Message";
            channel.basicPublish(exchangeName, "", null, msg.getBytes());
        }
        channel.close();
        connection.close();
    }
}
public class ConsumerFanoutExchange {
    public static void main(String[] args) throws Exception{
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        connectionFactory.setAutomaticRecoveryEnabled(true);
        connectionFactory.setNetworkRecoveryInterval(3000);
        
        Connection connection = connectionFactory.newConnection();
        
        Channel channel = connection.createChannel();
        //声明
        String exchangeName = "test_fanout_exchange";
        String exchangeType = "fanout";
        String queueName = "test_topic_queue";
        //无需指定路由key 
        String routingKey = "";
        //表示声明了一个交换机
        channel.exchangeDeclare(exchangeName, exchangeType,true,false,false,null);
        //表示声明了一个队列
        channel.queueDeclare(queueName,false,false,false,null);
        //建立一个绑定关系
        channel.queueBind(queueName, exchangeName, routingKey);
        
        //durable 是否持久化消息
        QueueingConsumer consumer = new QueueingConsumer(channel);
        //参数:队列名称,是否自动ACK,Consumer
        channel.basicConsume(queueName, true, consumer);
        
        //循环获取消息
        while(true){
            //获取消息,如果没有消息,这一步将会一直阻塞
            Delivery delivery = consumer.nextDelivery();
            String msg = new String(delivery.getBody());
            System.out.println("收到消息:" + msg);
        }
    }
}

5. Message 消息

服务器与应用程序之间传递的数据,本质上就是一段数据,由Properties和Body组成

常用属性:delivery mode、headers (自定义属性)

其他属性:content_type、content_encoding、priority、expiration

消息的properties属性用法示例:

public class Procuder {
    public static void main(String[] args) throws Exception {
        
        //1.创建一个ConnectionFactory 并进行配置
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.244.11");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setHandshakeTimeout(20000);
        //2.通过连接工厂创建连接
        Connection connection = connectionFactory.newConnection();
        
        //3.通过Connection 创建一个 Channel
        Channel channel = connection.createChannel();
    
        Map<String,Object> headers = new HashMap<>();
        headers.put("my1", "111");
        headers.put("my2", "222");
        
        //10秒不消费 消息过期移除消息队列
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                .deliveryMode(2)
                .contentEncoding("utf-8")
                .expiration("10000")
                .headers(headers)
                .build();
        
        //4.通过Channel发送数据
        for(int i = 0; i < 5; i++){
          System.out.println("生产消息:" + i);
          String msg = "Hello RabbitMQ" + i;
          channel.basicPublish("", "test", properties, msg.getBytes());
        }
        
        
        //5.记得关闭相关的连接
        channel.close();
        connection.close();
    }
}
public class Consumer {
    public static void main(String[] args) throws Exception{
                //1.创建一个ConnectionFactory 并进行配置
                ConnectionFactory connectionFactory = new ConnectionFactory();
                connectionFactory.setHost("192.168.244.11");
                connectionFactory.setPort(5672);
                connectionFactory.setVirtualHost("/");
                connectionFactory.setHandshakeTimeout(20000);
                //2.通过连接工厂创建连接
                Connection connection = connectionFactory.newConnection();
                
                //3.通过Connection 创建一个 Channel
                Channel channel = connection.createChannel();
                
                //4. 声明创建一个队列
                String queueName = "test";
                channel.queueDeclare(queueName,true,false,false,null);
                
                //5.创建消费者
                QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
                
                //6.设置channel
                channel.basicConsume(queueName, true, queueingConsumer);
                
                //7.获取消息
                while(true){
                    Delivery delivery = queueingConsumer.nextDelivery();
                    String msg = new String(delivery.getBody());
                    System.err.println("消费端:" + msg);
                    
                    Map<String, Object> headers = delivery.getProperties().getHeaders();
                    System.err.println("headers value:" + headers.get("my1"));
                }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值