RabbitMQ

目录

消息队列的优缺点及使用场景     

Kafka、ActiveMQ、 RabbitMQ、 RocketMQ 对比   

架构设计

简述RabbitMq的交换机类型     

rabbitmq可以直连队列么?

rabbitmq的持久化机制   

如何保证消息的可达或不丢失   

如何保证消息不被重复消费   

什么是延迟队列、死信队列

rabbitmq的镜像队列

RabbitMQ事务消息     (了解)

rabbitmq的普通集群原理


消息队列的优缺点及使用场景     

优点:

  1. 解耦,降低系统之间的依赖     
  2. 异步处理,不需要同步等待     
  3. 削峰填谷,将流量从高峰期引到低谷期进行处理     

缺点: .     

  1. 增加了系统的复杂度,幂等、重复消费、消息丢失等问题的带入     
  2. 系统可用性降低,mq的故障会影响系统可用     
  3. 一致性, 消费端可能失败场景:日志采集、发布订阅等 

适用场景  日志采集、发布订阅等

不需要实时性的,可达到效果:解耦、异步化、限流削峰

解耦:商品管理(静态页面+搜索信息)如果是同步状态下时间消耗长并且比较耦合,一个系统步骤有问题或者新增系统,就有可能影响到整体。不符合OCP的设计原则(开闭原则:对修改关闭对多扩展 新增开放);

异步:基础服务,采用异步方式为其他系统服务(短信、邮件、积分等)

削峰:就是前后两个交互的系统处理的速度不匹配,为了保护处理慢的系统,从而引入消息中间件。来实现削峰限流处理。     

比如异步写的方案,保护数据库,缓解瞬间写的压力

再比如秒杀系统,会产生很多的订单信息,此时也可以通过MQ来保护订单系统。也节省了服务器   

Kafka、ActiveMQ、 RabbitMQ、 RocketMQ 对比   

ActiveMQ: JMS规范,支持事务、支持XA协议,没有生产大规模支撑场最、官方维护越来越少  

RabbitMQ: erlang语言开发、性能好、高并发,支持多种语言。社区、文档方面有优势,erlang语言不利于java 程序员二次开发,依赖开源社区的维护和升级,需要学习AMQP协议、学习成本相对较高                以上吞吐量单机都在万級不是很高

kafka:高性能,高可用,生产环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长topic数量有限)、社区更新慢吞吐量单机百万   

rocketmq: java实现, 方便二次开发、设计参考了kafka,高可用、高可靠,社区活跃度一般、支持语言较少吞吐量单机十万 

架构设计

Broker: rabbitmq的服务节点     

Queue:队列,是RabbitMQ的内部对象, 用于存储消息。RabbitMQ中消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都收到所有的消息进行消费。(注意: RabbitMQ不支持队列尼面的广播消费,如果窝要厂播消费,可以采用一个交换器通过路由Key邦定多 个队列,由多个消费者来订阅这些队列的方式。     

Exchange:交换器。生产者将消息发送到Exchange,由交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其它处理。     

RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一 个RoutingKey, 用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定健 固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。     

Binding:通过绑定将交换器和队列关联起来,在绑定的时候一般会指定一 个绑定键,这样RabbitMQ就可以指定如何正确的路由到队列了。交换器和队列实际上是多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多关系表)。在投递消息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相对应的队列。     

信道:信道是建立在Connection之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接     着可以创建一个AMQP 信道(Channel),每个信道都会被指派一个唯一 的D。RabbitMQ 处理的每条AMQP指令都     是通过信道完成的。信道就像电缆里的光纤束。-条电缆内含有许多光纤束, 允许所有的连接通过多条光线束进行传输和接收。

简述RabbitMq的交换机类型     

交换器分发会先找出绑定的队列,然后再判断routekey,来决定是否将消息分发到某一个队列中 message到exchange,交换机根据routekey进行判断,exchange和队列是根据bindingkey判断两个key只是叫法不同,实则是同一key

//以下配置在生产者消费者设置均可,也可以在控制台配置
channe1 channe1 = connection .createChanne1O); //在rabbitmq中创建一个信道    
channe1.exchangeDeclare("exchangeName","direct"); //创建一个type为direct的交换器    channe1.queueDec lare("queueName") ;     //创建一个队列     
channe1.queueBind("queueName", "exchangeName", "zhangsna"); //绑定 并设置路由链     
channe1.queueBind"queueName", "exchangeName", "1isi"); //绑定 并设置路山链     
channe1.queueBind(" queueName", "exchangeName", "wangvu");//绑定 并设置路由键

fanout:扇形交换机,不再判断routekey,直接将消息分发到所有绑定的队列     

