01 如何保证消息不丢失
三个阶段,发送消息,存储消息,消费消息
发送消息阶段
到消息服务器,有同步发送和异步发送两种方式,只要处理好消息服务器的ack就可以保证消息成功发送到了服务器,没有ack或者发生异常,进行重试发送。
存储消息阶段
消息服务器收到消息后,返回ack给消息生产者,这也有几种情况,一种是收到消息还没持久化就返回ack,这时如果消息服务器宕机那么就会导致消息丢失。另一种是等消息刷盘之后再返回ack给消息生产者,那么消息就不会丢失。
如果是消息集群模式,则需要配置消息至少写入两台服务器再给生产者响应。这样基本就保证了消息存储阶段的可靠性。
消费消息阶段
需要在消费者处理业务逻辑完成之后再给消息服务器响应。
消息可靠性提高了,性能就会下降,消息同步刷盘,多副本同步都会影响消息队列的性能,需要看场景选择合理的配置。
02 如何保证高可用
RabbitMQ基于主从模式实现高可用
RabbitMQ有三种模式:单机模式,普通集群模式,镜像集群模式。
单机模式:单机模式就是demo级别的,生产中不会有人使用。
普通集群模式:普通集群模式就是在多台机器上启动多个rabbitmq实例,每个机器启动一个。但是创建的queue只会放在一个rabbitmq实例上面,但是其他的实例都同步了这个queue的元数据。在你消费的时候,如果连接到了另一个实例,他会从拥有queue的那个实例获取消息然后再返回给你。
这种方式并没有做到所谓消息的高可用,就是个普通的集群,这样还会导致要么消费者每次随机连接一个实例然后拉取数据,这样的话在实例之间会产生网络传输,增加系统开销,要么固定连接那个queue所在的实例消费,这样会导致单实例的性能瓶颈。
而且如果那个放消息数据的queue的实例宕机了,会导致接下来其他实例都无法拉取数据;如果没有开启消息的持久化会丢失消息;就算开启了消息的持久化,消息不一定会丢,但是也要等这个实例恢复了,才可以继续拉取数据。 所以这个并没有提供高可用,这种方案只是提高了吞吐量,也就是让集群中多个节点来服务某个queue的读写操作。
镜像集群模式:这种模式,才是rabbitmq提供是真正的高可用模式,跟普通集群不一样的是,你创建的queue,无论元数据还是queue里面是消息数据都存在多个实例当中,然后每次写消息到queue的时候,都会自动把消息到多个queue里进行消息同步。
这种模式的好处在于,任何一台机器宕机了,其他的机器还可以使用。
坏处在于: 1、性能消耗太大,所有机器都要进行消息的同步,导致网络压力和消耗很大。2、没有扩展性可言,如果有一个queue负载很重,就算加了机器,新增的机器还是包含了这个queue的所有数据,并没有办法扩展queue。
Kafka高可用
kafka的一个基本架构:多个broker组成,一个broker是一个节点;你创建一个topic,这个topic可以划分成多个partition,每个partition可以存在于不同的broker上面,每个partition存放一部分数据。这是天然的分布式消息队列。
实际上rabbitmq并不是分布式消息队列,他就是传统的消息队列,只不过提供了一些集群、HA的机制而已,因为无论如何配置,rabbitmq一个queue的数据就存放在一个节点里面,镜像集群下,也是每个节点都放这个queue的全部数据。
kafka在0.8以前是没有HA机制的,也就是说任何一个broker宕机了,那个broker上的partition就丢了,没法读也没法写,没有什么高可用可言。
kafka在0.8之后,提过了HA机制,也就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的replica副本。然后所有的replica副本会选举一个leader出来,那么生产者消费者都和这个leader打交道,其他的replica就是follower。写的时候,leader会把数据同步到所有follower上面去,读的时候直接从leader上面读取即可。
kafka会均匀地将一个partition的所有数据分布在不同的机器上,这样就可以提高容错性。 如果某个broker宕机了,那个broker的partition在其他机器上有副本,如果这上面有某个partition的leader,那么此时会重新选举出一个新的leader出来,继续读写这个新的leader即可。
写消息: 写数据的时候,生产者就写leader,然后leader将数据落到磁盘上之后,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好了数据,就会发送ack个leader,leader收到了所有的follower的ack之后,就会返回写成功的消息给消息生产者,这只是一种模式,可以调整。
读数据: 消费数据的时候,只会从leader进行消费。但是只有一个消息已经被所有follower都同步成功返回ack的时候,这个消息才会被消费者读到。
03 如何处理重复消息
消息为什么会重复?
发送消息失败,比如接收ack时网络问题导致消息生产者重试发送。或者,消息消费者处理完业务逻辑,执行ack失败或者消费者自己宕机了,那么这条消息又会发送给另一个消费者,消息重复。
如何处理?
使用幂等,比如我们给每条消息都设置一个全局唯一的id,消费者处理完这条消息同时存储这条消息的全局id,可以存储到redis或者mysql中,每次消费消息时都找查询一下,如果有记录也说明已经处理过了,直接丢弃即可。
04 如何保证消息的有序性
有序分为全局有序和部分有序
如果要保证消息的全局有序,首先只能由一个生产者往Topic发送消息,并且一个Topic内部只能有一个队列(分区)。消费者也必须是单线程消费这个队列。这样的消息就是全局有序的!
不过一般情况下我们都不需要全局有序,即使是同步MySQL Binlog也只需要保证单表消息有序即可。
因此绝大部分的有序需求是部分有序,部分有序我们就可以将Topic内部划分成我们需要的队列数,把消息通过特定的策略发往固定的队列中,然后每个队列对应一个单线程处理的消费者。这样即完成了部分有序的需求,又可以通过队列数量的并发来提高消息处理效率。
05 如何处理消息堆积
水平扩容,增加Topic的队列数和消费者数量,队列数一定要增加。一个Topic中,一个队列只会分配给一个消费者。
当然你消费者内部是单线程还是多线程消费那看具体场景。不过要注意上面提到的消息丢失的问题,如果你是将接受到的消息写入内存队列之后,然后就返回响应给Broker,然后多线程向内存队列消费消息,假设此时消费者宕机了,内存队列里面还未消费的消息也就丢了。

网友评论