消息队列相关概念


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

消息队列就是我们常说的 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 是一个分布式、高吞吐量、高扩展性的消息队列系统。下面是一些基础的概念,也是 mq 的基础功能:

Topic(主题)

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

Partition(分区)

消息键(Key)是 Kafka 消息的一个可选属性,用于标识消息的逻辑关联关系。每条消息可以携带一个关键字作为其键,这个键可以是字符串、整数等数据类型。Kafka 中会对这个消息键进行 hash 实现分区

生产者发送的消息数据 Topic 会被存储在分区中,这个分区的概念和 ElasticSearch 中分片的概念是一致的,都是想把数据分成多个块,合理的把消息分布在不同的分区上,好达到我们的负载均衡

分区是被分在不同的 Broker 上也就是服务器上,这样我们大量的消息就实现了负载均衡。注意这里每个分区的内容是不相同的,分区最常见的用法是使用分区选择器 PartitionSelector 将消息根据键进行分区。分区函数会将相同键的消息分配到同一个分区中。这样,相同键的消息将被放置在同一个分区,从而保证了相同键的消息在分区中的顺序,比如按照用户的 id 进行分区,就会保证消息消费的顺序

消费者可以选择订阅一个或多个分区来消费消息。消费者在订阅时可以指定自己感兴趣的键。当消费者订阅特定主题的特定键时,只会接收到该键对应的分区中的消息。这样可以确保消费者只消费自己感兴趣的键的消息,并保证了消费者消费消息的顺序

每个 Topic 可以指定多个分区,但是至少指定一个分区。同时,为了保证一个分区中的数据不丢失,我们还可以设置多个机器拥有这一个分区的副本

Replica(副本)和 Broker(服务器)

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

Kafka 的服务器端由被称为 Broker 的服务进程构成,即一个 Kafka 集群由多个 Broker 组成。Broker 负责接收和处理客户端发送过来的请求,以及对消息进行持久化。多个 broker 组成一个 Kafka 集群,通常一台机器部署一个 Kafka 实例,一个实例挂了不影响其他实例

Consumer(消费者)

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

同时多个消费者也可以被分为一组 Group,一组中的所有机器只会收到一条消息 mq 消息,即在一个 Consumer Group 中,每一个 Topic 中的消息只能被这个组中的一个 Consumer 消费,这样保证了一个业务线集群部署的机器不会重复消费消息。发布-订阅下,所有消息都会被 kafka 会向每一组消费者发送一次,有 n 个消费组,就会发送 n 条消息

对于设置了多分区的 Topic,分区的个数和消费者组中的个数应该是一样的,一个消费者消费一个分区,这样每个消费者就成了单播形式,类似队列的消费形式。同样一个消费者组里边的消费者不能多于 Topic 的分区数,一旦多于,多余的消费者实例将不会被分配分区,因此不会收到任何消息。直到有其他消费者实例离开或者分区重新平衡发生,这时它们才有可能获得分区进行消费

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

Consumer Group(消费者组)

Kafka 为消费者组定义了 5 种状态,它们分别是:Empty、Dead、PreparingRebalance、CompletingRebalance 和 Stable
在这里插入图片描述
一个消费者组最开始是 Empty 状态,当重平衡过程开启后,它会被置于 PreparingRebalance 状态等待成员加入,之后变更到 CompletingRebalance 状态等待分配方案,最后流转到 Stable 状态完成重平衡

当有新成员加入或已有成员退出时,消费者组的状态从 Stable 直接跳到 PreparingRebalance 状态,此时,所有现存成员就必须重新申请加入组,这个重新申请加入组的过程就是重平衡(事实上重平衡是为了均匀地分配某个 topic 下的所有 partition 到各个消费组中的消费者,从而使得消息的消费速度达到最快,消费者组和重平衡息息相关)。在以下两种情况下,会触发 rebalance:

  • Topic 的分区数发生变化
  • 消费组内成员个数发生变化。例如有新的 consumer 实例加入该消费组或者离开组(因此在消费者发布的时候会经常出现重平衡)

当所有成员都退出组后,消费者组状态变更为 Empty。Kafka 定期自动删除过期位移的条件就是,组要处于 Empty 状态。因此,如果你的消费者组停掉了很长时间(超过 7 天),那么 Kafka 很可能就把该组的位移数据删除了

常见问题

kafka 在什么时候删除 message 呢

消息队列的高可用性

任何一个中间件提高可用性的最简单的做法就是集群部署。同时,不同的消息队列有不同的集群方式,我们以 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 之后,才能认为是写成功了(可类比于 mysql 的全同步)
  • 在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了

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

kafka 丢失数据

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

在这个场景里,我们只要保证每个 partition 都有副本,就可以保证 kafka 不会丢失数据了

消费者丢失数据

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

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

同时,消费者和消息队列一般都有心跳检测机制,可以让 mq 精准的感受到哪个消费者挂了以及时进行重平衡

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

比如几千万条数据在 MQ 里积压了七八个小时,从下午4点多,积压到了晚上很晚。这种情况怎么解决呢?首先我们需要分析问题原因,可能原因如下:特定时间节点流量大,mq 消费不过来

注意这里的原因不太可能是消费者消费不过来,因为在 kafka 里,就算所有的消费者消费了这条消息,这条消息也是不会被删除的,kakfa 只有在以下两种情况才会删除数据:

1、数据保留时间 (retention.ms):这是 Kafka 配置中设置的时间阈值,表示消息至少应在 broker 上保留的时间。无论消息是否被消费,只要消息没有超过这个时间阈值,就会被保留
2、数据保留大小(retention.bytes):这是一个可选的配置,用于限制每个 topic 分区的最大存储大小。当分区的数据量超过这个限制时,最旧的消息将被删除,即使它们还没有达到保留时间

因此我们只能在 mq 本身或者生产者方面去处理问题,如果在 mq 本身处理问题的话,我们可能有以下思路:

1,对于不重要的数据,丢掉也没有关系的数据,可以缩短全局消息过期时间,这样一定可以删除大批数据。二是设置数据保留大小,分区的数据量超过这个限制直接删除数据
2,在保证下游消费者组中消费者数量大于 mq 的分区数时,我们可以扩容 mq 本身。多申请 mq 机器,然后扩充分区数,这样多出来的机器可以存放新加入的数据,如果有必要,在生产者端上一版代码,使用选择器让新的 message 打到新机器中

如果想在消息生产者端去处理消息积压的问题,可以紧急上一版程序,去让原先发送 mq 的程序,先不发送 mq,转而将数据存放在 db 中,不管是存 mysql 还是 redis,只要有一个数据存放位置即可,然后等 kafka 里的数据全部消费掉了,我们在写一个脚本,将 db 里的数据打进 mq

消息队列过期失效问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值