direct直连:判断routekey的规则是完全匹配模式,即发送消息时指定的routekey要等于绑定的routekey  

topic:判断routekey的规则是模糊匹配模式     key可用*(一个字符)和#(多个字符)进行匹配

header:绑定队列与交换器的时候指定一个键值对, 当交换器在分发消息的时候会先解开消息体里的headers数据,然后判断里面是否有所设置的键值对,如果发现匹配成功,才将消息分发到队列中;这种交换器类型在性能上相对来说较差,在实际工作中很少会用到 

rabbitmq可以直连队列么?

生产者不通过交换机直接将消息发给队列生产者和消费者使用相同的参数声明队列。重复声明不会改变队列     

//生产者     
channe1. queueDeclare(QUEUE. NANE,false, false, false, nu11);     
//发送10条消总,依次在消且后面附加1-10个点     
for(inti=6;i>0;1--){
    string message = "hellowor1d";     
    channe1.basicPub1sh(",QUEUE. NAME ,nu11, message . getBytesO); 
}    

//消费者     
channe1. queueDeclare(QUEUE_ NAME, false, false, false, nu11);     
QueueingConsumer consumer = new QueueingConsumer (channe1);     
//指定消费队列     
channe1. basi cConsume (QUEUE NAME,true, consumer);     
while (true){
    QueueingConsumer . Delivery delivery = consumer .nextDeliveryO;     
    String message = new String(delivery. getBodyO);
    dowork (message) ;
}

channe] . queueDeclare(queue, durab1e, excTusive, autoDelete, arguments) ;
queue:队列名字
durable:队列持久化标志,true为持久化队列
excTusive: exclusive: 排他队列,仅对创建的链接可见、链接中的channe1都可见,其他链按不能重复声明,链接关闭队列会被自动删除
autoDelete:自动删除, 如果该队列设有任何订阅的消费者的话,该队列会陂自动删除。这种队列适用于临时队列。
arguments: Map 类型,队列参数设置
	x-message-tt1:数字,消息队列中消息的春活时间,超过会被劃除
	x-expires:数字,队列自身的空闲存活时间,指定时间内没有被访问,就会被删除
	x-max- 1ength和Ix-max- length-bytes;队列最大长度和空间,超出会删除老的数据
	x-dead-1etter-exchange和X- dead-1etter-routing-key;设置死信
	x-max-priority:队列支持的优先级别,需要生产者在发送消息时指定,消总按照优先级从高到底分发给消费者
channel . basicpublish(exchange, routingKey, mandatory, inmmediate, basicProperties, body);
exchange:交换机名
routingKey:路由键
mandatory:为true时,如果exchange根据 自身类型和消总routeKey无法找到一一个符合条件的queue,那么会调用
basic .return方法将消息返还给生产者,channe1 . addReturnListener添加一个监听器, 当broker执行
basic .return方法时,会回调handleReturn方法,这样就可以处理变为死信的消息了:设为false时,出现上述情形broker会直接将消思扔掉;
immediate: 3. 0以前这个标志告诉服务器如果该消总关联的queue上有消费者,则马上将消息投递给它,如果所有
queue都没有消费者,直接把消总返还给生产者, 不用将消息入队列等待消费者了。3.0之后取消了该参数
basicProperties:消息的详细属性,优先级别、持久化、到期时间等,headers 类型的exchange要用到的是其中的headers字段。
body:消息实体,字节数组。

QueueingConsumer:一个已经实现好了的Consumer ,相比于自己实现Consumer接口,这是个比较安全快捷的方式。该类基于jdk的BlockingQueue实现,handleDelivery方法中将收到的消息封装成Delivery对象,并存放到BlockingQueue中,这相当干消费名本地存放了一个消 息缓存队列。nextDelivery() 方法底层调用的
BlockingQueue的阻塞方法take()

channel. basicconsume(queue, autoAck, consumer);
queue:队列名。
autoAck :自动应答标志,true为自动应答。
consumer:消费者对象,可以白己实现Consumer接口,建议使用QueueingConsumer.

rabbitmq的持久化机制   

  1. 交换机持久化: exchange. declare创建交互机时通过多数指定     
  2. 队列持久化: queue_ declare创建队列时通过参数指定   
  3. 消息持久化: new AMQPMessage创建消息时通过参数指定     

通过append的方式写文件,会根据大小(默认16M)自动生成新的文件,rabbitmq启动时会创建两个进程,一 个负责持久化消息的存储,另一个负责非持久化(没有指定持久化)消息的存储(内存不够时)。消息存储时会在ets表中记录消息在文件中的映射以及相关信息(包括id.偏移量,有效数据,左边文件。右边文件) .消息读取时根据该信息到文件中读取、同时更新信息     

