关于消息队列的一些思考

8 篇文章 0 订阅
4 篇文章 0 订阅

在一个事务中,A系统使用rpc调用了B系统的service,在本地调试时经常会出现rpc调用超时的问题,将会导致整个事务的回滚,况且随着业务量的增大,调用rpc的次数也会急剧增加。组内大佬一排砖头决定:将rpc调用改成消息队列MQ的方式,A系统成功将请求发送给MQ后便会继续执行后续操作。

作为组内十万个为什么的我就会思考:为什么会使用消息队列呢?使用了消息队列会带来什么好处呢?它会有什么缺点吗?

先说说消息队列的使用场景吧,大致分为三个:解耦、异步、削峰

解耦
  • 面向对象编码设计中,要求各个模块注重各自的代码实现,完成各自的子功能,减少模块之间的联系导致的对对方代码的“入侵”。
  • 设想如下场景:A系统通过接口发送数据给B、C系统,则在A中增加调用B、C的代码。此时,业务要求A需发送给D系统,则又需要在A中增加对D的调用,然后呢,业务又说不需要对D发送数据了,则在A中又要删除对D的调用…满满的心累有没有?
  • 心累是一点,另外A中严重地夹杂了很多和别的系统的交互代码,还要天天担心BCDEF…是否被意外kill掉了!
  • 但是!使用了消息队列MQ后情况就大大地不同了,A只负责将数据作为消息发送到MQ中,让需要消费的其他系统自己订阅消费即可。A内心OS:我反正已经成功地把消息发送给MQ了,你们自取自用,不想消费了的取消订阅就好,有新的系统想消费的就订阅。消费成功与否都是你们自己的事。
  • 就这样,通过MQ的消息发布和订阅,系统之间终于解耦了!鼓掌!
异步
  • 念初中那会,我可以为了下载几首纠结伦的歌等待很长的一段时间,可以用很慢的3G网在寝室被窝里刷QQ空间,但现在呢?打开某网页稍微慢了点我就会对该网站印象分打不及格,如今5G商用进入了倒计时,人们对速度的要求越来越挑剔。一般的互联网产品,为了用户对速度的无感知,响应时间基本需控制在200ms内。
  • 我们假设一个场景:A系统接收上游系统的数据后,要求在本系统持久化,并在后续的B、C系统写入操作。假设A持久化需2ms,B、C写入分别是400ms,500ms。这样串行的操作则需要将近1s的时间!要是这是个用户通过页面表单提交数据的操作,用户心里肯定在骂娘了。
  • 这时候MQ就派上用场了,A本地持久化后,将数据发送给MQ,发送成功后则返回写库成功的ack。假设前者花费了2ms,后者花费了4ms。总计6ms给用户的感觉就是“哇塞,这系统真牛!”。后续B、C对消息的消费也就可以异步执行了。
削峰
  • 每年看到双十一,618,情人节等大型电商流量猛增的节日,都会惊叹各个平台设计开发人员的niuAbility,优秀的网站能带给各位剁手党守着零点买买买的美好体验。
  • 假设平日某系统的并发请求数也就200,到了某狂欢节的0:00-1:00,并发数到了4000+,大量的访问请求涌向数据库,要是没有精良的设计,不崩才怪。崩了不就影响了各位剁手党的买买买的好心情了么。狂欢节一过,并发请求数又回到了低潮点。
  • 用户->系统A->数据库 被 用户->MQ->系统A->数据库 替代。
  • 庞大的用户群体以4000+的并发数写入MQ,A系统以每秒钟拉取2000请求的速率处理请求。这样不管,写入MQ的速率有多快,系统A没每秒钟的处理数量都是一样的在其可控范围内的。高峰期一过,A系统就可以毫无压力地将积压的消息消费掉了。

陈述了使用MQ的众多好处,再来说说使用过程中需要注意的几个点。

