资料学习整理于:B站编程不良人
MQ消息中间件之RabbitMQ以及整合SpringBoot2.x实战教程
本次学习使用系统:CentOS release 6.5 (Final)
MQ(Message Quene):消息队列,通过典型的生产者和消费者模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步进行的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统间的解耦。别名:消息中间件通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
市面上有很多主流的消息中间件如:老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发的RocketMQ等
ActiveMQ
AcitveMQ 是Apache的产品,最流行且能力强劲的开源消息总线。它是一个完全支持JMS规范的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小企业颇受欢迎。
缺点:吞吐量并没有达到一个很高的级别,而且性能瓶颈,所以在大型甚至中型企业中,很少有其身影。
Kafka
Kafka 是LinkedIn开源的使用scala语言开发的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,最初的目的是用于日志收集和传输。0.8版本支持复制,不支持事务,对消息的重复、丢失、错误没有严格的要求,适合大数据互联网服务的数据收集业务。
缺点:对数据的完整性较低,对数据的一致性支持几乎为零。
RocketMQ
RocketMQ 是阿里巴巴开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统。RocketMQ思路起源于Kafka,但并不是Kafka的拷贝版,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式计算、binglog分发等场景。现已将开源版本托管于Apache。
缺点:RrocketMQ中分布式事务、一致性等部分功能属于收费功能,并不在开源版本中。
RabbitMQ
RabbitMQ 使用Erlang语言开发的开源消息队列系统,基于AMQP协议实现。AMQP协议的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求更高的场景。
缺点:性能和吞吐量的能力其次。
基于AMQP协议,erlang语言开发,是部署最广泛的开源消息中间件,是最受欢迎的开源消息中间件之一。
官网:https://www.rabbitmq.com
官方教程:https://www.rabbitmq.com/#getstarted
AMQP协议
AMQP(advanced message queuing protocol)在2003年时被提出,最早用于金融领域不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是跟JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交互的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。
AMQP协议模型
首先在官网苦逼的找安装包,然后下载下来~注意linux版本
GitHub下载地址:https://github.com/rabbitmq/rabbitmq-server/releases
1、安装erlang
rpm -ivh erlang-22.3.4.7-1.el6.x86_64.rpm
2、安装socat
rpm -ivh socat-1.7.2.4-1.el6.rf.x86_64.rpm
3、安装RabbitMQ
yum install -y rabbitmq-server-3.7.18-1.el6.noarch.rpm
or
rpm -ivh rabbitmq-server-3.7.18-1.el6.noarch.rpm
默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.config
4、找到模版配置文件,复制到默认路径
[root@oracle rabbitmq]# find / -name rabbitmq.config.example
/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example
[root@oracle rabbitmq]# cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
5、打开来宾账号权限
将红框部分内容的前面的 %% 注释删掉,后面有个多余的逗号也删掉
6、启动rabbitmq的后台管理程序
是rabbitmq的一个插件,用于后台管理rabbitmq。
执行命令:
[root@oracle rabbitmq]# rabbitmq-plugins enable rabbitmq_management
warning: the VM is running with native name encoding of latin1 which may cause Elixir to malfunction as it expects utf8. Please ensure your locale is set to UTF-8 (which can be verified by running "locale" in your shell)
Enabling plugins on node rabbit@oracle:
rabbitmq_management
The following plugins have been configured:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@oracle...
The following plugins have been enabled:
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
set 3 plugins.
Offline change; changes will take effect at broker restart.
7、启动rabbitmq服务
[root@oracle rabbitmq]# rabbitmq-server
## ##
## ## RabbitMQ 3.7.18. Copyright (C) 2007-2019 Pivotal Software, Inc.
########## Licensed under the MPL. See https://www.rabbitmq.com/
###### ##
########## Logs: /var/log/rabbitmq/[email protected]
/var/log/rabbitmq/rabbit@oracle_upgrade.log
Starting broker...
completed with 3 plugins.
8、查看进程状态
[root@oracle /]# service rabbitmq-server status
...
9、关闭防火墙
[root@oracle ~]# service iptables status
iptables: Firewall is not running.
10、可以访问web管理页面
http://ip/15672/
至于为什么是这个端口呢,我也不知道,,,也不是在那份配置文件里面的,但是查看进程状态可以看到监听三个端口里面有一个是15672。
username: guest
password: guest
service rabbitmq-server start|restart|stop|status
可以操作全部web管理页面的内容
rabbitmqctl help 查看全部命令
rabbitmq-plugins enable|list|disable
connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况
channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
Exchanges:交换机,用来实现消息的路由
Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。
上面的Tags选项,其实是指定用户的角色,可选的有以下几个:
超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
其他
无法登陆管理控制台,通常就是普通的生产者和消费者。
# 虚拟主机
为了让各个用户可以互不干扰的工作,RabbitMQ添加了虚拟主机(Virtual Hosts)的概念。其实就是一个独立的访问路径,不同用户使用不同路径,各自有自己的队列、交换机,互相不会影响。
创建好虚拟主机,我们还要给用户添加访问权限:
点击添加好的虚拟主机:
进入虚拟机设置界面:
生产者通过通道与RabbitMQ服务的虚拟主机建立连接,发送消息。而消费者通过通道与RabbitMQ服务的虚拟主机建立连接,获取消息。
官方文档:https://www.rabbitmq.com/getstarted.html
docs->get started
3.5的rabbit支持前四种
3.7的rabbit支持前六种
3.8的rabbit支持前七种
新建项目,导入依赖
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.7.3version>
dependency>
创建虚拟主机
创建专属账号
场景举例:注册服务中的发短信验证码服务,注册在A系统,而发短信在B系统。
生产者
/**
* 生产者
* @author: stone
* @create: 2020-09-09 00:23
*/
public class Privider {
@Test
public void send() throws IOException, TimeoutException {
//工厂类
ConnectionFactory connectionFactory = new ConnectionFactory();
//rabbitmq服务的ip
connectionFactory.setHost("192.168.1.181");
//端口 5+6+7+2=20
connectionFactory.setPort(5672);
//虚拟主机
connectionFactory.setVirtualHost("ssx");
//账号密码
connectionFactory.setUsername("ssx");
connectionFactory.setPassword("123");
//然后就可以建立连接
Connection connection = connectionFactory.newConnection();
//握手成功建立通道
Channel channel = connection.createChannel();
/**
* 通道绑定消息队列
* 1、队列名称,如果不存在则创建
* 2、队列是否拥有持久化特性,true持久化
* 3、是否独占队列,true独占:别的通道不可访问
* 4、是否在消费者消费完成后删除队列
* 5、额外参数
*/
channel.queueDeclare("hello",false,false,false,null);
//可以发送消息了
/**
* 1、交换机名称
* 2、队列名称
* 3、额外设置
* 4、消息内容
*/
channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
//发送完,关闭资源
channel.close();
connection.close();
}
}
在看到管理界面,此时Queues列表自动多了一条队列,并且有一条空闲状态的消息
消费者
/**
* 消费者
* @author: stone
* @create: 2020-09-09 00:54
*/
public class Consumer {
public static void main(String args[]) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.181");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("ssx");
connectionFactory.setUsername("ssx");
connectionFactory.setPassword("123");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//绑定通道
channel.queueDeclare("hello",false,false,false,null);
/**
* 消费消息
* 1、队列名
* 2、消息自动确认
* 3.消费回调函数接口,里面有各种方法可以重写来处理各种事件
*/
channel.basicConsume("hello",true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("读取消息:"+new String(body));
}
});
/**
* 消费者一般不主动关闭通道
* 有时候关闭代码立刻执行了,但读取消息的回调还未执行,此时将不会执行读取回调的方法
*/
// channel.close();
// connection.close();
}
}
console
读取消息:hello rabbitmq
ps:消费者代码没用@Test,因为使用测试注解运行的话,当代码执行完会立刻关闭线程,关闭的速度比回调方法的执行还快,,,
工厂类是不需要每次都创建的,所以换成静态变量
/**
* rabbitmq连接工具类
* @author: stone
* @create: 2020-09-09 22:02
*/
public class RabbitMQUtils {
private static ConnectionFactory connectionFactory;
static{
//工厂
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.1.181");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("ssx");
connectionFactory.setUsername("ssx");
connectionFactory.setPassword("123");
}
/**
* 获取rabbitmq连接
* @return
*/
public static Connection getConnection(){
try {
return connectionFactory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return null;
}
/**
* 关闭通道、连接
* @param connection
* @param channel
*/
public static void closeConnection(Connection connection, Channel channel){
try {
if(channel!=null) channel.close();
if(connection!=null) connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
/**
* 通道绑定消息队列
* 1、队列名称,如果不存在则创建
* 2、队列是否拥有持久化特性,true持久化
* 3、是否独占队列,true独占:别的通道不可访问
* 4、是否在消费者消费完成后删除队列
* 5、额外参数
*/
channel.queueDeclare("hello",false,false,false,null);
/**
* 1、交换机名称
* 2、队列名称
* 3、额外设置
* 4、消息内容
*/
channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes());
这里的第一个参数是指队列名,如果不存在则创建,但真正发送消息的方法是第二行代码
channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
也就是说,一个通道可以声明A队列,但是发送消息给B队列!
第二个参数:durable,设置为true则此序列为可持久化序列。当rabbitMQ重启后,此通道会被反序列化回来。但是,队列中的消息不会跟着序列化。
也就是说,Durable参数仅仅是设置此队列是否需要持久化!
代码实现:
channel.queueDeclare("canDurable",true,false,false,null);
控制界面:(多了个蓝色D标志)
在推送消息的代码上,第三个参数,需要一个BasicProperties类型的参数
这是一个在AMQP中的内部类,里面包含了很多参数,对应各种设置(看得我很不爽)
然后呢,Rabbit还提供了另一个封装好BasicProperties的类让我们直接使用
MessageProperties这个类其实就是把一些对应各种常用场景的参数配置上,直接调用,所以一般情况下用这里面的六种类型应该就可以了,那么问题就变成我们对这六种类型的解析和掌握了。(我没有继续看下去了,,,以后会有机会的)
其中!MessageProperties.PERSISTENT_TEXT_PLAIN 这个东东可以让消息持久化!
也就是这样:
channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes());
声明队列的代码中,第四个参数可以声明此队列是否可以被自动删除。
注意这里的自动删除是指当 队列中的消息被消费完了,并且所有与此队列有关的通道都关闭了,才会被rabbit服务自动删除。
代码测试:
channel.queueDeclare("canAutoDelete",false,false,true,null);
控制界面:
Work queues,也被称为 Task queues 任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
角色:
跟直连一样
/**
* @author: stone
* @create: 2020-09-09 23:14
*/
public class Provider {
@Test
public void send() throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("workQueue",true,false,false,null);
//发送多条消息
for (int i = 0; i < 15; i++) {
channel.basicPublish("","workQueue",null,("hello work queues no."+i).getBytes());
}
RabbitMQUtils.closeConnection(connection,channel);
}
}
定义了两个消费者,代码一样
/**
* 消费者1
* @author: stone
* @create: 2020-09-09 23:33
*/
public class Comsumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("workQueue",true,false,false,null);
channel.basicConsume("workQueue",true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
}
}
消费者1 console
消费者2 console
消息被平均分配给了两个消费者,这不是巧合!
官方说明:
默认情况下,会执行循环往复这种机制分配消息。
这是因为当前消费者的消费方法中,使用了自动确认消息:
生产消费模型中,队列根据当前连接的通道的确认类型进行判断,如果开启自动确认消息,会快速的将消息平均分配给每一位消费者,哪怕消费者执行的速度很慢,可这时消息已经都分发下去了。
打个比方,当前队列有三个消费者连接着,都开启了 autoAck 自动确认消息,这时生产者推送了15条消息到这个队列,队列会把15条依次分发给三个消费者,consumer1=5;consumer2=5;consumer3=5;而不管这5个任务是否可以被消费者立即处理!也就是说,当consumer1执行到第二个任务时,如果机器宕机,那么另外3条未处理的任务将会丢失!
所以这种自动确认往往是不开启的。
那么就会有另一种更合理安全的机制代替:消息手动确认
改造消费者代码:
/**
* 消费者1
* @author: stone
* @create: 2020-09-09 23:33
*/
public class Comsumer1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("workQueue",true,false,false,null);
/**
* 设置每次接收的消息个事
*/
channel.basicQos(1);
/**
* autoAck 参数设置为 false:不自动确认消息
*/
channel.basicConsume("workQueue",false, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
try {
//执行业务代码
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 手动确认消息
* 1、消息的标签,告诉队列,这次要确认的是哪个具体的消息。然后队列会将这个消息标记为已读,由rabbit服务删除
* 2、是否开启多个消息的确认
*/
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
consumer1 console:
hello work queues No.1
consumer2 console:
hello work queues No.0
hello work queues No.2
hello work queues No.3
hello work queues No.4
hello work queues No.5
hello work queues No.6
hello work queues No.7
hello work queues No.8
hello work queues No.9
hello work queues No.10
hello work queues No.11
hello work queues No.12
hello work queues No.13
hello work queues No.14
按劳分配,能者多劳。