目录

  • 一、消息中间件概述
  • 1.1、为什么学习消息队列
  • 1.2、什么是消息队列
  • 1.3、应用场景
  • 1.3.1、QPS/PV
  • 1.3.2、什么是PV,UV,PR
  • 1.4、AMQP和JMS
  • 1.4.1、AMQP
  • 1.4.2、JMS
  • 二、RabbitMQ简介及安装
  • 2.1、RabbitMQ简介
  • 2.2、安装及配置
  • 2.2.1、下载
  • 2.2.2、安装
  • 2.2.3、卸载
  • 2.3、管理界面
  • 三、AMQP
  • 3.1、相关概念
  • 3.2、RabbitMQ运转流程
  • 四、RabbitMQ生产者消费者模型
  • 4.1、工作模式
  • 4.2、订阅模式
  • 4.3、路由模式
  • 4.4、通配符模式
  • 4.5、模式总结
  • 五、RabbitMQ高级特性
  • 5.1、消息的可靠投递
  • comsumer Ack
  • 5.2、消费端限流
  • 5.3、TTL
  • 5.4、死信队列
  • 5.5、延迟队列
  • 六、消息百分百成功投递
  • 七、消息幂等性保障
  • 八、RabbitMQ集群搭建(待更新...)


一、消息中间件概述

1.1、为什么学习消息队列

  • 队列的主要作用是消除高并发访问高峰,加快网站的响应速度。
  • 在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,会对数据库造成巨大的压力,同时也使得系统响应延迟加剧。

1.2、什么是消息队列

MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法

介绍:消息队列就是基础数据结构中的“先进先出”的一种数据机构。想一下,生活中买东西,需要排队,先排的人先买消费,就是典型的“先进先出”。

消息队列内容监控 消息队列使用教程_java


消息传递:指的是程序之间通过消息发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。

排队:指的是应用程序通过队列来通信。

在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而**提高了系统的吞吐量。

1.3、应用场景

消息中间件的主要的作用:

  • 异步处理
  • 解耦服务
  • 流量削峰

1.3.1、QPS/PV

QPS:即每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。、
因特网上,经常用每秒查询率来衡量域名系统服务器的机器的性能,即为QPS。或者理解:每秒的响应请求数,也即是最大吞吐能力。

计算关系:

  • QPS = 并发量 / 平均响应时间
  • 并发量 = QPS * 平均响应时间
  • 原理:每天80%的访问集中在20%的时间里,这20%时间叫做峰值时间。
  • 公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS) 。
  • 机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器 。

1.3.2、什么是PV,UV,PR

  • PV(page view),即页面浏览量,或点击量;通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标。
  • uv(unique visitor),指访问某个站点或点击某条新闻的不同IP地址的人数。
  • PR(PageRank),网页的级别技术,用来标识网页的等级/重要性。级别从1到10级,10级为满分。PR值越高说明该网页越受欢迎(越重要)。

1.4、AMQP和JMS

MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQPJMS

1.4.1、AMQP

AMQP是一种高级消息队列协议(Advanced Message Queuing Protocol),更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。

1.4.2、JMS

JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

AMQP 与 JMS 区别
1.JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
2.JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
3.JMS规定了两种消息模式;而AMQP的消息模式更加丰富

二、RabbitMQ简介及安装

2.1、RabbitMQ简介

2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言由 Ericson 设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图:

消息队列内容监控 消息队列使用教程_rabbitmq_02

  • Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
  • Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server
    提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
  • Connection:publisher/consumer 和 broker 之间的 TCP 连接
  • Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP
  • Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic(publish-subscribe) and fanout (multicast)
  • Queue:存储消息的容器,消息最终被送到这里,等待 consumer 取走
  • Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

2.2、安装及配置