系统可用性的降低
  • 原来强耦合的系统之间多加了一个外来物种——MQ,如果MQ本身宕机了怎么办?MQ一挂,A写入有问题,B、C订阅消费有问题,整一个就给挂了。引入了消息队列后,系统的高可用性就是一个值得探讨的话题。
    • RabbitMQ如何保证高可用

      • 普通集群模式(无高可用性)

        • 多台机器启动多个RabbitMQ实例,每台机器启动一个。我们所创建的消息队列Queue只会放在其中一个实例上。每个实例都同步queue的元数据,即queue的一些配置信息,通过元数据可以找到queue所在的实例。消费时如果连接到了另外的实例,则会通过元数据拉取原queue所在实例上的数据以供消费。
          • 普通集群模式导致集群之间大量的数据传输操作
          • 万一原queue对应的实例宕机了,则其他实例无法从此实例中获取数据。即使原实例做了数据的持久化,也得等重启后才可以同步数据到其他实例。
          • 并非高可用,只是增加了系统的吞吐,多个实例共同服务某一queue的读写访问。
      • 镜像集群模式(高可用性)

        • 我们创建的queue,元数据和业务数据都会存放于多个实例中。每个节点都包含该queue完整的数据。写数据到queue时,会自动将数据同步到多个实例中。
          • 好处:其中一台实例宕机了,消费者可以到其他实例上消费数据。

          • 缺点:

            • 性能开销大,所有的数据都需要同步到所有的实例上,带来网络带宽的压力。
            • 并非分布式,如果某queue的数据量太大负载很重,即使加了新的机器,新机器也得同步之前所有的数据。
    • Kafka如何保证高可用

      • 基本架构:由多个broker组成;对于一个新建的topic,会被分成多个分区partition,每个partition 都位于不同的broker上,每个partition分别存储一定量的数据。也就是说一个topic的数据被分配在多个partition中。
      • Kafka 最初的版本 0.8 是没有高可用之说的,其中一个机器宕机了,那么该机器上的partition就暂时废了,没法被读写访问。
      • Kafka 0.8之后的版本中加入了HA(High Availability)机制——Replica 副本机制,每个partition的数据都会被备份到其他的partition中形成自己的Replica副本。所有的Replica选举一个leader,读写访问操作都是和leader交互的,其他的Replica是follower。读操作时,leader负责将数据同步到其他follower上。
        - 为什么读写操作只能在leader上执行呢?要是能从任意的follower上读写,则数据的一致性难以保持,整个系统就实在是太太太尼玛复杂了。
        - 要是某台机器宕机了,上面的broker里正好有某个partition的leader咋整呢?此时会从follower中选举出新的leader,OK,此后的读写操作和新的leader交互。
        - 写操作,leader本地持久化,follower从leader处pull数据同步到自身。leader收到了follower的pull 成功的ack后才会 返回写操作成功。
        - 读操作,从 leader 去读,当消息已经被所有 follower 都同步成功返回 ack 的时,这个消息才会被消费者消费到。
系统复杂性增加
  • 如何保证消息不会被重复消费?

    • 谈谈重复消费:消息被重复消费是一件很可怕的事,譬如重复支付,搁在你身上怕是不敢再在这个平台上消费了,更别谈免密支付啥的。保证消息不被重复消费,我认为应该是各个系统开发人员的职责。不该把锅甩给消息队列。

      • Kafka如何保证消息不会被重复消费?

        • Kafka 有offSet的概念,每个写入的消息都有 特定的offSet来表示它的消息序号。消费者消费了消息后悔定时定期将这些消息的offSet提交,表示:服务重启后就从我本次消费完的offSet开始消费吧。但如果直接kill了进程再重启,这种粗暴的方式不会让消费者虽然消费了一些消息,但还没来记得提交offSet。这样,重启后,有些消息就会难以避免再次被消费。
        • 如果消费者对消息的处理是:往自己库里插入数据,就会造成相同数据多次持久化;要是这是一条跳转收银台收取用户money的操作,可想而知被用户知道后的后果!
        • 此时有必要引入一个概念叫做:幂等性。什么是幂等
        • 简单来讲,保证幂等性便是对于同一条数据,同一个请求,即使前后“轰击”了系统好几次,也要保证系统对此作出的反应的正确无误的。
        • 那么怎么保证消息队列的幂等性呢?
          • 譬如消费到消息后需进行写库操作,先根据业务判断什么是主键(联合主键,要是数据库里已经存在了该主键(联合主键),则抛弃该条数据或者update而非insert。基于数据库的唯一键能避免数据重复插入导致的脏数据存在,是一种保证幂等的方式。
  • 如何保证消息丢失的情况?

    • 保证消息不丢失即保证消息的可靠传输,假设该消息是涉及到用户自身利益,公司群体利益的计费,扣款,收银等核心链路数据,消息一旦丢失,亏就大发了!
    • 谈谈Kafka对保证消息不丢失都有哪些操作:
      • 消费者搞丢了数据:
        • 消费消息后,自动提交了offSet,让Kafka认为你已消费完成,但其实你还没完成对消息的处理自己就挂了,造成了消息的丢失。你一脸懵B。
        • 为了避免上述尴尬场景,需将Kafka的自动提交关闭,等待消费者消费完消息后手动提交offSet,就可以保证数据不丢失。
        • 手动提交offSet可能会导致数据的重复消费,此时就需要消费端进行必要的幂等控制啦。
      • Kafka搞丢了数据:
        • 设想这样一个场景:Kafka所在的某个broker宕机,需重新选举partition的leader,此时正好有些follower没有从原leader同步完数据,此时原leader挂掉了,这些缺数据的follower中又正好有人被选举成了leader,自然而然就造成了一些数据的丢失。
      • 如何避免上述尴尬场景呢?起码设置如下 4 个参数:
        • 给 topic 设置 replication.factor 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
        • Kafka 服务端设置 min.insync.replicas 参数:必须大于 1,要求一个 leader 感知到有至少一个 follower 还跟自己保持联系,确保 leader 挂了还有一个 follower 。
        • producer 端设置 acks=all:要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。
        • producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试):这个是要求一旦写入失败,就无限重试,卡在这里了。
    • 生产者搞丢了数据
      • 设置了 acks=all,一定不会丢,因为只有你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。否则生产者重试无限次。

详细见解请戳:
为什么使用MQ
如何保证高可用
如何保证幂等

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值