文章目录:
- 消息队列如何选型,各自的优缺点?
- 如何保证其可用性
- 如何保证消息不丢失?
- 如何保证消息的消费不重复(EOS语义)?
- 如何保证消息的消费的顺序性?
- 如何解决集群故障后大量数据积压?
一、消息队列的优缺点
我们常常听见一些大佬说学习一个东西不仅要知其然,还要知自其所以然。我们项目中常常用到消息队列,那么为什么我们需要使用消息队列,用了它对系统带来了什么好处了?解决了什么问题,还是是为了用而用,架构师设计的架构就是这样?对于这一系列疑问我们应该一探究竟,才能理解使用消息队列的好处。
消息队列的优点
1. 解耦
在一个简单的业务系统中,假设我们需要通过O系统的接口来调用其他子系统。如下图所示:
刚开始,O系统的接口中需要持有其他两个子系统的引用,当过一段时间时,突然来了需求,需要调用C系统,那么可能需要修改代码如下:
public void method(){
A a=new A();
B b=new B();
C c=new C();
}
又过了一段时间,需要加入D系统,代码修改如下:
public void method(){
A a=new A();
B b=new B();
C c=new C();
D d=new D();
}
又过了一段时间,B系统不需要调用了,代码修改如下:
public void method(){
A a=new A();
C c=new C();
D d=new D();
}
又过了一段时间,,,,,(此刻程序员心里mmp)。那么有没有一种设计能够不需要那么频繁的修改业务代码,降低代码之间的耦合度呢?
这时我们就用到了消息队列,它的第一个优点就是解耦。在引入消息队列之后,系统的整个架构如下:
从上图我们可以看到,业务代码之间不需要直接耦合,而是间接的通过消息队列来通信,这样一来就降低系统的耦合度,一旦需要加入新子系统,我们只需要让新的系统订阅消息队列即可,而O系统只需要将消息发送到MQ中即可,之前的问题完美解决。
2.异步
假设我们的一个系统需要执行SQL,查询数据库,那么其基本流程图如下:
按照上图的描述,执行一条请求需要耗时50+100+100=250ms。如果调用的系统增加,请求时延很容易达到秒级别,这对于一个高并发系统是绝对不允许的。这时如果我们引入消息队列呢?
在引入消息队列后,我们只需要将消息写入消息队列即可返回给用户,请求时长为50+50=100ms,相比之前降低了2.5倍。这里就体现了消息队列的异步特性。(虽然这个例子有点勉强,但是并不影响我们理解其异步的优点)
3.削峰
一个系统在某大促期间,大量的请求全部去请求业务系统,比如某一个时间段的QPS达到1万。假设这里有A,B两个子系统,每个处理5000个。这可能就达到了单个服务器性能的极限(假设普通服务器)。当QPS达到2万的时候,很可能系统会挂掉,那么如何来解决呢?(这里我们暂不考虑横向扩展的状况)。
这时不管并发量有多大,对于两个子系统,只要尽其所能去处理请求,不至于使得机器宕机,服务不可用,等到高峰期过去很快就能处理完所有的请求。这也体现了消息队列的另一个优点削峰。
在了解了消息队列的一些优点之后,下面介绍几款比较常用的MQ,看看他们的各自优缺点是什么。
常用MQ的优缺点
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比RocketMQ | 万级,吞吐量比RocketMQ和 | 10万级, | 10万级 ,kafka最大的优点 |
. | 和Kafka要低了一个 | 和Kafka要 | RocketMQ也是可以 | 就是吞吐量高。 |
. | 一个数量级 | 低了一个数量级 | 支撑高吞吐的一种MQ | 用于实时计算,日志采集等场景 |
topic数量对吞吐量的影响 | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降 | topic从几十个到几百个的时候,吞吐量会大幅度下降所以在同等机器下, | ||
这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | 同等机器下保证kafka的topic数量不要太多,如要支持高并发,可以增加机器资源 | |||
时效性 | ms | 微秒,延迟最低 | ms | ms |
可用性 | 主从高可用架构 | 主从高可用架构 | 非常高,分布式架构 | 非常高,分布式架构多副本机制,ISR机制 |
消息可靠性 | 低 | 参数优化可以做到0丢失 | 参数优化可以做到0丢失 | |
功能支持 | 性能完善 | 基于erlang语言开发,性能好,延时低 | 完善,性能好,分布式 | 功能简单,常用语大数据场景 |
二、高可用性
对于一个系统,如何做到高可用很重要,尤其在分布式系统中。这里以RabbitMQ和Kafka为例,我们简单介绍其高可用。
RabbitMQ
对于RabbitMQ来说,主要有三种模式:单机模式,普通集群模式,镜像集群模式。
- 单机模式:这种模式很简单,就是一台机器处理所有消息。
- 普通集群模式:多台机器,当消费者拉取在一台机器中拉取数据的时候,如果不存在,那么就去其他机器中拉取,这种方式容易产生大量的网络传输,耗时比较严重。而且一台机器宕机,数据很可能会丢失。
- 镜像集群模式:多台机器,每台机器上的数据都一样,这种模式数据不会丢失,但是数据冗余,机器浪费比较严重,大数据场景下不适用。
Kafka
对于kafka来说,它主要通过多个broker,多副本机制以及ISR机制来保证其高可用性。其基本架构图如下:
对于kafka而言,每个topic会分为多个parition,每个partition会有多个副本,其中会选举出来一个leader,一旦一个副本所在的broker宕机,那么其他副本察觉到就会选举出新的leader,保证数据不丢失。
关于ISR机制的详细介绍可以参考之前的一篇文章【深入理解Kafka数据高并发写入、可靠性以及EOS语义】
三、如何保证消息不丢失
对于一个消息队列,如何保证其处理的消息不丢失呢?首先我们就一个 系统来讨论一下其消息丢失的情况。
以上消息队列主要三个部分组成,生产者、消息队列,消费者。发生消息丢失的情况有以下三种:
- 在生产者端,发送消息给MQ,可能由于网络等原因丢失。
- 在MQ中,可能生产者刚发送来消息,没来得及持久化发生宕机,内存中的数据丢失。
- 在消费者端,拉取到MQ的消息之后,还没来得及处理消费者宕机。
针对以上三种情况,我们以kafka为例来看看其是怎样保证数据不丢失的。
Kafka中,product端通过ack机制来保证数据不丢失,其有一个重要的参数acks
可以设置,保证数据不丢失,为了防止MQ中的数据在内存中丢失,我们来让其写入多个副本并且持久化来保证数据不丢失。
在消费端,kafka有自动提交机制,当一条消息被消费者接收到之后,就会发送自动提交消费者的offset给kafka,此时我们可以通过参数enable.auto.commit
设置为false来取消自动提交,当消费者处理完消息之后我们手动提交。不过这里也有一个问题,就是当你处理完数据之后,还没来得及手动提交,服务挂了,下次就会重复消息,此时需要做幂等性处理(比如使用数据库的唯一键)。
在生产者写入broker中时,kafka来了防止在leader中数据还没来得及同步给follower就宕机导致数据丢失,我们可以通过参数replication.factor
设置多副本,必须设置为大于1的值,还需要在kafka服务端通过参数min.insync.replicas
保证ISR机制,即保证至少有一个follower与leader同步了数据才认为是发送成功。
好了,时间比较晚了,今天就介绍到这里,后面的文章中会继续今天遗留的话题继续讲解,欢迎关注,也欢迎批评指正。