rabbitmq基础
目标
- RabbitMq是什么?
- RabbitMq能解决什么问题?
- RabbitMq怎么用的?
- RabbitMq模式
- 安装RabbitMq
- 掌握springboot整合RabbitMq
什么是MQ
MQ
(Message Quene) : 翻译为 消息队列
,通过典型的 生产者
和消费者
模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统间解耦。别名为 消息中间件
通过利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。
MQ有哪些
当今市面上有很多主流的消息中间件,如老牌的ActiveMQ
、RabbitMQ
,炙手可热的Kafka
,阿里巴巴自主开发RocketMQ
等。
AMQP和JMS
MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。
两者间的区别和联系:
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模型;而AMQP的消息模型更加丰富
JMS是一种规范,只限定java语言,从API层进行限定,类比于JDBC
AMQP是一种协议,不限定语言,它兼容JMS,类比于http协议
MQ的作用
1、解耦
场景说明:用户下单后,订单系统需要通知库存系统
-
传统做法
- 传统的做法是,订单系统调用库存系统的接口。如下图:
- 传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合
- 如何解决以上问题呢?
- 传统的做法是,订单系统调用库存系统的接口。如下图:
-
使用消息队列
- 引入应用消息队列后的方案,如下图:
- 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功
- 库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作
- 在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦
- 引入应用消息队列后的方案,如下图:
2、异步
场景说明:用户注册后,需要发注册邮件和注册短信
-
传统做法
-
a.串行:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信
-
b.并行:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信
-
-
使用消息队列
- 将不是必须的业务逻辑,异步处理。改造后的架构如下:
- 将不是必须的业务逻辑,异步处理。改造后的架构如下:
3、流量削峰
场景说明:商品秒杀业务,一般会因为流量过大,导致流量暴增,应用挂掉
- 传统做法
- 限制用户数量
- 使用消息队列
-
用户的请求,服务器接收后,首先写入消息队列,秒杀业务根据消息队列中的请求信息,再做后续处理
-
假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。
-
4、日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题
- 使用消息队列完成日志处理
- 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
- Kafka消息队列,负责日志数据的接收,存储和转发
- 日志处理应用:订阅并消费kafka队列中的日志数据
MQ的具体产品
`1、RabbitMq`
+ ERLANG语言 用起来环境要求高
+ 吞吐量 5W级别
+ 消息的延迟级别 WS
+ 支持很多协议
+ 消息会丢失(少)
`kafka` apache
+ 吞吐量特别高 可以到100W/S
+ 高堆积能力(亿级)
+ 会丢失消息数据
+ 消息的延迟级别 MS
+ 在日志领域里使用比较多
`rocketmq` apache alibaba
+ JAVA
+ 吞吐量很高(50w/s)
+ 高堆积能力(亿级)
+ 在线扩容能力很强
+ 理论上不丢失
+ 支持事务消息
+ 可以实现消费消息的顺序性
+ 消息的延迟级别 MS
`activemq `apache
+性能差
+消息丢失很严重
+ spring
RabbitMq的组件介绍
Server:Broker、RabbitMQ Server,实现 AMOP 实体服务,接受客户端的连接
Conneciton:链接,应用程序与 Server 的网络连接
Channel:网络信道,进行消息读写的通道,客户端可以建立多个 Channel,每个 Channel 就是一个会话
Message:消息,服务器和应用程序之间传输的数据,由 Properties 和 Body 组成。Properties 用于修饰消息,比如消息优先级、延迟等,Body 是消息体
Virtual host:虚拟地址,用于逻辑隔离,是最上层的路由。一个虚拟地址中可以有多个 Exchange 和 Queue,但不允许同名
Exchange:交换机,用于接收生产者的消息,根据 Routing key 转发到 Queue
Queue:Message queue,消息队列,保存消息并转发给消费者,消费者监听这个队列达到接收消息的目的
Bingding:Exchange 和 Queue 之间的虚拟连接,可以包含多个 Routing key
端口:
5672: rabbitMq的编程语言客户端连接端口
15672:rabbitMq管理界面端口
25672:rabbitMq集群的端口
RabbitMq的消息模式
1、简单模式
2、工作模式
work
3、发布订阅模式
+ 广播模式 fanout
+ 路由模式 direct
+ 通配符模式 route
简单模式
在上图的模型中,有以下概念:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
pom
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
开发生产者
public class provider {
//消息生产者
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置RabbitMq服务主机地址默认localhost
connectionFactory.setHost("localhost");
//设置RabbitMq服务端口,默认5672
connectionFactory.setPort(5672);
//设置虚拟主机名称,默认/
connectionFactory.setVirtualHost("/");
//设置用户连接名,默认guest
connectionFactory.setUsername("guest");
//设置连接密码,默认guest
connectionFactory.setPassword("guest");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
* **/
channel.queueDeclare("simple_queue", true, false, false, null);
//创建消息
String message = "hello!welcome to itheima";
/**
* 消息发送
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("", "simple_queue", null, message.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
开发消费者
//简单模式接受消息
public class consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建链接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置RabbitMQ服务主机地址,默认localhost
connectionFactory.setHost("localhost");
//设置RabbitMQ服务端口,默认5672
connectionFactory.setPort(5672);
//设置虚拟主机名字,默认/
connectionFactory.setVirtualHost("/");
//设置用户连接名,默认guest
connectionFactory.setUsername("guest");
//设置链接密码,默认guest
connectionFactory.setPassword("guest");
//创建链接
Connection connection = connectionFactory.newConnection();
//创建频道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("simple_queue",true,false,false,null);
//创建消费者,并设置消息处理(实现业务逻辑 比如:订单(生产者)发送消息, 库存(消费者)扣减库存)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* @param consumerTag 消费者名称 如果之前没有指定 则自己生成一个
* @param envelope 就是消息的一些额外参数 比如exchange routing key等数据
* @param properties 另外的数据所封装的属性对象
* @param body 消息本身
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//自己实现消费的业务逻辑
//路由的key
String routingKey = envelope.getRoutingKey();
//获取交换机信息
String exchange = envelope.getExchange();
//获取消息ID
long deliveryTag = envelope.getDeliveryTag();
//获取消息信息
String message = new String(body,"UTF-8");
System.out.println("routingKey:"+routingKey+",exchange:"+exchange+",deliveryTag:"+deliveryTag+",message:"+message);
}
};
//消息监听
//参数1 指定要监听的队列名称
//参数2 设置消息的应答模式 true 自动应答 false 手动应答(消费者手动ACK)
channel.basicConsume("simple_queue",true,defaultConsumer);
//关闭资源(不建议关闭,建议一直监听消息)
}
}
参数说明
channel.queueDeclare("simple_queue",true,false,false,null);
'参数1':用来声明通道对应的队列
'参数2':用来指定是否持久化队列
'参数3':用来指定是否独占队列
'参数4':用来指定是否自动删除队列
'参数5':对队列的额外配置
第二种模型(work quene)
Work queues
,也被称为(Task queues
),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
角色:
- P:生产者:任务的发布者
- C1:消费者-1,领取任务并且完成任务,假设完成速度较慢
- C2:消费者-2:领取任务并完成任务,假设完成速度快
首先写一个工具类
public class ConnectionUtil {
/***
* 创建链接对象
* @return
* @throws IOException
* @throws TimeoutException
*/
public static Connection getConnection() throws IOException, TimeoutException {
//创建链接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置RabbitMQ服务主机地址,默认localhost
connectionFactory.setHost("localhost");
//设置RabbitMQ服务端口,默认5672
connectionFactory.setPort(5672);
//设置虚拟主机名字,默认/
connectionFactory.setVirtualHost("/");
//设置用户连接名,默认guest
connectionFactory.setUsername("guest");
//设置链接密码,默认guest
connectionFactory.setPassword("guest");
//创建链接
Connection connection = connectionFactory.newConnection();
return connection;
}
}
生产者
//work工作模式
public class Provider {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 声明队列
* 参数1:队列名称
* 参数2:是否定义持久化队列
* 参数3:是否独占本次连接
* 参数4:是否在不使用的时候自动删除队列
* 参数5:队列其它参数
* **/
channel.queueDeclare("work_queue", true, false, false, null);
for (int i = 0; i < 20; i++) {
//创建消息
String message = "hello world work" + i;
/**
* 消息发送
* 参数1:交换机名称,如果没有指定则使用默认Default Exchage
* 参数2:路由key,简单模式可以传递队列名称
* 参数3:消息其它属性
* 参数4:消息内容
*/
channel.basicPublish("", "work_queue", null, message.getBytes());
}
//关闭资源
channel.close();
connection.close();
}
}
消费者1
//工作模式接受消息
public class consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("work_queue", true, false, false, null);
//创建消费者,并设置消息处理(实现业务逻辑 比如:订单(生产者)发送消息, 库存(消费者)扣减库存)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
* @param consumerTag 消费者名称 如果之前没有指定 则自己生成一个
* @param envelope 就是消息的一些额外参数 比如exchange routing key等数据
* @param properties 另外的数据所封装的属性对象
* @param body 消息本身
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//自己实现消费的业务逻辑
System.out.println("11111111111");
//路由的key
String routingKey = envelope.getRoutingKey();
//获取交换机信息
String exchange = envelope.getExchange();
//获取消息ID
long deliveryTag = envelope.getDeliveryTag();
//获取消息信息
String message = new String(body, "UTF-8");
System.out.println("routingKey:" + routingKey + ",exchange:" + exchange + ",deliveryTag:" + deliveryTag + ",message:" + message);
}
};
//消息监听
//参数1 指定要监听的队列名称
//参数2 设置消息的应答模式 true 自动应答 false 手动应答(消费者手动ACK)
channel.basicConsume("work_queue", true, defaultConsumer);
//关闭资源(不建议关闭,建议一直监听消息)
}
}
消费者2
//work模式接受消息
public class consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
//创建频道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare("work_queue",true,false,false,null);
//创建消费者,并设置消息处理(实现业务逻辑 比如:订单(生产者)发送消息, 库存(消费者)扣减库存)
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* @param consumerTag 消费者名称 如果之前没有指定 则自己生成一个
* @param envelope 就是消息的一些额外参数 比如exchange routing key等数据
* @param properties 另外的数据所封装的属性对象
* @param body 消息本身
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//自己实现消费的业务逻辑
System.out.println("22222222222222");
//路由的key
String routingKey = envelope.getRoutingKey();
//获取交换机信息
String exchange = envelope.getExchange();
//获取消息ID
long deliveryTag = envelope.getDeliveryTag();
//获取消息信息
String message = new String(body,"UTF-8");
System.out.println("routingKey:"+routingKey+",exchange:"+exchange+",deliveryTag:"+deliveryTag+",message:"+message);
}