介绍
RabbitMQ是一个消息代理:它接受并转发消息。你可以把它当成一个邮局:当你想邮寄信件的时候,你会把信件放在投递箱中,并确信邮递员最终会将信件送到收件人的手里。在这个例子中,RabbitMQ就相当与投递箱、邮局和邮递员。
RabbitMQ与邮局的区别在于:RabbitMQ并不处理纸质信件,而是接受、存储并转发二进制数据---消息。
安装
docker安装rabbitmq:
https://www.jianshu.com/p/14ffe0f3db94
docker镜像集群
https://blog.csdn.net/qq_40378034/article/details/89788708
使用场景
他就是服务器之间通信的,相对于其他通信在中间做了一个中间仓库。这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
与其他队列的对比
https://www.cnblogs.com/mengchunchen/p/9999774.html
相关名称
1. 生产者: 在现实生活中就好比制造商品的工厂,他们是商品的生产者。生产者只意味着发送。发送消息的程序称之为一个生产者。
2. 队列:rabbitMQ就像一个仓库,一个仓库里面可以 有很多队列,每个队列才是服务器之间消息通信的载体。
3.消费者:消费者就好比是从商店购买或从仓库取走商品的人,消费的意思就是接收。消费者是一个程序,主要是等待接收消息。
4.交换器:在生产者和消息队列之间的交换器,功能类似于网络宽带的交换机,可以根据不同的关键字,将信息发送到不同的队列。
在RabbitMQ中,生产者不是直接将消息发送给消费者,生成者根本不知道这个消息要传递给哪些队列。实际上,生产者只是将消息发送到交换机。交换机收到消息到,根据交换机的类型和配置来处理消息,有如下几种情况:
- 将消息传送到特定的队列
- 有可能发送到多个队列中
- 也有可能丢弃消息
在声明交换机时还可以附带许多其他的属性,其中最重要的几个分别是:
Name:交换机名称
Durability:是否持久化。如果持久性,则RabbitMQ重启后,交换机还存在
Auto-delete:当所有与之绑定的消息队列都完成了对此交换机的使用后,删掉它
Arguments:扩展参数
上图的E就是交换器,通过关键字绑定,如果生产者给的消息中指定类型是ERROR,就给队列1,如果是INFO或者WARN就给队列2。当然也可以一个关键字绑定两个队列。(INFO等字段自己可以定义,也可以用*,#来匹配。*(星号)表示一个单词#(井号)表示零个或者多个单词。 比如ok.yes可以被ok.*匹配到)
5.临时队列:根据需求临时创建的一条队列,在断开连接后自动删除。
流程介绍
生产者发送一条消息给交换机——交换机根据关键字匹配到对应的队列——将消息存入队列——消费者从队列中取出消息使用
交换机的类型:
交换机主要包括如下4种类型:
- Direct exchange(直连交换机)
- Fanout exchange(扇型交换机)
- Topic exchange(主题交换机)
- Headers exchange(头交换机)
另外RabbitMQ默认定义一些交换机:
- 默认交换机
- amq.* exchanges
还有一类特殊的交换机:Dead Letter Exchange(死信交换机)
Direct exchange直连交换机(使用例子:https://blog.csdn.net/vbirdbest/article/details/78596426)
直连型交换机(direct exchange)是根据消息携带的路由键(routing key)将消息投递给对应队列的,步骤如下:
将一个队列绑定到某个交换机上,同时赋予该绑定一个路由键(routing key)
当一个携带着路由值为R的消息被发送给直连交换机时,交换机会把它路由给绑定值同样为R的队列。
Fanout exchange扇型交换机(使用例子:https://blog.csdn.net/vbirdbest/article/details/78628659)
扇型交换机(funout exchange)将消息路由给绑定到它身上的所有队列。不同于直连交换机,路由键在此类型上不启任务作用。如果N个队列绑定到某个扇型交换机上,当有消息发送给此扇型交换机时,交换机会将消息的发送给这所有的N个队列
Topic exchange主题交换机(使用例子:https://blog.csdn.net/vbirdbest/article/details/78631035)
主题交换机(topic exchanges)中,队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列。
扇型交换机和主题交换机异同:
对于扇型交换机路由键是没有意义的,只要有消息,它都发送到它绑定的所有队列上
对于主题交换机,路由规则由路由键决定,只有满足路由键的规则,消息才可以路由到对应的队列上
Headers exchange头交换机(使用例子:https://blog.csdn.net/vbirdbest/article/details/78638988)
类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。
此交换机有个重要参数:”x-match”
当”x-match”为“any”时,消息头的任意一个值被匹配就可以满足条件
当”x-match”设置为“all”的时候,就需要消息头的所有值都匹配成功
RabbitMQ默认定义一些交换机
在RabbitMQ默认定义一些交换机,主要如下:
默认交换机
默认交换机(default exchange)实际上是一个由RabbitMQ预先声明好的名字为空字符串的直连交换机(direct exchange)。它有一个特殊的属性使得它对于简单应用特别有用处:那就是每个新建队列(queue)都会自动绑定到默认交换机上,绑定的路由键(routing key)名称与队列名称相同。
如:当你声明了一个名为”hello”的队列,RabbitMQ会自动将其绑定到默认交换机上,绑定(binding)的路由键名称也是为”hello”。因此,当携带着名为”hello”的路由键的消息被发送到默认交换机的时候,此消息会被默认交换机路由至名为”hello”的队列中。即默认交换机看起来貌似能够直接将消息投递给队列,如同我们之前文章里看到一例子。
类似amq.*的名称的交换机
这些是RabbitMQ默认创建的交换机。这些队列名称被预留做RabbitMQ内部使用,不能被应用使用,否则抛出403 (ACCESS_REFUSED)错误
@RabbitListener 与 @RabbitHandler
http://www.imooc.com/article/274470
RabbitMQ之消息确认机制(事务+Confirm)
https://blog.csdn.net/u013256816/article/details/55515234
https://www.cnblogs.com/wangiqngpei557/p/9381478.html
RabbitMQ消息确认机制之消息的正确消费
消费者的ack方式默认是自动的,也就是说消息一旦被消费(无论是否处理成功),消息都会被确认,然后会从队列中删除。这就意味着当消息处理失败的时候,也会被从队列中删除,这绝对不是我们所期望的。我们希望当消息正确消费时,消息从队列中删除,否则,消息不能删除,该消息应该继续被消费,直到成功消费。
所以,首先我们将ack的方式设置为手动:
spring:
rabbitmq:
host: xxx.xxx.xxx.xx
port: 5672
username: xxxx
password: xxxx
listener:
direct:
acknowledge-mode: manual # 配置该消费者的ack方式为手动
消费成功后手动确认
处理成功时直接确认,处理失败时,将消息重新放回队列中。
package com.space.rbq.store.consumer;
import com.google.gson.Gson;
import com.rabbitmq.client.Channel;
import com.space.rbq.store.bean.Order;
import com.space.rbq.store.service.StoreService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* 负责接收处理订单服务发送的消息
* @author zhuzhe
* @date 2018/6/7 10:09
* @email 1529949535@qq.com
*/
@Slf4j
@Component
public class OrderConsumer {
@Autowired
private StoreService storeService;
/*对列名称*/
public final String QUEUE_NAME1 = "first-queue";
/**
* queues 指定从哪个队列(queue)订阅消息
* @param message
* @param channel
*/
@RabbitListener(queues = {QUEUE_NAME1})
public void handleMessage(Message message,Channel channel) throws IOException {
try {
// 处理消息
System.out.println("OrderConsumer {} handleMessage :"+message);
// 执行减库存操作
storeService.update(new Gson().fromJson(new String(message.getBody()),Order.class));
/**
* 第一个参数 deliveryTag:就是接受的消息的deliveryTag,可以通过msg.getMessageProperties().getDeliveryTag()获得
* 第二个参数 multiple:如果为true,确认之前接受到的消息;如果为false,只确认当前消息。
* 如果为true就表示连续取得多条消息才发会确认,和计算机网络的中tcp协议接受分组的累积确认十分相似,
* 能够提高效率。
*
* 同样的,如果要nack或者拒绝消息(reject)的时候,
* 也是调用channel里面的basicXXX方法就可以了(要指定tagId)。
*
* 注意:如果抛异常或nack(并且requeue为true),消息会重新入队列,
* 并且会造成消费者不断从队列中读取同一条消息的假象。
*/
// 确认消息
// 如果 channel.basicAck channel.basicNack channel.basicReject 这三个方法都不执行,消息也会被确认
// 所以,正常情况下一般不需要执行 channel.basicAck
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
}catch (Exception e){
log.error("OrderConsumer handleMessage {} , error:",message,e);
// 处理消息失败,将消息重新放回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true);
}
}
}
/*
* 消息的标识,false只确认当前一个消息收到,true确认consumer获得的所有消息
* channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
*
* ack返回false,并重新回到队列
* channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
*
* 拒绝消息
* channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
*
这样,如果处理失败,handleMessage方法就会一直收到这个消息,直到成功消费。
关于ack可以再看看这篇文章https://my.oschina.net/gaoguofan/blog/776057
Message详解
消息。服务器和应用程序之间传送的数据,本质上就是一段数据,由Properties和Payload(body)组成。
Properties(属性)
配置项 | 类型 | 说明 |
---|---|---|
content_type | 短文本 | MIME类型表示消息是一种什么类型的格式,参考MIME类型 |
content_encoding | 短文本 | 正文传输编码,比如内容是gzip压缩的.值就是gzip,参考 |
application_headers | 数组 | 请求的headers信息 |
delivery_mode | 数字 | 表示是否持久化,1为否,2为是 参考 |
priority | 数字 | 发送权重,也就是优先级 |
correlation_id | 短文本 | 相关性ID 参考 |
reply_to | 短文本 | 消息被发送者处理完后,返回回复时执行的回调 |
expiration | 短文本 | 存活时间,毫秒数 |
message_id | 短文本 | 扩展属性 |
timestamp | 数字 | 时间戳 |
type | 短文本 | 扩展属性 |
user_id | 短文本 | 扩展属性 |
app_id | 短文本 | 扩展属性 |
cluster_id | 短文本 | 扩展属性 |
channel参数
channel.exchangeDeclare()
channel.ExchangeDeclare(string exchange: "cjlTest",string type: "direct/topic/header/fanout",bool durable: true);
参数解析:
exchange:交换机名称
type:交换机类型,有fanout、direct、topic、header;选择合适自己的
durable:是否开启持久化exchange。true:服务器重启会保留下来Exchange。警告:仅设置此选项,不代表消息持久化。即不保证重启后消息还在。
autoDelete: 当已经没有消费者时,服务器是否可以删除该exchange
实现真正持久化两步操作
①、将queue的持久化标识durable设置为true,则代表是一个持久的队列
②、发送消息的时候将deliveryMode=2
chanel.basicQos()
channel.basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;
参数解析:
prefetchSize:消息的大小
prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
global:是否将上面设置应用于channel,简单点说,就是上面限制是channel级别的还是consumer级别
channel.basicPublish()
void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body)
throws IOException;
参数解析:
exchange:交换机名称
routingKey:路由键,#匹配0个或多个单词,*匹配一个单词,在topic exchange做消息转发用
mandatory:为true时如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者。为false时出现上述情形broker会直接将消息扔掉
immediate:为true时如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。
props:需要注意的是BasicProperties.deliveryMode,1:不持久化 2:持久化 这里指的是消息的持久化,配合channel(durable=true),queue(durable)可以实现,即使服务器宕机,消息仍然保留
body:要发送的信息
channel.basicAck()
void basicAck(long deliveryTag, boolean multiple) throws IOException;
参数解析
deliveryTag:该消息的index
multiple:是否批量处理.true:将一次性ack所有小于deliveryTag的消息。
channel.basicNack()
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
throws IOException;
参数解析
deliveryTag:该消息的index
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列
channel.basicReject()
void basicReject(long deliveryTag, boolean requeue) throws IOException;
参数解析
deliveryTag:该消息的index
requeue:被拒绝的是否重新入队列
channel.basicNack 与 channel.basicReject 的区别在于basicNack可以拒绝多条消息,而basicReject一次只能拒绝一条消息
channel.basicConsume()
String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;
参数解析
queue:队列名称
autoAck:是否自动ack,如果不自动ack,需要使用channel.ack、channel.nack、channel.basicReject 进行消息应答callback:回调函数,一个事件
chanel.exchangeBind()
用于通过绑定bindingkey讲queue到exchange,之后便可以进行消息接收
Exchange.BindOk exchangeBind(String destination, String source, String routingKey) throws IOException;
channel.queueDeclare()
Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
Map<String, Object> arguments) throws IOException;
durable:true、false true:在服务器重启时,能够存活
exclusive :是否为当前连接的专用队列,在连接断开后,会自动删除该队列,生产环境中应该很少用到吧。
autodelete:当没有任何消费者使用时,自动删除该队列
关于消息顺序与重复消费的问题:https://blog.csdn.net/varyall/article/details/79111745
RabbitAdmin 与 RabbitTemplate 使用
https://www.jianshu.com/p/e647758a7c50
springboot整合rabbitmq
https://www.cnblogs.com/hlhdidi/p/6535677.html
https://blog.51cto.com/13877966/2297056
RabbitMq各个参数含义(new Queue)
https://blog.csdn.net/fsgsggd/article/details/81349553
@RabbitListener源码解析
https://blog.csdn.net/u013905744/article/details/86736536
附:推和拉消费模型对比
RabbitMQ高可用系列之消费失败处理-死信
https://blog.csdn.net/zjcjava/article/details/79410137
延时队列
https://www.cnblogs.com/qjm201000/p/10346471.html
RabbitMQ消息可靠性投递解决方案 - 基于SpringBoot实现
https://www.imooc.com/article/49814
消息确认机制(Confirm模式)
https://blog.csdn.net/anumbrella/article/details/81321701
整理的有点乱
其他入门博客文章:https://blog.csdn.net/lyhkmm/article/details/78775369