RabbitMQ总结

1.如何保证高可用性?

  • 普通集群模式

    普通集群模式就是在多台机器上启动多个RabbitMQ实例,每台机器上启动一个.创建的每一个queue,只会放在一个RabbitMQ实力上,但是每个实例都会同步queue的元数据(元数据可以认为是queue的配置信息,通过元数据可以找到queue所在的实例),消费的时候,实际上如果连接到另外一个实例,那么那个实例会从queue所在的实例拉取数据.

    1596527165(1).png

    这种方式很麻烦,没有做到所谓的分布式,就是普通的集群,因为这会导致要么从消费者每次随机连接一个实例然后获取数据,要么固定连接到能queue所在的实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈
    而且如果那个存放queue的实例宕机了,其他实例就无法获取到数据,如果开启了消息持久化机制,让MQ落地存取消息,消息不一定会丢,但是得等实例恢复了,然后才能继续从上面拉取数据.
    这就没有所谓的高可用性,主要是提高吞吐量,就是说让集群多个节点服务某个queue的读写操作

  • 镜像集群模式

    这种才是所谓的RabbitMQ的高可用性解决方式,跟普通集群不一样的地方是,在镜像集群模式下,你创建的queue,无论是元数据还是queue的消息都会存在于多个实例上,就是说,每个RabbitMQ节点都有queue的一个完整镜像,包含queue所有数据的意思,然后每次写消息到queue的时候,都会自动把消息同步到每个实例当中.

    1596528017(1).png

    如何开启镜像集群模式呢,在RabbitMQ控制后台新增一个策略,也就是镜像集群模式的策略,指定的时候可以要求数据是同步到所有的节点上,还是同步到指定数量的节点上.
    [链接地址] https://blog.csdn.net/wexiaoword/article/details/81352045

2.如何保证消息不被重复消费(如何保证消息的幂等性)?

重复的原因

第一类原因
1. 消息发送端消息的重复发送
- 消息发送端发送消息给消息中间件,消息中间件接收到消息并成功存储,而这时消息中间件出现了问题,导致应用端没有收到消息发送成功的返回因而进行重试产生了重复.
- 消息中间件因为负载搞响应变慢,成功把消息存储到消息存储中后,返回"成功"这个结果时超时.
- 消息中间件讲消息成功存储后,在返回结果时出现网络问题,导致应用发送端重试,而重试时网络恢复,导致消息的重复
可以看到,通过消息发送端产生的重复问题主要原因是消息成功进入消息存储中后,因为各种原因导致应用发送端没有收到返回"成功"的结果,并且又有重试机制导致重复.
第二类原因
2. 消息到达了消息存储,由消息中间件向外投递时产生重复
- 消息被投递到消息接收者应用进行处理,处理完毕后应用出现了问题,消息中间件不知道处理结果,会再次进行投递.
- 消息被投递到消息接收者应用进行处理,处理完毕后网络出现问题消息中间件没有接收到结果,会再次进行投递.
- 消息被投递到消息接收者应用进行处理,处理时间比较长,消息中间件因为消息超时,会再次进行投递.
- 消息被投递到消息接收者应用进行处理,处理完毕后消息中间件出现了问题,没有收到消息结果并处理,会再次进行投递.
- 消息被投递到消息接收者应用进行处理,处理消息完毕后消息中间件收到处理结果但是遇到消息存储故障,无法更新投递状态,会再次进行投递.
可以看到,在投递过程中产生的消息重复问题主要是因为消息接收者成功处理消息之后,消息中间件不能及时更新投递状态造成的.

如何解决重复消费

主要是要求消息接收者来处理这种重复情况,也就是要求消息接收者的操作是幂等性操作.

什么是幂等性?

对于消息接收端的情况,幂等的含义是采用同样的输入多次调用处理函数,得到统一的结果.
例如,一个sql操作
update stat_table set count = 10 where id = 1 ;
这个操作多次执行,id等于1的count的结果始终是10,这个操作就是幂等的,我们不用担心操作被重复.
另外一个sql操作
update stat_table set count = count +1 where id =1 ;
这样的操作就不是幂等的,一旦重复操作,结果就会发生变化.

常见办法

1.MVCC
多版本并发控制,乐观锁的一种实现,在生产者发送消息进行更新数据时需要带上数据的版本号,消费者去更新时需要去比较持有数据的版本号,版本号不一致时无法操作成功.
例如博客点赞数自动+1接口:
public boolean addCount(Long id,Long version);
update blogTable set count = count +1 ,version = version +1 where id =321 and version = 123;
每一个version只有一次执行成功的机会,一旦失败了生产者必须必须重新获取数据的版本号再次发起更新.
2.去重表
利用数据库表单的特性来实现幂等,常见的一个思路就是在表上建立唯一索引,保证某一类数据一旦执行完毕,后续的操作就不再重复处理了.(利用一张日志表来记录已经成功处理消息的id,如果新到的消息的id已经存在于表中,就不再处理这条消息).
以电商平台为例,电商平台上的订单id就是最合适的token,当用户进行下单时可能会经历多个环节,比如生成订单,减库存,减优惠券等等,每一个环节执行时都先检测下该订单id是否已经执行了这个操作,如果执行了,直接返回结果,不做任何操作,如果没有执行,执行操作并缓存结果.这样可以最大程度上避免重复执行问题,并且缓存起来的执行结果也能用于事物的控制等.

描述一下RabbitMQ概念里 channel,exchange,queue这些概念及作用.

Queue就是队列,用于存储消息,具有自己erlang进程.exchange内部实现为保存binding关系的查找表;channel由实际进行路由操作的实体,即负责按照routing_key将message投递到queue.在RabbitMQ中所有客户端与MQ之间的通讯都是在channel上,channel是真实TCP连接之上的虚拟连接,所有AMQP命令都是通过channel发送的.