消息删除时只从ets删除,变为垃圾数据,当垃圾数据超出比例(默认50%) , 并且义件数达到3个,触发垃圾回收,锁定左右两个文件,整理左边文件有效数据、将右边文件有效数据写入左边,更新文件信息,删除右边,完成合并。当一个文件的有用数据等于0时,删除该文件。     

写入文件前先弓buffer缓冲区,如果buffer已满,则写入文件(此时只是操作系统的页存)。

每隔25ms刷一次磁盘, 不管buffer满没满,都将buffer和页存中的数据落盘。

每次消息写入后。如果没有后续写入请求,则直接刷盘 

如何保证消息的可达或不丢失   

1,背景:     网络是不可靠的     像我们上面所描述的各种场最,一旦消息出现丢失, 将会造成各种问题。比如邮件没有发送,短信没有收到等等。

解决方案:确认机制+补偿机制  

生产这----->MQ服务器----->消费者  这里面面的每个环节出问题都有可能造成消息的不可达,所以,我们需要保证针对每个环节设计解决方案。     

1,消息要发送到MQ服务路     解决的方式有两种:     

  • 方式一:事务的方式(了解即可,性能不佳)     事务消息
  • 方式二: 异步confirm模式 (推荐) , 客户端通过设置confirm监听器,获取MQ服务器的异步响应。如果消息成功传递到MQ服务器,则服务器会传递回ACK=true,否则ack=false     

 落地关键点:

#spr ingboot配置:
spring.rabbi tmq. publisher -confirms=true
	#代码层面
	#需要去创建confirm回调监听器
	//回调函数: confi rm确认
	fina1 Confi rmcallback confirmcallback = new RabbitTemplate 。ConfirmcallbackO {
	    @override
	    public void confirm(CorrelationData correlationData, boolean ack, string cause) {
	        System. err .print1n("correlationData:”中correlationData); 
	        System. err .print1n("ack: " + ack);
	        if(!ack){
	            System.err .print1n("异常处....");
                //表示消息已经送到MQ服务器
	        }
          }
	};
	//在使用rabbitTemplate的时候,窝要设置confirm
	rabbitTemp late .setConfirmCallback(confirmcal1back);
	//其他操作眼之前一样
	

    注意: confirm机制只保证消息到了MQ服务器
    

2, return机制,保证消息到了MQ服务器之后,是否能正确路由到交换机或者队列.这个是在正常情况是不会发生的,所以一般在开发中我们会忽略掉这个设置。

 3,消息队列做持久化。消息成功传递到MQ服务路之后,还需要对消息队列做持久化,否则一旦消息 服务路宕机,那么未处理的消息将会丢失     

4,消息确认机制。消费端的手工确认模式开启,只有消费端手工确认之后,才表示这个消息已经被正确处理     这个环节是确保消息真的被消费端正确处理了,如果没有被正确处理,那么消息还是需要重新处理的。

  1. 有可能MQ服务器没有收到消费端发送的确认消息,会重复处理,所以要保证接口的幕等性   
  2. 为了避免重复处理不成功,造成阻塞,所以,我们引入有固定次数的重试机制(失败的情况)

当然为了避免消息重复处埋依然失败的情况,所以我们一般设置为重复处理二 次,如果三次还是有问题。则应该将错误信息记录到日志表中,然后人工介入处理。避免堵塞到后面的消息处理   

发送方确认机制:     

信道需要设置为confirm 模式,则所有在信道上发布的消息都会分配一个唯一ID.一旦消息被投递到queue (可持久化的消息需要写入磁盘),信道会发送一个确认给生产者(通过ConfirmCallback接口:只确认是否正确到达Exchange中,成功到达则回调包含消息唯一ID) 。   

如果RabbitMQ发生内部错误从而导致消息丢失,会发送一条nack (未确认)消息给生产者(Returncallback接口:消息失败返回时回调)。所有被发送的消息都将被confirm (即ack)或者 被nac一次。但是没有对消息被confirm 的快慢做任何保证,并且网一-条消息不会既被confirm又 被nack发送方确认模式是异步的,生产者应用程序在等待确认的同时,可以继续发送消息。当确认消息到达生产者,生产者的回调方法会被触发。

接收方确认机制:     

消费者在声明队列时,可以指定noAck参数,当noAck=false时, RabbitMQ会 等待消费者显式发回ack信号后才从内存(或者磁盘,持久化消息)中移去消息。否则,消息被消费后会被立即删除。Rabbi tMQ不会为末ack的消息设置超时时间,它判新此消总是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否已经断开。这么设计的原因是RabbitMQ允许消费者消费一条消 息的时间可以很长(可以用作限流)。保证数据的最终-致性。 如果消费者返回ack之前断开了链接,RabbitmQ 会重新分发给下一个订阅的消费者。(可能存在消息重复消费的隐患,需要去重)

如何保证消息不被重复消费   

幕等: 一个数据或者一个请求, 重复来多次,确保对应的数据是不会改变的,不能出错。     

思路:     

  • 如果是写redis, 就没问题,反正每次都是set ,天然幕等性     
  • 生产者发送消息的时候带上一个全局唯一的id,消费者拿到消息后,先根据这个id去redis里查一下,之前有没消费过,没有消费过就处理,并且写入这个id到redis,如果消费过了,则不处理。  
  • 基于数据库的唯一键

什么是延迟队列、死信队列

作用:     需要延迟处理的消息,我们将使用延迟队列来达到这个效果     

背景:     比如订单超时未支付,我们需要取消订单,解锁库存。     

实现方案:     

方案一:采用定时任务     通过定时任务扫描订单表,判断订单的下单时间跟当前时间的时间差,如果超过指定时间,比如30分钟,则取消订单。但是这个方案存在问题,就是频繁扫描订单表,会给数据库带来较大的压力。     

方案二:采用消息队列提供的延迟队列,跟数据库的交换只要一次。消息超过有效期没有被处理就是死信

 

死信队列

  1. 消息被消费方否定确认,使用channel . basicNack或channe1. basicReject,并且此时requeue(重新消费)属性被设置为false,这条消息就会变为死信。     
  2. 消息在队列的存活时间超过设置的TTL时间。     
  3. 消息队列的消息数量已经超过最大队列长度。     这三种情况称为死信消息

那么该消息将成为“死信"。“死信“消 息会被RabbitMQ进行特殊处理,如果配置了死信队列信息(配置了DLX属性),那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃  

为每个需要使用死信的业务队列配置一个死信交换机, 这里同一个项目的死信交换机可以共用一 个, 然后为每个业 务队列分配一个单独的路由key, 死信队列只不过是绑定在死信交换机上的队列,死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型[Direct. Fanout. Topic]     

延时队列:可以认为是死信队列+TTL。

TTL:一条消息或者 该队列中的所有消息的最大存活时间。

如果一条消息设置了TTL属性或者进入了设置TTL属性的队列,那么这条消息如果在TTL设置的时间内没有被消费,则会成为“死信”。如果同时配置了队列的TTL和消息的TTL,那么较小的那个值将会被使用。只需要消费者一直消费死信队列里的消息

rabbitmq的镜像队列

  

GM负责消息的广播,所有的GM组成gm. group,形成链表结构,负责监听相邻节点的状态,以及传递消息到相邻节点,master的GM收到消息时代表消息同步完成   

 mirror_ queue_ master/slave负责消息的处理, 操作blockingQueue, Queue负责AMQP协议(commit. rollback. ack等)master处理读写 

RabbitMQ事务消息     (了解)

通过对信道的设置实现     

  1. chanel.txSelect0;通知服务器开启事务模式;服务端会返回Tx.Select-Ok     
  2. channel.basicPublish;发送消息,可以是多条,可以是消费消息提交ack     
  3. channel.txCommit()提交事务;     
  4. channel.txRolback)回滚事务:     

消费者使用事务:   

  1. autoAck=false,手动提交ack,以事务提交或回滚为准;     
  2. autoAck-true,不支持事务的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了     

如果其中任意一个环节出现问题, 就会抛出loException异常, 用户可以拦截异常进行事务回滚,或决定要不要重 复消息。.     事务消息会降trabbitimq的性能

rabbitmq的普通集群原理

 元数据:     

  • 队列元数据:队列名称和它的属性   
  • 交换器元数据:交换器名称、类型和属性   
  • 绑定元数据:一张简单的表格展示 了如何将消息路由到队列     routekey到queue的映射关系
  • vhost元数据:为vhost内的队列 交换器和绑定提供命名空间和安全属性     

为什么只同步元数据:     

  • 存储空间,每一个节点都保存全量数据,影响消息堆积能力   
  • 性能,消息的发布者需要将消启复制到每一 个集群节点     
  • 客户端连接的是非队列数据所在节点:则该节点会进行路由转发,包括发送和消费     

集群节点类型:

  • 磁盘节点:将配置信息和元信息存储在磁盘上。   
  • 内存节点:将配置信息和元信息存储在内存中。性能优于磁盘节点。依赖磁盘节点进行持久化  

RabbitMQ要求集群中至少有一个磁盘节点, 当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩溃了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。 如果唯一磁盘的磁盘节点崩溃,集群易可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个可以,就能正常操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值