2.2.1、下载

  1. 下载Erlang的rpm包
    RabbitMQ是Erlang语言编写,所以Erang环境必须要有,注:Erlang环境一定要与RabbitMQ版本匹配:
    https://www.rabbitmq.com/which-erlang.html
  2. 下载socat的rpm包
    rabbitmq安装依赖于socat,所以需要下载socat。
    socat下载地址:http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
  3. 下载RabbitMQ的rpm包
    RabbitMQ下载地址:https://www.rabbitmq.com/download.html(根据自身需求及匹配关系,下载对应rpm包)rabbitmq-server-3.8.1-1.el7.noarch.rpm

2.2.2、安装

  • 安装Erlang、Socat、RabbitMQ
rpm -ivh erlang-21.3.8.9-1.el7.x86_64.rpm 
rpm -ivh socat-1.7.3.2-1.el6.lux.x86_64.rpm

在安装rabbitmq之前需要先安装socat,否则,报错。
可以采用yum安装方式:yum install socat,我们这里采用rpm安装方式

rpm -ivh rabbitmq-server-3.8.1-1.el7.noarch.rpm

/usr/lib/rabbitmq/bin/

  • 启用管理插件
rabbitmq-plugins enable rabbitmq_management
  • 启动RabbitMQ
systemctl start rabbitmq-server.service
systemctl status rabbitmq-server.service
systemctl restart rabbitmq-server.service
systemctl stop rabbitmq-server.service
  • 查看进程
ps -ef | grep rabbitmq
  • 关闭防火墙
systemctl stop firewalld.service
  • 测试
    在web浏览器中输入地址:http://虚拟机:15672/
    输入默认账号密码: guest,guest,guest用户默认不允许远程连接。
  • 添加自定义账号
    添加管理员账号密码:rabbitmqctl add_user admin 123456
    分配账号角色:rabbitmqctl set_user_tags admin administrator
    设置用户权限:set_permissions [-p ]
    rabbitmqctl set_permissions -p “/” admin “." ".” “.*”
  • 使用新账号登录

2.2.3、卸载

rpm -qa | grep rabbitmq
rpm -e rabbitmq-server

2.3、管理界面

  • 添加用户
  • 创建Virtual Hosts
    虚拟主机:类似于mysql中的database。他们都是以“/”开头
  • 设置权限

三、AMQP

3.1、相关概念

AMQP 一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
RabbitMQ是AMQP协议的Erlang的实现。

概念

说明

连接Connection

一个网络连接,比如TCP/IP套接字连接

信道Channel

多路复用连接中的一条独立的双向数据流通道。为会话提供物理传输介质。

客户端Client

AMQP连接或者会话的发起者。AMQP是非对称的,客户端生产和消费消息,服务器存储和路由这些消息。

服务节点Broker

消息中间件的服务节点;一般情况下可以将一个RabbitMQ Broker看作一台RabbitMQ 服务器。

端点

AMQP对话的任意一方。一个AMQP连接包括两个端点(一个是客户端,一个是服务器)。

消费者Consumer

一个从消息队列里请求消息的客户端程序。

生产者Producer

一个向交换机发布消息的客户端应用程序。

3.2、RabbitMQ运转流程

  • 生产者发送消息
  • 1.生产者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker;
  • 2.声明队列并设置属性;如是否排它,是否持久化,是否自动删除;
  • 3.将路由键(空字符串)与队列绑定起来;
  • 4.发送消息至RabbitMQ Broker;
  • 5.关闭信道;
  • 6.关闭连接;
  • 消费者接收消息
  • 1.消费者创建连接(Connection),开启一个信道(Channel),连接到RabbitMQ Broker
  • 2.向Broker 请求消费相应队列中的消息,设置相应的回调函数;
  • 3.等待Broker投递响应队列中的消息,消费者接收消息;
  • 4.确认(ack,自动确认)接收到的消息;
  • 5.RabbitMQ从队列中删除相应已经被确认的消息;
  • 6.关闭信道;
  • 7.关闭连接;

消息队列内容监控 消息队列使用教程_rabbitmq_03

四、RabbitMQ生产者消费者模型

4.1、工作模式

多个消费端共同消费同一个队列中的消息。

消息队列内容监控 消息队列使用教程_发送消息_04

4.2、订阅模式

