Rabbit MQ

Rabbit MQ

1.原理

特征:
  • 这是一个在进程间传递异步消息的网络协议,因此数据的发送方、接收方以及容器(MQ)都可以在不同的设备上。
  • 主要特征是面向消息、队列、灵活的路由、可靠性、安全性等
  • 支持符合要求的客户端和消息中间件代理之间进行通信,并不受产品、开发语言的限制
    在这里插入图片描述

rabbitMQ的工作流程简言之就是,消息发送者将消息发送到交换机(Exchange)中,交换机通过routing_key将消息路由到绑定的队列中,队列再将消息发送给每个链接到当前队列的消费者。

概念介绍:
生产者(Publisher)

消息的生产者,就是一个向交换机发布消息的客户端应用程序。

消息(Message)

消息,包含消息头和消息体,消息头是由一系列的属性构成的,这些属性包含routing-key(路由键)、priority(优先级)、delivery-mode(消息是否需要持久化存储)等内容,消息头中的属性有的是对exchange透明的,有的则是非透明的。

broker

AMQP的服务端被称为broker,broker就是接收和分发消息的应用,也就是说RabbitMQ server就是broker。

虚拟主机(Virtual Host)

虚拟主机,主要用作不同用户之间的权限分离,生产环境可以使用不同的VH来区分不同的应用。

交换机(Exchange)

交换机用来接收生产者发送的消息,并且根据royting_key将消息路由到绑定的不同队列中。交换机主要有fanout、direct、topic、headers等几种类型

交换机不用来存储数据,只是用来路由消息到队列。

队列(Queue)

队列才是最终保存消息并且发送给消费者的位置,一个消息可以被投递到一个或者多个队列。

声明队列时,几个常用属性如下:

  • name 队列名称,不设置时自动生成队列名称
  • durable 队列是否持久化,默认false
  • exclusive 默认false,为true时表示只有一个connection使用该队列,且该connection断开后自动删除队列
  • autodelete 自动删除,默认false,为true时表示当没有消费者连接队列时,队列自动删除
  • priority 优先级,官方建议1-10,数值越大消息越早被消费
绑定(Binding)

队列和交换机之间的关系叫做绑定,一个绑定就是基于路由键将交换机和队列连接起来的路由规则。

routing_key和binding_key

routing_key: 路由键,生产者发送消息时设置,消息从交换机路由到队列的依据;

binding_key: 队列绑定交换机的依据,也是消息从交换机路由到队列的依据,消息从交换机路由到队列时routing_key和binding_key必须匹配(根据交换机不同,匹配规则也不相同)。

routing_key和binding_key最大长度都是255个字节。

信道(Channel)

信道是建立在Connection内部的虚拟连接,AMQP命令都是通过信道发送出去的,不管是发布消息还是接收消息,这些动作都是通过信道完成的。

之所以要有信道是由于操作系统tcp创建销毁占用资源多,在Connection内部创建虚拟的信道,多个信道可以复用单个Connection可以有效地节约tcp资源。

Exchange类型
Fanout Exchange(扇形交换机)

在这里插入图片描述

特点:(类似广播)

发布消息时没有routing-key
生产者发送到exchange中的消息会被路由到所有绑定的队列中
Direct Exchange(直接交换机)

在这里插入图片描述

特点:(一一对应)

生产者发布消息时必须带着routing-key,队列绑定到交换机时必须指定binding-key ,
且routing-key和binding-key必须完全相同,如此才能将消息路由到队列中。
Topic Exchange(主题交换机)

在这里插入图片描述

特点:(根据通配符实现一对多,一对一)

routing-key必须由多个单词或者通配符组成,单词或者通配符之间使用.隔开,上限为255个字节;
*通配符只能匹配一个单词;
#通配符可以匹配零个或者多个单词;
队列绑定交换机时的binding-key要能够匹配发送消息时的routing-key才能将消息路由到对应的队列;
主题队列的routing-key设置为#时,表示所有所有的队列都可以接收到消息,相当于fanout交换机;
主题队列的routing-key中不包含#或者*时,表示指定队列可以接收到消息,相当于direct交换机;
发布确认

