消息队列相关概念


在之前学习阶段没有重视过这个中间件,因为之前觉得这玩意很鸡肋,在一次项目中使用到了,感觉在特定的环境下该中间件还是挺好用的,现在深入了解一下

消息队列就是我们常说的 MQ,英文叫 Message Queue,是作为一个单独的中间件产品存在的,独立部署

引入一个新的技术产品,肯定是要考虑为什么要用它呢?消息队列也不列外,说到为什么要用,还真是因为它能在某些场景下发挥奇效。例如:解耦,异步,削峰,这三个词你也听说过吧,那下面就就从这三个好处出发,讲讲到底什么是解耦,异步,削峰

消息队列的好处

消息队列的缺点,都在我们下午的常见问题里了。引入任何技术,都需要考虑可能引发的问题

解耦

解耦都不陌生吧,就是降低耦合度,我们都知道 Spring 的主要目的是降低耦合,那 MQ 又是如何解耦的呢?

系统 A 是一个关键性的系统,产生数据后需要通知到系统 B 和系统 C 做响应的反应,三个系统都写好了,稳定运行

某一天,系统 D 也需要在系统 A 产生数据后作出反应,那就得系统 A 改代码,去调系统 D 的接口,好,改完了,上线了。假设过了某段时间,系统 C 因为某些原因,不需要作出反应了,不要系统 A 调它接口了,就让系统 A 把调接口的代码删了,系统 A 的负责人可定会很烦,改来改去,不停的改,不同的测,还得看会不会影响系统 B,系统 D

而且这样还没考虑异常情况,假如系统 A 产生了数据,本来需要实时调系统 B 的,结果系统 B 宕机了或重启了,没调成功咋办,或者调用返回失败怎么办,系统 A 是不是要考虑要不要重试?还要开发一套重试机制,系统 A 要考虑的东西也太多了吧

那如果使用 MQ 会是什么样的效果呢?系统 A 产生数据之后,将该数据写到 MQ 中,系统 A 就不管了,不用关心谁消费,谁不消费,即使是再来一个系统 E,或者是系统 D 不需要数据了,系统 A 也不需要做任何改变,而系统 B、C、D 是否消费成功,也不用系统 A 去关心,通过这样一种机制,系统 A 和其他各系统之间的强耦合是不是一下子就解除了

总结一下解耦的意义:

  • 当有类似分布式(单个系统多态机器)、多个系统依赖某一系统产出的数据时,使用 MQ 可以完成该任务
  • 可以灵活配置,只需要订阅消息即可
  • MQ 揽下了所有的异常情况,消息传递之间的异常都可以在这一层处理

异步

使用了 MQ 可以将一些不影响业务主流程的问题异步处理,给用户立即返回结果,系统 A 就只需要把产生的数据放到 MQ 里就行了,就可以立马返回用户响应,用户体验提升了很多

系统 A 把数据写到 MQ 里,系统 B、C、D 就自己去拿,慢慢消费就行了。一般就是一些时效性要求不高的操作,比如下单成功系统 A 调系统 B 发下单成功的短信,短信晚几秒发都是 OK 的

削峰

削峰是什么意思?大家都知道对于大型互联网公司,典型的就是电商,时不时的搞一些大促,流量会高于平时几十倍几百倍…例如,平时下单也就每秒一二十单,对于现有的架构来说完全不是事儿,那大促的时候呢?每秒就有可能举个例子是5000单,如果说下单要实时操作数据库,假设数据库最大承受一秒2000,那大促的时候一秒5000的话数据库肯定会被打死的,数据库一挂导致系统直接不可用,那是多么严重的事情。

所以在这种场景下使用 MQ 完美的解决了这个问题,下游系统下单时只需要往 MQ 里发消息,我的订单系统可以设定消费的频率,比如每秒我就消费2000个消息(在数据库的可承受范围),不管你下游系统每秒下多少单,我都保持这个速率,既不会影响我订单系统的数据库,也不影响你下游系统的下单操作,很好的保护了系统,也提高了下单的吞吐量。

我们都知道,大促也就几分钟的事,往多了说是几个小时吧,咱就说4个小时吧,每秒5000,4小时 7200W 单往 MQ 里写,订单系统每秒消费2000单,大促过后,MQ 里会积压 4320W 个消息,剩下的就慢慢消费呗。当然了,大促的时候肯定会临时申请加机器的,每秒消费不止2000

这就是削峰,将某一段时间的超高流量分摊到更长的一段时间内去消化,避免了流量洪峰击垮系统

消息队列虽然有诸多好处,但是使用时也需要注意一些问题:消息队列因为会造成队列堆积,时间不长的数据才能放进去,跨天的数据尽量不放进去

Kafka

kafka 是一个分布式、高吞吐量、高扩展性的消息队列系统。下面是一些基础的概念:

Topic(主题):每一个发送到 Kafka 的消息都有一个主题,也可叫做一个类别,类似我们传统数据库中的表名一样,比如说发送一个主题为 order 的消息,那么这个 order 下边就会有多条关于订单的消息,只不过 kafka 称之为主题,都是一样的道理

Partition(分区):生产者发送的消息数据 Topic 会被存储在分区中,这个分区的概念和 ElasticSearch 中分片的概念是一致的,都是想把数据分成多个块,好达到我们的负载均衡,合理的把消息分布在不同的分区上,分区是被分在不同的 Broker 上也就是服务器上,这样我们大量的消息就实现了负载均衡。每个 Topic 可以指定多个分区,但是至少指定一个分区。同时,为了保证一个分区中的数据不丢失,我们还可以设置多个机器拥有这一个分区的副本