发布订阅模式:

  • 1、每个消费者监听自己的队列。
  • 2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息

消息队列内容监控 消息队列使用教程_java_05


订阅模型中,多了一个exchange角色,而且过程略有变化:

  • P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • C:消费者,消息的接受者,会一直等待消息到来。
  • Queue:消息队列,接收消息、缓存消息。
  • Exchange交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。

Exchange有常见以下3种类型:

  • Fanout:广播,将消息交给所有绑定到交换机的队列
  • Direct:定向,把消息交给符合指定routing key 的队列
  • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

4.3、路由模式

路由模式特点:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
  • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的Routing key完全一致,才会接收到消息。
  • P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
  • X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
  • C1:消费者,其所在队列指定了需要routing key 为 error 的消息
  • C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

4.4、通配符模式

Topic类型与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:

  • #:匹配零个或多个词
  • *:匹配不多不少恰好1个词

举例:

  • item.#:能够匹配item.insert.abc 或者 item.insert
  • item.*:只能匹配item.insert

消息队列内容监控 消息队列使用教程_java_06


消息队列内容监控 消息队列使用教程_发送消息_07

4.5、模式总结

RabbitMQ工作模式:

  1. 简单模式
    一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
  2. 工作队列模式
    一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
  3. 发布订阅模式
    需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
  4. 路由模式
    需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
  5. 通配符模式
    需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

五、RabbitMQ高级特性

5.1、消息的可靠投递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

  • 1.confirm 确认模式
  • 2.return 退回模式

rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
l消息从 producer 到 exchange 则会返回一个 confirmCallback 。
l消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递

  • 设置 ConnectionFactory的publisher-confirms=“true” 开启确认模式
  • 使用 rabbitTemplate.setConfirmCallback 设置回调函数。当消息发送到 exchange 后回调 confirm 方法。在方法中判断 ack,如果为true,则发送成功,如果为false,则发送失败,需要处理。
  • 设置 ConnectionFactory 的 publisher-returns=“true” 开启退回模式
  • 使用 rabbitTemplate.setReturnCallback 设置退回函数,当消息从exchange 路由到 queue
    失败后,如果设置了 rabbitTemplate.setMandatory(true) 参数,则会将消息退回给
    producer并执行回调函数returnedMessage

comsumer Ack

ck指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有二种确认方式:

  • 自动确认:acknowledge=“none” 默认
  • 手动确认:acknowledge=“manual

其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。
如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。

5.2、消费端限流

消息队列内容监控 消息队列使用教程_java_08

  • 在 rabbit:listener-container中配置 prefetch 属性设置消费端一次拉取多少条消息
  • 消费端的必须确认才会继续处理其他消息。

修改spring-rabbitmq-consumer.xml 配置文件

消息队列内容监控 消息队列使用教程_发送消息_09

5.3、TTL

TTL 全称 Time To Live(存活时间/过期时间)。

当消息到达存活时间后,还没有被消费,会被自动清除

RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

消息队列内容监控 消息队列使用教程_发送消息_10

  • 代码实现
/**
     * TTL:过期时间
     *  1. 队列统一过期
     *  2. 消息单独过期
     * 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
     */
    @Test
    public void testMessageTtl() {
        // 消息后处理对象,设置一些消息的参数信息
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //1.设置message的信息
                // 第二个方法:消息的过期时间 ,5秒之后过期
                message.getMessageProperties().setExpiration("5000");
                //2.返回该消息
                return message;
            }
        };
        //消息单独过期
        rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.hehe","message ttl....",messagePostProcessor);
    }

5.4、死信队列

死信队列,英文缩写:DLX 。DeadLetter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。

什么是死信队列

先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,自然就有了死信队列;

消息队列内容监控 消息队列使用教程_消息队列内容监控_11