分为单个确认,批量确认,异步确认

最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列,
异步确认 比如说用 ConcurrentLinkedQueue 这个队列在 confirm callbacks 与发布线程之间进行消息的传递。

三种发布速度的对比
  • 单独发布消息
    同步等待确认,简单,但吞吐量非常有限。
  • 批量发布消息
    批量同步等待确认,简单,合理的吞吐量,一旦出现问题但很难推断出是哪条消息出现了问题。
  • 异步处理
    最佳性能和资源使用,在出现错误的情况下可以很好地控制,但是实现起来稍微难些
消息应答
  1. 轮流调度 Round-robin dispatchin

默认情况下,RabbitMQ会把消息按顺序传给下一个消费者。平均来看,每个消费者拿到的信息数量都是相同的。这种分发信息的机制被称为轮流调度(轮询,round-robin)。

  1. 消息确认 Message Acknowledgement
  • NONE:无应答,rabbitmq默认consumer正确处理所有请求。
  • AUTO:consumer自动应答,处理成功(注意:此处的成功确认是没有发生异常)发出ack,处理失败发出nack。rabbitmq发出消息后会等待consumer端应答,只有收到ack确定信息后才会将消息在rabbitmq清除掉。收到nack异常信息的处理方法由setDefaultRequeueReject()方法设置,这种模式下,发送错误的消息可以恢复。
  • MANUAL:基本等同于AUTO模式,区别是需要人为调用方法确认。
  1. 消息持久化
    如果RabbitMQ服务器挂掉了,消息也是会丢失的,除非你将队列和消息进行持久化(写入磁盘)。

  2. 公平分配
    假设我们有两个工人,按顺序分配任务,如果奇数的任务很重偶数的任务很轻松,就会出现有一个工人累的要死,另一个却很闲的情况。任务量分配不均的原因是:RabbitMQ没有看每个工人完成的工作量(即,收到的ACK数)。

消息可靠性传输

在这里插入图片描述

原因:不可靠的原因,上图流程在结束前,任一被阶段都可能造成消息丢失
  • 生产者 (消息发送失败,路由不到指定交换机)
  • RabbitMQ(broker) (宕机)
  • 消费者 (消费报错)
生产者发送消息到broker时,要保证消息的可靠性,主要的方案有以下2种:
1.事务 (每次发送消息必须要等到mq回应之后才能继续发送消息,比较耗费性能,会导致吞吐量降下来) 不考虑
2.confirm机制 (实现RabbitTemplate.ConfirmCallbackRabbitTemplete.ReturnCallback接口)

rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
⚫ 消息从 producer 到 exchange 则会返回一个 confirmCallback 。
⚫ 消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递

1、 ConfirmCallback确认模式

消息只要被 rabbitmq broker 接收到就会触发 confirmCallback 回调 。

2、 ReturnCallback 退回模式

如果消息未能投递到目标 queue 里将触发回调 returnCallback ,一旦向 queue 投递消息未成功,这里一般会记录下当前消息的详细投递数据,方便后续做重

RabbitMQ(broker) : 开启持久化(宕机后重启重新读取存储数据)

创建交换机时,设置durable=true

创建queue时,设置durable=true (两个都得开)

消费者 (消费报错)

消息报错时,重试,

  1. 达到一定次数进入死信队列,之后对死信队列进行处理。
  2. 达到一定次数发送到其他队列,再单独处理其他队列。
死信队列
定义:
  • 消费者端配置自动ack acknowledge-mode: auto在重试几次后(配置文件配置默认是3)后进入到死信队列
  • 消费者端使用basic.reject和basic.nack拒绝签收消息,并且配置requeue参数是false(即不重回原来的队列)
  • 消息在队列存活的时间大于TTL
  • 消息队列中消息的数量超过最大长度

采用死信队列的方式处理重试失败的消息

/**
 * 死信交换机
 * @return
 */
@Bean
public DirectExchange dlxExchange(){
	return new DirectExchange(dlxExchangeName);
}

/**
 * 死信队列
 * @return
 */
