RabbitMq简介:是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据RabbitMQ使用ErLang语 言编写的并且是基于AMQP协议的。
RabbitMq安装使用:略
核心概念:
- server:又称为brocker,接受客户端的连接实现AMOP实体服务工作
- connection:应用程序与brocker之间的网络连接
- channel:网络信道,几乎所有的操作都在这里进行,是进行消息读写的通道。客户端可以建立多个通道,每个通道代表一 个会话任务。
- message:消息,服务器和应用程序之间传送的数据,由properties和body组成。properties可以对消息修饰,比如消息的优先级,延迟性等。body就是消息内容。
- virtual host:虚拟主机地址。用于进行逻辑隔离。最上层的消息路由。一个virtual host 里面可以有若干个Exchange 和Queue,同一个virtual host不能有相同名称的Exchange或Queue.
- Exchange:交换机,接受消息,根据路由key转发消息到绑定的队列
- bingding:Exchange和Queue之间的虚拟连接,bingding中可以包含 routing key
- Routing key:一个路由规则,虚拟机用它来确定如何路由一个特定消息
- Queue: 消息队列,保存消息并转发给消费者
死信队列
什么情况下消息会变成死信?
1)消息被消费者拒绝并且未设置重回队列:(NACK || Reject ) && requeue == false
2)消息过期
3)队列达到最大长度,超过了 Max length(消息数)或者 Max length bytes (字节数),最先入队的消息会被发送到 DLX
死信队列如何使用?
1、声明原交换机(ORI_USE_EXCHANGE)、原队列(ORI_USE_QUEUE)相互绑定
队列中的消息 10 秒钟过期,因为没有消费者,会变成死信。指定原队列的死信交换机(DEAD_LETTER_EXCHANGE)
@Bean("oriUseExchange")
public DirectExchange exchange() {
return new DirectExchange("ORI_USE_EXCHANGE", true, false, new HashMap<>());
}
@Bean("oriUseQueue")
public Queue queue() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("x-message-ttl", 10000); // 10 秒钟后成为死信
map.put("x-dead-letter-exchange", "DEAD_LETTER_EXCHANGE"); // 队列中的消息变成 死信后,进入死信交换机
return new Queue("ORI_USE_QUEUE", true, false, false, map);
}
@Bean
public Binding binding(@Qualifier("oriUseQueue") Queue queue,@Qualifier("oriUseExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("ori.use");
}
2 、 声 明 死 信 交 换 机 (DEAD_LETTER_EXCHANGE ) 、 死信队列 (DEAD_LETTER_QUEUE),相互绑定
@Bean("deatLetterExchange")
public TopicExchange deadLetterExchange() {
return new TopicExchange("DEAD_LETTER_EXCHANGE", true, false, new HashMap<>());
}
@Bean("deatLetterQueue")
public Queue deadLetterQueue() {
return new Queue("DEAD_LETTER_QUEUE", true, false, false, new HashMap<>());
}
@Bean
public Binding bindingDead(@Qualifier("deatLetterQueue") Queue queue,@Qualifier("deatLetterExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#"); // 无条件路由
}
3、最终消费者监听死信队列。
4、生产者发送消息。
延迟队列
RabbitMQ 本身不支持延迟队列,总的来说有三种实现方案:
1、 先存储到数据库,用定时任务扫描
2、 利用 RabbitMQ 的死信队列(Dead Letter Queue)实现
3、 利用 rabbitmq-delayed-message-exchange 插件
使用死信队列实现延时消息的缺点:
1) 如果统一用队列来设置消息的 TTL,当梯度非常多的情况下,比如 1 分钟,2 分钟,5 分钟,10 分钟,20 分钟,30 分钟……需要创建很多交换机和队列来路由消息。
2) 如果单独设置消息的 TTL,则可能会造成队列中的消息阻塞——前一条消息没有出队(没有被消费),后面的消息无法投递(比如第一条消息过期 TTL 是 30min,第二条消息 TTL 是 10min。10 分钟后,即使第二条消息应该投递了,但是由于第一条消息 还未出队,所以无法投递。
3) 可能存在一定的时间误差
服务端流控
https://www.rabbitmq.com/configure.html
队列有两个控制长度的属性:(意义不大)
x-max-length:队列中最大存储最大消息数,超过这个数量,队头的消息会被丢 弃。
x-max-length-bytes:队列中存储的最大消息容量(单位 bytes),超过这个容 量,队头的消息会被丢弃
内存控制
RabbitMQ 会在启动时检测机器的物理内存数值。默认当 MQ 占用 40% 以上内 存时,MQ 会主动抛出一个内存警告并阻塞所有连接(Connections)。可以通过修改 rabbitmq.config 文件来调整内存阈值,默认值是 0.4 [{rabbit, [{vm_memory_high_watermark, 0.4}]}]
磁盘控制
当磁盘空间低于指定的值时(默认 50MB),触发流控措施
消费端限流
https://www.rabbitmq.com/consumer-prefetch.html
可以基于 Consumer 或者 channel 设置 prefetch count 的值,含义为 Consumer端的最大的 unacked messages 数目。当超过这个数值的消息未被确认,RabbitMQ 会停止投递新的消息给该消费者
channel.basicQos(2); // 如果超过 2 条消息没有发送 ACK,当前消费者不再接受队列消息
channel.basicConsume(QUEUE_NAME, false, consumer);
springboot整合Rabbitmq:
- 加入依赖
- 配置文件
来个例子:
public class Order implements Serializable{
private String name;
private String id;
private String messageId;
//set get 省略
}
@component
public class OrderSender{
@Autowired
private RabbitTemplate rabbitTemplate;
//不要忘了创建交换机和队列
public sendOrder(Order order){
CorrelationData correlationData = new CorrelationData();
correlationData .set(order.getMessageId);
//有很多重载方法
rabbitTemplate.converAndSend(
"order-exchange", //交换机
"order.abcd", //routingkey order.* order.#
order, // 消息体内容
correlationData // 消息唯一标识
)
}
}
消费端的配置
@Component
public class OrderReceiver{
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "order-queue", durable = "true"),
exchange = @Exchange(name="order-exchange", durable = "true", type="topic")
key = "order.*"
)
)
@RabbitHandler
public void onOrderMessage(
@Payload Order order, //消息体内容
@Heads Map<String,Object>, //消息的properties
Channer channel
)
{
Long deliverTag=(Long) headers.get(AmapHeaders.DELIVERY_TAG);
channel.basicAck(deliverTag,false);//手动回收
}
}
消息100%可靠的投递解决方案
https://www.rabbitmq.com/reliability.html
消息发送到 RabbitMQ 服务器
在 RabbitMQ 里面提供了两种机制服务端确认机制,也就是在生产者发送消息给 RabbitMQ 的服务端的时候,服务端会通过某种方式返回一个应答,只要生产者收到了 这个应答,就知道消息发送成功了.
第一种是 Transaction(事务)模式,第二种 Confirm(确认)模式。
Transaction(事务)模式
事务模式有一个缺点,它是阻塞的
try {
channel.txSelect();
// 发送消息
// String exchange, String routingKey, BasicProperties props, byte[] body
channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
// int i =1/0;
channel.txCommit();
System.out.println("消息发送成功");
} catch (Exception e) {
channel.txRollback();
System.out.println("消息已经回滚");
}
Spring Boot 中的设置:rabbitTemplate.setChannelTransacted(true);
Confirm(确认)模式
异步确认模式需要添加一个 ConfirmListener,并且用一个 SortedSet 来维护没有被确认的消息。
Confirm 模式是在 Channel 上开启的,因为 RabbitTemplate 对 Channel 进行了封装,叫做ConfimrCallback
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack) {
System.out.println("发送消息失败:" + cause);
throw new RuntimeException("发送异常:" + cause);
}
}
});
消息从交换机路由到队列
两种方式处理无法路由的消息,一种就是让服务端重发给生产者,一种是让交换机路由到另一个备份的交换机
- 消息回发的方式:使用 mandatory 参数和 ReturnListener(在 Spring AMQP 中是 ReturnCallback)
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey){
System.out.println("回发的消息:");
System.out.println("replyCode: "+replyCode);
System.out.println("replyText: "+replyText);
System.out.println("exchange: "+exchange);
System.out.println("routingKey: "+routingKey);
}
});
- 消息路由到备份交换机的方式:在创建交换机的时候,从属性中指定备份交换机。
Map<String,Object> arguments = new HashMap<String,Object>();
arguments.put("alternate-exchange","ALTERNATE_EXCHANGE"); // 指定交换机的备份交换机
channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, arguments);
(注意区别,队列可以指定死信交换机;交换机可以指定备份交换机)
消息在队列存储
把消息本身和元数据(队列、交换机、绑定)都 保存到磁盘。开启一系列的持久化属性。
消息投递到消费者
RabbitMQ 提供了消费者的消息确认机制(message acknowledgement),消费者可以自动或者手动地发送 ACK 给服务端
NONE:自动 ACK
MANUAL: 手动 ACK
AUTO:如果方法未抛出异常,则发送 ack。
当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝, 且不重新入队。当抛出 ImmediateAcknowledgeAmqpException 异常,则消费者会 发送 ACK。其他的异常,则消息会被拒绝,且 requeue = true 会重新入队
消费者回调
消费者的是否成功最终生产者是无法知道的。
1) 调用生产者 API
2) 发送响应消息给生产者
首先要开启异步确认模式
主要代码