RabbitMQ学习笔记(基础篇)
文章目录
MQ的概述
- MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
- MQ,消息队列,存储消息的中间件
- 分布式系统通信两种方式:直接远程调用和借助第三方完成间接通信
- 发送方称为生产者,接收方称为消费者
MQ的优势
-
应用解耦
- 系统的耦合性越高,容错性就越低,可维护性就越低。
- 使用MQ使得应用间解耦,提升容错性和可维护性。
- 系统的耦合性越高,容错性就越低,可维护性就越低。
-
异步提速
-
- 不使用mq的访问速度达到900ms的阶段,但是一般来说在200ms之内的响应时间是最好的,这个时间内用户是无感知的状态
- 不使用mq的访问速度达到900ms的阶段,但是一般来说在200ms之内的响应时间是最好的,这个时间内用户是无感知的状态
-
使用MQ的时间消耗
-
削峰填谷
- 不使用mq的情况,当单位时间内的请求数增加的时候,所调用的微服务可能无法处理并发量,导致服务挂掉
- 不使用mq的情况,当单位时间内的请求数增加的时候,所调用的微服务可能无法处理并发量,导致服务挂掉
- 使用了MQ之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。
MQ的劣势
- 系统可用性降低
- 系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响。如何保证MQ的高可用?
- 系统复杂度提高
- MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
- 一致性问题
- A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统敞处理成功,D系统处理生败,如何保证消息数据处理的一致性问题
MQ的使用场景
- 生产者不需要从消费者处获得及馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明
明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。 - 容许短暂的不一致性。
- 确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,
管理MQ这些成本。
常见的MQ产品
- 目前业界有很多的MQ产品,例如RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,
也有直接使用Redis充当消息队列的案例,而这些消息队列品,各有侧重,在实际选型时,需要结合自身需
求及MQ产品特征,综合考虑。 - 产品对比
RabbitMQ的基本概念
AMQP协议
- AMQP协议:AMQP,即Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP规范发布。类比HTTP。
RabbitMQ
- 2007年,Rabbit技术公司基于AMQP标准开发的RabbitMQ1.0发布。RabbitMQ采用Erlang语言开发。Erlang语言由Ericson设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
- Broker:接收和分发消息的应用,RabbitMQ Server就是Message Broker
- Virtual host:出于多租宁和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等
Connection:publisher/consumer和broker之间的TCP连接 - Channel:如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销
- Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去。常用的类里型有:direct(ooint-to-point),topic(publish-subscribe)and fanout(multicast)
- Queue:消息最终被送到这里等待consumer取走
- Binding:exchange和queue之间的虚拟连接,binding中可以包含routing key。.Binding信息被保存到exchange中的查询表中,用于message的分发依据
- 生产模式
- RabbitMQ提供了6种工作模式:简单模式、work queues、Publish,/Subscribe发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。
- RabbitMQ提供了6种工作模式:简单模式、work queues、Publish,/Subscribe发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。
JMS
- JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件的API
- JMS是JavaEE规范中的一种,类比JDBC
- 很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ官方没有提供JMS的实现包,但是开源社区有
RabbitMQ的安装
-
由于RabbitMQ是由erlang编写的所以需要先安装对应版本的erlang
-
点击方框内的按钮
-
点击github可以查看以往版本,点击第二个方框,直接下载最新版本
-
点击erlang version按钮,查看RabbitMQ对应的版本信息,
-
erlang下载地址:https://github.com/rabbitmq/erlang-rpm
-
点击后选择对应的版本进行下载
-
安装过程
-
安装所需要的环境
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
-
上传安装包
-
首先安装erlang
rpm -Uvh erlang-23.4-1.el7.x86_64.rpm
-
使用erl -v查看是否安装成功
-
安装ncurses
yum -y install ncurses-devel
-
安装socat插件
yum install -y socat
–force --nodeps代表暴力安装,忽略rpm包之间的依赖关系
- 安装rabbitmq
# 解压
rpm -Uvh rabbitmq-server-3.9.14-1.el7.noarch.rpm
#安装
yum install -y rabbitmq-server
- 启动服务
# 启动rabbitmq
systemctl start rabbitmq-server
# 查看rabbitmq状态
systemctl status rabbitmq-server
- 安装rabbitmq远程管理届满
rabbitmq-plugins enable rabbitmq_management
注意:erlang-24.3-1.el8.x86_64.rpm中的el8是centos8,在centos7系统上安装erlang与rabbitmq需要el7版本
- 在安装完成后,发现没有rabbitmq.conf配置文件,需要在官网上复制示例文件
rabbitmq.conf.example
在etc/rabbitmq目录下新建rabbitmq.conf文件,重启服务
RabbitMQ控制台
注意:给rabbitmq添加用户 https://www.cnblogs.com/FengGeBlog/p/13905541.html
RbbitMQ的快速入门
生产者
- 生产者
- 创建连接工厂
- 设置参数:host,port,username,password,viruture host(虚拟主机)
- 建立连接
- 建立管道
- 设置队列,如果没有则创建,如果存在,不创建
- 发送消息
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建队列
/*
* String queue, 队列的民称
* boolean durable, mq重启后,仍然存在
* boolean exclusive,是否独占连接,只能有一个消费者监听,connection关闭时是否删除对列
* boolean autoDelete, 是否自动删除,没有consumer时自动删除
* Map<String, Object> arguments 其他的参数
*
* */
// 如果队列不存在,则创建
channel.queueDeclare("one", true, false, false, null);
// 发送消息
/*
* String exchange, 交换机民称,默认情况下使用""
* String routingKey, 路由地址,发送到哪个队列
* BasicProperties props,配置信息
* BasicProperties props, byte[] body 发送的信息
*String exchange, , byte[] body
* */
channel.basicPublish("", "one", null, "hello,world".getBytes());
System.out.println("信息发送成功");
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消费者
- 生产者
- 创建连接工厂
- 设置参数:host,port,username,password,viruture host(虚拟主机)
- 建立连接
- 建立管道
- 设置队列,如果没有则创建,如果存在,不创建
- 监听消息
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建队列
/*
* String queue, 队列的民称
* boolean durable, mq重启后,仍然存在
* boolean exclusive,是否独占连接,只能有一个消费者监听,connection关闭时是否删除对列
* boolean autoDelete, 是否自动删除,没有consumer时自动删除
* Map<String, Object> arguments 其他的参数
*
* */
channel.queueDeclare("one", true, false, false, null);
// 监听消息
/*
* String queue, 监听的队列名称
* boolean autoAck, 是否给到消费者回应
* DeliverCallback deliverCallback,回调函数
* */
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("收到的消息是:" + new String(delivery.getBody()));
Envelope envelope = delivery.getEnvelope();
System.out.println("交换机是:" + envelope.getExchange());
System.out.println("路由信息是:" + envelope.getRoutingKey());
System.out.println("消息的序号是:" + envelope.getDeliveryTag());
System.out.println("s是:" + s);
System.out.println("=======================================");
}
};
channel.basicConsume("one", deliverCallback, (CancelCallback) null);
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
}
// 一直存在监听的状态
while (true) {
}
RabbitMQ的工作模式
WorkQueues
- 与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
- 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
- Work Queues与入门程序的简单模式的代码几乎是一样的。可以完全复制,并多复制一个消费者进行多
个消费者同时对消费消息的测试。
- 生产者,连续发送30条消息
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建队列
/*
* String queue, 队列的民称
* boolean durable, mq重启后,仍然存在
* boolean exclusive,是否独占连接,只能有一个消费者监听,connection关闭时是否删除对列
* boolean autoDelete, 是否自动删除,没有consumer时自动删除
* Map<String, Object> arguments 其他的参数
*
* */
channel.queueDeclare("WorkQueues", true, false, false, null);
// 发送消息
/*
* String exchange, 交换机民称,默认情况下使用""
* String routingKey, 路由地址,发送到哪个队列
* BasicProperties props,配置信息
* BasicProperties props, byte[] body 发送的信息
*String exchange, , byte[] body
* */
// 发送30条消息
for (int i=0;i<30;i++){
channel.basicPublish("", "WorkQueues", null, (i+"hello,workqueues").getBytes());
}
System.out.println("信息发送成功");
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
- 消费者
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建队列
/*
* String queue, 队列的民称
* boolean durable, mq重启后,仍然存在
* boolean exclusive,是否独占连接,只能有一个消费者监听,connection关闭时是否删除对列
* boolean autoDelete, 是否自动删除,没有consumer时自动删除
* Map<String, Object> arguments 其他的参数
*
* */
channel.queueDeclare("WorkQueues", true, false, false, null);
// 监听消息
/*
* String queue, 监听的队列名称
* boolean autoAck, 是否给到消费者回应
* DeliverCallback deliverCallback,回调函数
* */
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("收到的消息是:" + new String(delivery.getBody()));
// Envelope envelope = delivery.getEnvelope();
// System.out.println("交换机是:" + envelope.getExchange());
// System.out.println("路由信息是:" + envelope.getRoutingKey());
// System.out.println("消息的序号是:" + envelope.getDeliveryTag());
// System.out.println("s是:" + s);
// System.out.println("=======================================");
}
};
channel.basicConsume("WorkQueues", deliverCallback, (CancelCallback) null);
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
}
// 一直存在监听的状态
while (true) {
}
- 运行结果1
- 运行结果2
Pub/Sub订阅模式(广播:BuiltinExchangeType.FANOUT)
- 生产者
- 创建连接工厂
- 设置参数:host,port,username,password,viruture host(虚拟主机)
- 建立连接
- 建立管道
- 创建交换机
- 设置两个队列,如果没有则创建,如果存在,不创建
- 队列与交换机绑定
- 发送消息到交换机
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建交换机
/*
* String exchange, 交换机的名称
* BuiltinExchangeType type, 传播的类型
* DIRECT("direct"),
FANOUT("fanout"),不需要指定路由key
TOPIC("topic"),
HEADERS("headers");
* boolean durable, 是否持久化
* boolean autoDelete, 是否自动删除,没有consumer监听时删除
* boolean internal, 是否内部使用,默认为false
* Map<String, Object> arguments,其他参数
*
*
* */
channel.exchangeDeclare("pubsub", BuiltinExchangeType.FANOUT, true, false, false, null);
// 创建队列
channel.queueDeclare("first", true, false, false, null);
channel.queueDeclare("sencend", true, false, false, null);
// 绑定交换机与队列
/*
* String queue,
* String exchange,
* String routingKey 路由规则
* 如果是广播类型路由,则不需要指定路由规则
* */
channel.queueBind("first", "pubsub", "");
channel.queueBind("sencend", "pubsub", "");
// 发送消息
String log = "日志是:findall方法-------";
channel.basicPublish("pubsub", "", false, null, log.getBytes());
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
- 消费者
消费者监听的消息队列
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
Connection connection = null;
Channel channel = null;
try {
// 设置参数
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");//设置用户名密码
factory.setVirtualHost("/domo"); // 设置虚拟主机
// 创建连接
connection = factory.newConnection();
// 创建管道
channel = connection.createChannel();
// 创建队列
/*
* String queue, 队列的民称
* boolean durable, mq重启后,仍然存在
* boolean exclusive,是否独占连接,只能有一个消费者监听,connection关闭时是否删除对列
* boolean autoDelete, 是否自动删除,没有consumer时自动删除
* Map<String, Object> arguments 其他的参数
*
* */
channel.queueDeclare("first", true, false, false, null);
// 监听消息
/*
* String queue, 监听的队列名称
* boolean autoAck, 是否给到消费者回应
* DeliverCallback deliverCallback,回调函数
* */
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("收到的消息是:" + new String(delivery.getBody()));
// Envelope envelope = delivery.getEnvelope();
// System.out.println("交换机是:" + envelope.getExchange());
// System.out.println("路由信息是:" + envelope.getRoutingKey());
// System.out.println("消息的序号是:" + envelope.getDeliveryTag());
// System.out.println("s是:" + s);
System.out.println("----------写入数据库中------");
// System.out.println("=======================================");
}
};
channel.basicConsume("first", deliverCallback, (CancelCallback) null);
// 关闭连接
} catch (Exception e) {
e.printStackTrace();
}
// 一直存在监听的状态
while (true) {
}
路由模式( BuiltinExchangeType.DIRECT)
其余部分同上面相同,唯一变化的就是指定了路由地址
channel.exchangeDeclare("pubsub", BuiltinExchangeType.DIRECT, true, false, false, null);
=======================================================
channel.queueBind("first", "pubsub", "f");
channel.queueBind("sencend", "pubsub", "s");
// 发送消息
String log = "日志是:findall方法-------";
channel.basicPublish("pubsub", "s", false, null, log.getBytes());
topic模式
- Topic主题模式可以实现Pub/Sub发布与订阅模式和Routing路由模式的功能,只是Topic在配置routing key的时候可以使用通配符,显得更加灵活。
- Topic模式下,#代表多个字符,*代表一个字符
- 生产者
ConnectionFactory factory=new ConnectionFactory();
factory.setVirtualHost("/domo");
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String exchange = "TocpicDomo";
channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC,true,false,false,null);
String quenename1="TocpicQueneFirst";
String quenename2="TocpicQueneSecond";
channel.queueDeclare(quenename1,true,false,false,null);
channel.queueDeclare(quenename2,true,false,false,null);
channel.queueBind(quenename1,exchange,"#.error");
channel.queueBind(quenename2,exchange,"#.log");
channel.queueBind(quenename2,exchange,"#.info");
channel.queueBind(quenename2,exchange,"#.error");
// 发送消息
channel.basicPublish(exchange,"order.info",false,null,"info级别的日志信息".getBytes());
channel.close();
connection.close();
- 消费者
ConnectionFactory factory=new ConnectionFactory();
factory.setVirtualHost("/domo");
factory.setHost("192.168.253.129");
factory.setPort(5672);
factory.setUsername("lmx");
factory.setPassword("123456");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String quenename1="TocpicQueneFirst";
String quenename2="TocpicQueneSecond";
channel.queueDeclare(quenename1,true,false,false,null);
// channel.queueDeclare(quenename2,true,false,false,null);
DeliverCallback deliverCallback = new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println(new String(delivery.getBody()));
System.out.println("-----写入数据库------");
}
};
channel.basicConsume(quenename1, false, deliverCallback, (CancelCallback) null);
}
- 运行结果
SpringBoot整合RabbitMQ
整合生产者
- 引入mavne依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 配置基本信息
spring:
rabbitmq:
port: 5672
host: 192.168.253.129
virtual-host: /domo
username: lmx
password: 123456
- 定义配置类
@Configuration
public class RabbitMQConfigure {
// 使用Tocpic模式进行联系
public static String TocpicExchangeName = "TocpicExchange";
public static String quneq1="quneq1";
public static String quneq2="quneq2";
// 创建交换机
@Bean
public Exchange TocpicExchange() {
return ExchangeBuilder.topicExchange(TocpicExchangeName).durable(true).build();
}
@Bean
public Queue queue1() {
return QueueBuilder.durable(quneq1).build();
}
@Bean
public Queue queue2() {
return QueueBuilder.durable(quneq2).build();
}
@Bean
public Binding ExchangeBind(Exchange TocpicExchange,Queue queue1){
return BindingBuilder
.bind(queue1)
.to(TocpicExchange)
.with("#.error")
.and(null);
}
@Bean
public Binding ExchangeBind2(Exchange TocpicExchange,Queue queue2){
return BindingBuilder
.bind(queue2)
.to(TocpicExchange)
.with("#.info")
.and(null);
}
@Bean
public Binding ExchangeBind3(Exchange TocpicExchange,Queue queue2){
return BindingBuilder
.bind(queue2)
.to(TocpicExchange)
.with("#.warning")
.and(null);
}
}
- 发送消息
@Resource
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
String s="error级别的数据";
rabbitTemplate.convertAndSend(RabbitMQConfigure.TocpicExchangeName,"order.error",s);
}
整合消费者
- 导入mavne依赖与生产者端相同
- yml配置,与生产者相同
- 定义监听类
- 给监听方法添加@RabbitListener注解,通过queues参数配置监听的队列,方法的参数是Message对象,传递的消息参数在Message对象中
@Component
public class RabbotListenser {
@RabbitListener(queues = "quneq1")
public void Lister(Message message){
System.out.println("error级别的消息是"+new String(message.getBody()));
System.out.println("交换机"+message.getMessageProperties().getReceivedExchange());
System.out.println("路由信息"+message.getMessageProperties().getReceivedRoutingKey());
System.out.println("============================");
}
@RabbitListener(queues = "quneq2")
public void Lister2(Message message){
System.out.println("info或waring级别的消息是"+new String(message.getBody()));
System.out.println("交换机"+message.getMessageProperties().getReceivedExchange());
System.out.println("路由信息"+message.getMessageProperties().getReceivedRoutingKey());
System.out.println("============================");
}
}