zoukankan      html  css  js  c++  java
  • 消息中间件常见问题解答

    一   为什么使用消息队列?

    1 解耦
    通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。
    实际场景:店铺授权,通知其余各系统

    2 削峰
    高峰期大量的请求进来,队列起着缓冲的作用,不至于一下子涌进处理不完的任务,把服务拖死,

    3 异步
    很多业务没必要用同步做,而且同步耗时太长,所以用异步,如流水通知,


    二  消息队列有什么优点和缺点?

    1 系统可用性降低

         系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,人 ABCD 四个系统好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整,MQ 一挂,整套系统崩溃的,你不就完了

    2 系统复杂度提高

        硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。

    3 数据一致性问题

        A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了

    三  常见消息队列的比较

     

    综上,各种对比之后,有如下建议:

    一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;

    后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;

    不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 Apache,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。

    所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择。

    如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

    四  如何保证消息队列中的消息不被重复消费

    1 重复消费产生的问题描述

          因为消息发送是基于网络发送的,假设网络延迟或者网络卡顿,消息发送机制多次重试,消息重复发送的问题不可避免的发生。要直接避免不重复发送基本太难,因为网络环境无法预知,还会使程序复杂度加大,因此默认允许消息重复发送。因此无论是点对点,还是发布/订阅模型,都可能出现生产者发送多条一样的数据到MQ,此时就会出现重复数据。或者是消费者端启用了批量确认模式,但批量业务处理到一半后服务挂了,并未来得及ACK,此时也会导致重复消费。

    其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性。

    给你举个例子吧。假设你有个系统,消费一条往数据库里插入一条,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,不就保留了一条数据?

    一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性

    幂等性,我通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。

    那所以第二个问题来了,怎么保证消息队列消费的幂等性?

    其实还是得结合业务来思考,我这里给几个思路:

    (1)比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧

    (2)比如你是写redis,那没问题了,反正每次都是set,天然幂等性

    (3)比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。

    五  消息的可靠性传输

    1  生产者确保发送的可靠性  

    用生产者的事务模式,发送者确认模式,确保发送的可靠性

    2  RabbitMQ弄丢了数据  

      就是RabbitMQ自己弄丢了数据,这个你必须开启RabbitMQ的消息持久化(交换器+队列+消息 三者都持久化才能保证消息持久化),消息写入之后会持久化到磁盘,哪怕是RabbitMQ自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。

      除非极其罕见的是,RabbitMQ还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。 

    3  消费端弄丢了数据  

      消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,RabbitMQ认为你都消费了,这数据就丢了。  

    这个时候得用RabbitMQ提供的ack机制,简单来说,就是你关闭RabbitMQ自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。

      这样的话,如果你还没处理完,不就没有ack?那RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。

    六  消息的顺序性

    当我们的系统中引入了MQ之后,不得不考虑的一个问题是如何保证消息的顺序性,这是一个至关重要的事情,如果顺序错乱了,就会导致数据的不一致。

           比如:业务场景是这样的:我们需要根据mysql的binlog日志同步一个数据库的数据到另一个库中,加如在binlog中对同一条数据做了insert,update,delete操作,我们往MQ顺序写入了insert,update,delete操作的三条消息,那么根据分析,最终同步到另一个库中,这条数据是被删除了的。但是,如果这三条消息不是按照insert,update,delete顺序被消费,而是按照delete,insert,update的顺序被消费,那么最终这条数据是会保存到新库中的。这就导致了数据错乱了。下面分别讲解下RabbitMQ是如何保证消息的顺序性。

    1 RabbitMQ的消息不被顺序消费的情况

     注意:queue(队列)中的消息只能被一个消费者所消费,然后消费者在消费消息的过程中是无序的。如上图所示,如果按照BAC的消费顺序,

    那么最终数据库中是被保存这条数据的。这和我们预期的结果不符,如果这样的情况很多,那么就造成了数据库中的数据完成不对,同步工作也是白费了

    1 RabbitMQ保证消息顺序性的措施

    如图所示,RabbitMQ保证消息的顺序性,就是拆分多个 queue,每个 queue 对应一个 consumer(消费者),就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 收到的消息就是有序的,内部可以用内存队列做排队,然后分发给底层不同的 worker 来处理。也可以单线程处理,一条条消费并ACK

     七  如何保证消息队列的高可用

    RabbitMQ的高可用性

      RabbitMQ是基于主从做高可用性的,有三种模式:单机模式,普通集群模式,镜像集群模式

    1 单机模式:

      demo级别

    2 普通集群模式:

    在多台机器上启动rabbitmq实例,每个机器启动一个。

      但是你创建的queue,只会放在一个rabbtimq实例上,每个实例都同步queue的元数据。

      消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从queue所在实例上拉取数据过来。 这种方式没做到所谓的分布式,就是个普通集群。

      如果那个放queue的实例宕机了,会导致接下来其他实例就无法从那个实例拉取。

      所以这就没有什么所谓的高可用性可言了,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个queue的读写操作。

    3 镜像集群模式:

    这种模式,才是所谓的rabbitmq的高可用模式

      你创建的queue,无论元数据还是queue里的消息都会存在于多个实例上,然后每次你写消息到queue的时候,都会自动把消息到多个实例的queue里进行消息同步。  

      好处:你任何一个机器宕机了,别的机器都可以用。

      坏处:

            1  性能开销大,消息同步所有机器,导致网络带宽压力和消耗很重!

       2  没有扩展性,如果某个queue负载很重,你加机器,新增的机器也包含了这个queue的所有数据,并没有办法线性扩展你的queue  

      开启镜像集群模式:rabbitmq有很好的管理控制台,在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点的,也可以要求就同步到指定数量的节点,然后你再次创建queue的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

    八 如何解决消息队列的延时及过期失效问题? 如何解决几百万消息持续积压几小时?

        你看这问法,其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了,或者消费的极其极其慢。接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是整个这就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如rabbitmq设置了消息过期时间后就没了怎么办?

    所以就这事儿,其实线上挺常见的,一般不出,一出就是大case,一般常见于,举个例子,消费端每次消费之后要写mysql,结果mysql挂了,消费端hang那儿了,不动了。或者是消费端出了个什么叉子,导致消费速度极其慢。

    关于这个事儿,我们一个一个来梳理吧,先假设一个场景,我们现在消费端出故障了,然后大量消息在mq里积压,现在事故了,慌了。

    1、大量消息在mq里积压了几个小时了还没解决

    几千万条数据在MQ里积压了七八个小时,从下午4点多,积压到了晚上很晚,10点多,11点多。

    这里有一个场景,确实是线上故障了,这个时候要不然就是修复consumer的问题,让他恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。

    一个消费者一秒是1000条,一秒3个消费者是3000条,一分钟是18万条,1000多万条。

    所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概1小时的时间才能恢复过来。

        一般这个时候,只能操作临时紧急扩容了,具体操作步骤和思路如下:

    先修复consumer的问题,确保其恢复消费速度,然后将现有cnosumer都停掉;

    新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的queue数量;

    然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue;

    接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据;

    这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据;

    等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息;

    2、这里我们假设再来第二个坑,消息由过期时间
        假设你用的是rabbitmq,rabbitmq是可以设置过期时间的,就是TTL,如果消息在queue中积压超过一定的时间就会被rabbitmq给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在mq里,而是大量的数据会直接搞丢。

    这个情况下,就不是说要增加consumer消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。

    这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入mq里面去,把白天丢的数据给他补回来。也只能是这样了。

    假设1万个订单积压在mq里面,没有处理,其中1000个订单都丢了,你只能手动写程序把那1000个订单给查出来,手动发到mq里去再补一次。

    3、然后我们再来假设第三个坑
        如果走的方式是消息积压在mq里,那么如果你很长时间都没处理掉,此时导致mq都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,消费一个丢弃一个,都不要了,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。

  • 相关阅读:
    如何有效地报告 Bug
    Linux开始结束ping命令
    【转】未能加载文件或程序集“XXX”或它的某一个依赖项。试图加载格式不正确的程序。
    .net session 使用误区
    [转] ADO.NET调用存储过程带输出参数或返回值
    ASP.NET Easyui datagrid增删改+sqlhelper
    C#分割字符串并统计重复出现的次数
    C# 读取TXT文本数据 添加到数据库
    记一次Spring项目打包问题排查
    Wiki系列(三):我的Wiki
  • 原文地址:https://www.cnblogs.com/hup666/p/13340311.html
Copyright © 2011-2022 走看看