RabbitMQ中的元数据有哪些?

元数据主要分为queue元数据(queue名字和属性等),exChange元数据(exChange名字,类型和属性等),Binding元数据(存放路由关系的查找表),Vhost元数据(vhost范围内针对前三者的名字空间约束和安全属性设置),另外,在集群当中,元数据都是在一个broker中全局复制的.

RabbitMQ中的Vhost是什么?起什么作用?

vhost可以理解为虚拟的broker,即一个迷你版的RabbitMQ server.其内部含有独立的queue,exChange,binding等,但最重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制.从rabbitMq全局来讲,vhost可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的vhost中)

RabbitMQ中一个queue存放的message是否有数量限制?

默认情况下一般是没有限制的,因为限制取决于机器的内存,但是消息过多会导致处理效率下降,同时可以通过参数来限制,x-max-length:对队列中消息的条数进行限制,
x-max-length-bytes:对队列中消息的总量进行限制.

为什么对所有的message都使用持久化机制

首先,必然导致性能下降,因为写磁盘比写内存慢得多,Rabbit的吞吐量有10倍差距.
其次,message的持久化机制用在RabbitMQ集群时出现"坑爹"的问题,矛盾点在于,要实现持久化的话,必须消息,消息队列,转换器三者持久化,如果不同机器中三者属性有差异,会发生不可预料的问题,所以一般处理原则是:仅对关键消息进行持久化处理(根据业务的重要程度),且应该保证关键消息的量不会导致性能瓶颈.

RAM node 和disk node的区别?

RAM node就是内存节点,RabbitMQ中queue,exChange,Binding等基础构件中的元数据就是存放在其中,disk node是磁盘节点,上述数据会在内存和磁盘中均进行存储.
一般在RabbitMQ集群中至少存放一个disk node 磁盘节点

RabbitMQ如何保证消息的可靠性传输?

1.生产者
生产者将数据发送到RabbitMQ时,可能数据在半路就丢失了.此时可以选择用rabbitMQ提供的事物功能,就是生产者发送消息之前开启RabbitMQ事物(channel.txSelect),然后发送消息,如果消息没有被RabbitMQ接收到,那么生产者会异常报错,此时可以回滚事物(channel.txRollback),然后重试发送消息;如果消息成功接收,那么就可以提交事物(channel.txCommit).这样处理的问题是,RabbbitMQ的吞吐量会下来,太耗性能.
所以一般情况下,要确保生产者消息不丢,可以开启comfirm模式,在生产者设置comfirm模式后每次发送消息都会分配一个唯一id,如果MQ消息接收成功,那么会回调一个ack接口告诉生产者消息接收ok了;如果MQ消息接收失败,那么会回调一个nack接口,告诉你消息接收失败,你可以重试.而且可以结合这个机制在内存里维护每个id的状态,如果超过一定时间没有接收,可以重发消息.
事物机制和comfirm模式最大的不同之处在于,事物机制是同步的,提交完一个事物过后会阻塞在那儿,comfirm模式是异步的,发送一个消息然后就可以发送下一个消息,然后rabbitMQ接收到消息会异步回调接口通知消息接收成功.
所以一般生产者防止消息丢失会选择comfirm模式.
2.RabbitMQ本身
RabbitMQ本身丢失消息,这个必须得开启持久化机制,就是消息写入之后会持久化到磁盘,哪怕是MQ自己挂了,恢复之后也可以自动从磁盘获取到之前存储的数据,一般数据不会丢,除非极其罕见的是,RabbitMQ还没持久化,自己就挂了,可能会导致少量数据丢失,这种情况概率很小.
设置持久化有两个步骤,第一个是创建queue和交换器的时候设置成持久化,这样就可以保证RabbitMQ持久化相关的元数据,但是这样不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryCode设置为2,就是将消息设置持久化,此时RabbitMQ就会将消息持久化到磁盘,必须同时设置这两个持久化才行,哪怕是RabbitMQ挂了,再次重启,也会从磁盘上恢复queue以及queue中的数据.
而且消息的持久化是可以和生产者的comfirm模式配合起来,只有消息持久化到磁盘之后才会回调生产者的ack.所以哪怕是在持久化之前,RabbitMQ挂了,数据丢了,生产者接收不到ack,也是可以重发消息的.
哪怕是RabbitMQ开启了持久化机制,也有一种可能,消息写入到MQ中了,但是在还没来得及持久化到磁盘的时候,RabbitMQ挂了,这种时候可能会导致少量数据丢失.
3.消费者丢失
RabbitMQ如果丢失数据,主要是因为刚消费的时候,还没处理,结果进程挂了,这时候MQ认为已经消费到了,这数据就丢了.
这个时候得用RabbitMQ提供的ack机制,简单来说,就是关闭MQ的自动ack,可以通过一个api来调用,然后每次确保自己代码里处理完成之后,再进行ack.这样的话,没处理完就没有ack,MQ本身也就认为你还没消费完,这个时候RabbitMQ会把消息分配给其他的消费者,这样也不会导致数据丢失.

RabbitMQ如何保证消息的顺序性?

从根本上来说,异步是不应该有顺序依赖的,在MQ上估计是没法解决的,要实现严格的顺序消息,简单可行的办法是:保证生产者---MQServer---消费者是一对一对一的关系.
如果有顺序依赖的消息,要保证消息的同一个hashKey,保证对同一个key的消息发送到相同队列.A用户产生的消息()都按A的hashKey分发到同一个队列

你可能感兴趣的:(RabbitMQ总结)