@Bean
public Queue dlxQueue(){
	return new Queue(dlxQueueName);
}

/**
 * 死信队列绑定死信交换机
 * @param dlxQueue
 * @param dlxExchange
 * @return
 */
@Bean
public Binding dlcBinding(Queue dlxQueue, DirectExchange dlxExchange){
	return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(dlxRoutingKey);
}

/**
 * 业务队列
 * @return
 */
@Bean
public Queue queue(){
	Map<String,Object> params = new HashMap<>();
	params.put("x-dead-letter-exchange",dlxExchangeName);//声明当前队列绑定的死信交换机
	params.put("x-dead-letter-routing-key",dlxRoutingKey);//声明当前队列的死信路由键
	return QueueBuilder.durable(queueName).withArguments(params).build();
    //return new Queue(queueName,true);
}

总结:死信交换机和死信队列都只是普通的交换机和队列,只不过被用来处理死信消息,而死信消息的产生是由于TTL过期或者队列中的消息数超过最大消息数,再或者时消费端reject或者nack消息时设置了requeue=false,消息变为死信后,由死信交换机路由到死信队列,再由专门的消费者消费死信队列中的消息。主要用于保证消息的可靠性

对于消费失败消息的处理,可以由死信队列统一处理,也可以基于RepublishMessageRecoverer实现重发到指定队列

使用RepublishMessageRecoverer好处就是可以不用像死信队列那样,只要有一个队列需要处理到死信队,就得在定义时与死信队列绑定,不利于开发,采用注解自定义生成队列时就不好指定绑定死信队列了。

@Bean
public Queue queue(){
	Map<String,Object> params = new HashMap<>();
	// 指定绑定死信队列
	params.put("x-dead-letter-exchange",dlxExchangeName);//声明当前队列绑定的死信交换机
	params.put("x-dead-letter-routing-key",dlxRoutingKey);//声明当前队列的死信路由键
	return QueueBuilder.durable(queueName).withArguments(params).build();
    //return new Queue(queueName,true);
}

基于RepublishMessageRecoverer实现重发,在设置了max-attempts . retry =true后,在重试一定次数后,会将自定失败消息,加入指定队列,做进一步的处理。这里可以设置自动处理也可以手动,none方式默认消费者成功消费,不推荐。

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual  # 消息确认方式,其有三种配置方式,分别是none、manual(手动ack) 和auto(自动ack) 默认auto
        retry:
          enabled: true  #监听重试是否可用
          max-attempts: 5   #最大重试次数 默认为3
          initial-interval: 2000  # 传递消息的时间间隔 默认1s
    @Bean
    @ConditionalOnProperty(SimpleConsumerProperties.SIMPLE_CONSUMER_ENABLED)
    public DirectExchange recoveryExchange() {
        return new DirectExchange(RECOVERY_EXCHANGE, true, false);
    }

    @Bean
    @ConditionalOnProperty(SimpleConsumerProperties.SIMPLE_CONSUMER_ENABLED)
    public Queue recoveryQueue() {
        return new Queue(RECOVERY_QUEUE, true);
    }

    @Bean
    @ConditionalOnProperty(SimpleConsumerProperties.SIMPLE_CONSUMER_ENABLED)
    public Binding errorBinding(Queue recoveryQueue, DirectExchange recoveryExchange) {
        return BindingBuilder.bind(recoveryQueue).to(recoveryExchange).with(RECOVERY_ROUTINGKEY);
    }

    @Bean
    @ConditionalOnProperty(SimpleConsumerProperties.SIMPLE_CONSUMER_ENABLED)
    public MessageRecoverer messageRecoverer(SimpleRabbitTemplate rabbitTemplate) {
        return new RepublishMessageRecoverer(rabbitTemplate, RECOVERY_EXCHANGE, RECOVERY_ROUTINGKEY);
    }

注意:

如果ack模式是手动ack,那么需要调用channe.nack方法,同时设置requeue=false才会将异常消息发送到死信队列中。重回队列是回到普通的业务队列

相关文档

http://rabbitmq.mr-ping.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值