消息成为死信的三种情况:

  • 1.队列消息数量到达限制;比如队列最大只能存储10条消息,而发了11条消息,根据先进先出,最先发的消息会进入死信队列。
  • 2.消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  • 3.原队列存在消息过期设置,消息到达超时时间未被消费;
    死信的处理方式:
  • ① 丢弃,如果不是很重要,可以选择丢弃
  • ② 记录死信入库,然后做后续的业务分析或处理
  • ③ 通过死信队列,由负责监听死信的应用程序进行处理

综合来看,更常用的做法是第三种,即通过死信队列,将产生的死信通过程序的配置路由到指定的死信队列,然后应用监听死信队列,对接收到的死信做后续的处理。

队列绑定死信交换机:

给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key

消息队列内容监控 消息队列使用教程_java_12

  • 1.死信交换机和死信队列和普通的没有区别
  • 2.当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
  • 3.消息成为死信的三种情况:
  • 队列消息长度(数量)到达限制;
  • 消费者拒接消费消息,并且不重回队列;
  • 原队列存在消息过期设置,消息到达超时时间未被消费;

5.5、延迟队列

延迟队列存储的对象肯定是对应的延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。

场景:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行取消处理。这时就可以使用延时队列将订单信息发送到延时队列。
需求:
1.下单后,30分钟未支付,取消订单,回滚库存。
2.新用户注册成功30分钟后,发送短信问候。
实现方式:延迟队列

很可惜,在RabbitMQ中并未提供延迟队列功能。

但是可以使用:TTL+死信队列组合实现延迟队列的效果。

消息队列内容监控 消息队列使用教程_消息队列内容监控_13

六、消息百分百成功投递

消息的可靠性投递,无法避免的,在实际的工作中会经常碰到,比如一些核心业务需要保障消息不丢失,接下来我们看一个可靠性投递的流程图,说明可靠性投递的概念:

消息队列内容监控 消息队列使用教程_发送消息_14

Step 1: 首先把消息信息(业务数据)存储到数据库中,紧接着,我们再把这个消息记录也存储到一张消息记录表里(或者另外一个同源数据库的消息记录表)
Step 2:发送消息到MQ Broker节点(采用confirm方式发送,会有异步的返回结果)
Step 3、4:生产者端接受MQ Broker节点返回的Confirm确认消息结果,然后进行更新消息记录表里的消息状态。比如默认Status = 0 当收到消息确认成功后,更新为1即可!
Step 5:但是在消息确认这个过程中可能由于网络闪断、MQ Broker端异常等原因导致 回送消息失败或者异常。这个时候就需要发送方(生产者)对消息进行可靠性投递了,保障消息不丢失,100%的投递成功!(有一种极限情况是闪断,Broker返回的成功确认消息,但是生产端由于网络闪断没收到,这个时候重新投递可能会造成消息重复,需要消费端去做幂等处理)所以我们需要有一个定时任务,(比如每5分钟拉取一下处于中间状态的消息,当然这个消息可以设置一个超时时间,比如超过1分钟 Status = 0 ,也就说明了1分钟这个时间窗口内,我们的消息没有被确认,那么会被定时任务拉取出来)
Step 6:接下来我们把中间状态的消息进行重新投递 retry send,继续发送消息到MQ ,当然也可能有多种原因导致发送失败
Step 7:我们可以采用设置最大努力尝试次数,比如投递了3次,还是失败,那么我们可以将最终状态设置为Status = 2 ,最后 交由人工解决处理此类问题(或者把消息转储到失败表中)。

七、消息幂等性保障

幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

消息队列内容监控 消息队列使用教程_消息队列内容监控_15


在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。

消息幂等性保障 乐观锁机制

  1. 生产者发送消息:
id=1,money=500,version=1
  1. 消费者接收消息
id=1,money=500,version=1
id=1,money=500,version=1
  1. 消费者需要保证幂等性:第一次执行SQL语句
第一次执行:version=1
update account set money = money - 500 , version = version + 1
where id = 1 and version = 1
  1. 消费者需要保证幂等性:第二次执行SQL语句:版本不同无法执行
update account set money = money - 500 , version = version + 1
where id = 1 and version = 1

八、RabbitMQ集群搭建(待更新…)