Replica(副本):副本就是分区中数据的备份,是 Kafka 为了防止数据丢失或者服务器宕机采取的保护数据完整性的措施,一般的数据存储软件都应该会有这个功能

Consumer Group 和 Consumer:我们知道传统的消息队列有两种传播消息的方式,一种是单播,类似队列的方式,一个消息只被消费一次,消费过了,其他消费者就不能消费了;另一种是多播,类似发布-订阅的模式,一个消息可以被多个消费者同时消费

Kafka 通过消费者组的方式来实现这两种方式,在一个 Consumer Group 中,每一个 Topic 中的消息只能被这个组中的一个 Consumer 消费,所以对于设置了多分区的 Topic 来说,分区的个数和消费者的个数应该是一样的,一个消费者消费一个分区,这样每个消费者就成了单播形式,类似队列的消费形式。所以说,一个消费者组里边的消费者不能多于Topic的分区数,一旦多于,多出来的消费者就不能消费到消息

另外,不同的消费者组可以同时消费一个消息,这样就实现了多播,类似发布-订阅的模式。我们可以设置每个组中一个消费者的方式来实现发布-订阅的模式。当我们有多个程序都要对消息进行处理时,我们就可以把他们设置到不同的消费者组中,来实现不同的功能

同时 kafka 依赖 zk,在分布式系统中,消费者需要知道有哪些生产者是可用的,通过使用 ZooKeeper 协调服务,Kafka 就能将 Producer,Consumer,Broker 等结合在一起,kafka 的选举、负载均衡、partition 对应都是通过 zk 来的。比如 broker 向 Zookeeper 进行注册后,生产者根据 broker 节点来感知 broker 服务列表变化,这样可以实现动态负载均衡
在这里插入图片描述

常见问题

消息队列的高可用性

任何一个中间件提高可用性的最简单的做法就是集群部署。同时,不同的消息队列有不同的集群方式,我们以 Kafka 举例

Kafka 天生就是一个分布式的消息队列,它可以由多个 broker 组成(每个 broker 是一个节点)。创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 只保存一部分数据

Kafka 提供了 replica 副本机制来保证每个节点上的数据不丢失。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,来提高容错性。每个 partition 的数据都会同步到其他机器上,形成多个副本。然后所有副本会选举一个 leader 出来,那么生产和消费都找 leader,其他副本只做同步操作。当 leader 挂了集群会自动去找 replica,然后会再选举一个 leader 出来,这样就具有高可用性了

写数据的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。只有同步好所有数据才会给 leader 发生 ack,leader 收到所有 follower 的 ack 之后,才会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)

消费的时候,只会从 leader 去读,但是只有一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到

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

生产者丢失数据

生产者将数据发送到 MQ 的时候,可能数据就在半路给搞丢了,因为网络啥的问题。Kafka 为了处理这个问题,可以这么设置参数:

  • 在 producer 端设置 acks=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了
  • 在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了

即只有当所有的 follower 都同步到了消息之后才会给生产者返回 ack 应答,如果没满足这个条件,生产者会自动不断地重试,重试无限次

kafka 丢失数据

kafka 挂了,导致数据丢失。这种情况是是 kafka 某个节点宕机,然后重新选举 partiton 的 leader 时,要是此时其他的 follower 刚好还有些数据没有同步,同时 leader 挂了,然后选举某个 follower 成 leader 之后,他就少了一些数据

在这个场景里,我们只要配置上面的参数,就可以保证 kafka 不会丢失数据了

消费者丢失数据

消费到了这个消息,然后消费者那边自动提交了 offset,让 kafka 以为你已经消费好了这个消息,其实你刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢了

大家都知道 kafka 会自动提交 offset,那么只要关闭自动提交 offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是会重复消费,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,幂等性问题自己保证就好了

大量消息积压/消息队列满了

比如几千万条数据在 MQ 里积压了七八个小时,从下午4点多,积压到了晚上很晚。可能原因是线上故障了,或者消费速度跟不上生产速度

一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条,所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来

如果业务非常紧急的话,只能操作临时紧急扩容了,具体操作步骤和思路如下:

  • 先修复 consumer 的业务问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉
  • 新建一个 topic,partition 是原来的10倍,临时建立好原先10倍或者20倍的 queue 数量
  • 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的 queue
  • 接着临时征用10倍的机器来部署原来的消费者,每一批消费者消费一个临时 queue 的数据

这种做法相当于是临时将 queue 资源和 consumer 资源扩大10倍,以正常的10倍速度来消费数据。等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的 consumer 机器来消费消息

为什么不直接扩大消费者机器呢?

消息队列过期失效问题

假设你用的是 rabbitmq 等可以设置过期时间的 mq,如果消息在 queue 中积压超过一定的时间就会被 rabbitmq 给清理掉,这个数据就没了,但是这些数据非常重要,我们先恢复一下

这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,等过了高峰期以后(晚上12点),用户都睡觉了。这个时候就开始写程序,将丢失的那批数据,一点一点地查出来,然后重新灌入 mq 里面去

如何保证消息的顺序性

如果是使用多个 partition 的话,肯定会出现顺序性问题的,除非解决拜占庭将军问题,因此如果有顺序性的需求的话,可以只设置一个 partition,每次对消费者发送一个消息,都需要接受到 ack 响应

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值