RabbitMQ

消息队列是典型的:生产者、消费者模型。生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,这样就实现了生产者和消费者的解耦。

1 订阅模型

Fanout(广播): 每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的

Direct(直连): 消息中的路由键(routing key)如果和 Binding 中的 binding key 一致,交换器就将消息发到对应的队列中。它是完全匹配、单播的模式。

topic(主题): Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符!Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert“#” 匹配 0 个或多个单词,“*”匹配不多不少一个单词

Headers: 不处理路由键。而是根据发送的消息内容中的headers属性进行匹配。在绑定Queue与Exchange时指定一组键值对;当消息发送到RabbitMQ时会取到该消息的headers与Exchange绑定时指定的键值对进行匹配;如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers属性是一个键值对,可以是Hashtable,键值对的值可以是任何类型。而fanout,direct,topic 的路由键都需要要字符串形式的。

2 消息丢失分类

  1. 生产者丢失: 生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能
  2. MQ中丢失: 就是 RabbitMQ 自己弄丢了数据
  3. 消费端丢失: 你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。

在这里插入图片描述

2.1 生产者端丢失消息

(不推荐) 一种方法是用RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ事务channel.txSelect,然后发送消息,如果消息没有成功被RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。吞吐量会下来,因为太耗性能。而且事务机制是同步的,你提交一个事务之后会阻塞在那儿。

最常用的方法是开启confirm模式:以Spring AMQP为例,可以实现RabbitTemplate.ConfirmCallback和RabbitTemplate.ReturnCallback接口来完成生产者确认。

  1. 如果消息没有到exchange,则confirm回调,ack=false
  2. 如果消息到达exchange,则confirm回调,ack=true
  3. exchange到queue成功,则不回调return
  4. exchange到queue失败,则回调return
@Configuration
@Slf4j
public class ProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);            //指定 ConfirmCallback
        rabbitTemplate.setReturnCallback(this);             //指定 ReturnCallback
    }

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            log.info("消息发送成功:" + JSON.toJSONString(correlationData));
        } else {
            log.info("消息发送失败:{} 数据:{}", cause, JSON.toJSONString(correlationData));
        }
    }

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        // 反序列化对象输出
        System.out.println("消息主体: " + new String(message.getBody()));
        System.out.println("应答码: " + replyCode);
        System.out.println("描述:" + replyText);
        System.out.println("消息使用的交换器 exchange : " + exchange);
        System.out.println("消息使用的路由键 routing : " + routingKey);
    }
}

2.2 MQ中丢失消息

开启交换机和队列的持久化

在@Queue和@Exchange注解中都有autoDelete属性,值是布尔类型的字符串。如:autoDelete=“false”

  • @Queue:当所有消费客户端断开连接后,是否自动删除队列: true:删除,false:不删除。

  • @Exchange:当所有绑定队列都不在使用时,是否自动删除交换器: true:删除,false:不删除。

  • durable: 参数为false,消息在重启rabbitmq后丢失;修改为true后重新启动项目

@RabbitListener(bindings = @QueueBinding(
        exchange = @Exchange(value = "seckillExchange", type = "topic",durable = "true",autoDelete="false"),
        value = @Queue(value = "seckillQueue",durable = "true",autoDelete="false"),
        key = "seckill.message"
))

持久化可以跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。

2.3 消费端丢失

这个时候得用 RabbitMQ 提供的ack机制,简单来说,就是你关闭 RabbitMQ 的自动ack,设置为手动ack

spring.rabbitmq.listener.simple.acknowledge-mode: manual

手动确认模式

确认消息:

// 参数二:是否批量确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

拒绝消息:

// 参数二:是否重新入队,false时消息不再重发,如果配置了死信队列则进入死信队列,没有死信就会被丢弃
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);

不确认消息:

// 参数二:是否批量; 参数三:是否重新回到队列,true重新入队
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);

3 死信队列

死信,在官网中对应的单词为“Dead Letter”,可以看出翻译确实非常的简单粗暴。那么死信是个什么东西呢?

“死信”是RabbitMQ中的一种消息机制,当你在消费消息时,如果队列里的消息出现以下情况:

  1. 消息被否定确认,使用 channel.basicNackchannel.basicReject ,并且此时requeue 属性被设置为false
  2. 消息在队列的存活时间超过设置的TTL时间。
  3. 消息队列的消息数量已经超过最大队列长度。

那么该消息将成为“死信”。

“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。

4 延时队列

延时队列,最重要的特性就体现在它的延时属性上,延时队列中的元素则是希望被在指定时间得到取出和处理,所以延时队列中的元素是都是带时间属性的,通常来说是需要被处理的消息或者任务。

简单来说,延时队列就是用来存放需要在指定时间被处理的元素的队列。

比如以下场景:

  1. 订单在十分钟之内未支付则自动取消。
  2. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  3. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值