消息队列 | 青训营笔记

121 阅读10分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记

一:消息队列介绍

1.1 消息队列使用场景

当系统中出现"数据生产"与"数据消费"的速度不一致;或者"数据生产"与"数据消费"两个环节的性能存在差异;或者在从数据生产到消费过程中遭遇到系统崩溃、链路耗时长尾、服务日志丢失等问题,就需要用到消息队列。

这样说比较抽象,举个在现实中存在的例子:

  • 小明今天10个达布溜工资到账,打算购买Apple Watch提升生活档次(用来装逼),这时候他在某搜索页面发现某个之前没见过的电商平台推出了限时秒杀活动,Apple Watch只需要官方价格的十分之一。激动的小明立马下载了APP,并下了订单。但是由于此款新APP秒杀活动过于火爆并且设计过于垃圾,面对大量的数据请求时系统处理非常缓慢,以致于付了钱之后半天没有弹出下单成功的消息,这让小明怀疑自己是不是误下了一个杀猪APP。经过了180s的苦等,下单成功的消息终于弹出来了。小明觉得消费体验十分差劲,发誓等Apple Watch送到家之后就把APP删了。
  • 这怎么行?小明还没被这款APP宰,薅了个羊毛就跑了?APP的老板十分生气,并把他们基础架构团队的张三直接炒鱿鱼了。
  • 在以上的案例中,小明下单Apple Watch,存在以下几个业务逻辑(不全):发起订单、库存记录-1、订单记录+1、通知商家、消息反馈用户。小明等了180s,可能是上面的任何一个或多个服务挂了。这里我们就假设"通知商家"这一服务挂掉了,张三处理了180s这项服务才重启。
  • 这个架构肯定是有问题的,面对高量请求时就会服务瘫痪。而这种业务环境下状态是不能回滚的,毕竟小明付了钱,被举报了是有概率上CCTV315晚会的。
  • 临危受命的李四接手了张三的工作,发现张三这小子居然把上述的五项服务设计成线型的,也就是说上一服务不完成就没办法进行下一服务,只要中间有一项服务宕机,后面的所有服务都会中断。
  • 重点来了,李四重构时,引入了消息队列。直接将发起订单且付钱成功之后所将要处理的服务,通过消息的形式告诉消息队列,只要告诉完了,就可以直接执行"消息反馈服务"这项服务了。对于"库存-1"、"订单+1"和"通知商家"三项服务,后面只需慢慢消费消息队列中的消息就行了(当然怎么可能慢慢消费呢?库存没了要及时通知用户的,不然付了钱再退款可太麻烦了,因此要么把"库存-1"这项服务设计成不用消息队列的形式;要么保证其在消费消息队列信息的过程中能够保持高性能)
  • 简单来说就是降低用户下单后看到反馈信息的时间,也是为了防止出事故而导致用户迟迟看不到反馈信息。用户至上,用户的问题先解决,APP后面才能开始宰人(bushi)。如果中间的"通知商家"服务宕机了,由于存在消息队列,数据被保存在消息队列中,后面只要"通知商家"服务重启,就可以直接消费消息队列中的数据信息,整个服务流程照样完整。额...无非是商家发货慢了点。

\

1.2 消息队列的优点

  1. 提高系统响应速度(这个很明显,1.1提到的例子就是)

——小明下单付钱成功后,把数据消息直接扔进消息队列里,无需等待后续对数据的其他处理,直接把响应结果返回给小明。

  1. 提高系统稳定性(这个也很明显,同样也是1.1的例子)

——小明下单后响应直接返回,不用怕后面的服务宕机,使用消息队列等服务重启后从消息队列中拿出数据消费就行了

  1. 异步化、解耦、消除峰值(还是用1.1的例子emmm)

——异步化、解耦、消除峰值可以一起讲,就是把总的业务逻辑拆分。像张三设计的架构是总线型,也就是所有逻辑全是主逻辑,一步出问题就全部出问题。使用消息队列对整个服务进行解耦,拆分为多个副逻辑,实现服务的异步化。由于有多个副逻辑对主逻辑进行分担,于是就可以降低主逻辑的流量峰值,保证主逻辑的运行速度,降低主逻辑崩溃概率(因为很多跟C端用户相关的业务逻辑都是在主逻辑层实现的)

\

二:案例——Kafka实践

没错,首先要进入无聊的概念解释

2.1 kafka架构

  • Topic(逻辑队列):

每一个不同的业务场景就对应一个Topic,跟这项业务有关的所有数据都存储在这个Topic中

  • Cluster(物理集群):

每个集群中包含多个Topic,即多个业务逻辑的组合

  • Producer(生产者):

消息的生产端,负责将业务消息发送到对应的Topic

  • Consumer(消费者):

消息的消费端,负责将对应Topic中的消息取出消费

  • ConsumerGroup(消费者组):

多个消费者组成的集合,不同的消费者组要保证互不干涉

  • Partition(消息分片):

在Topic中存在多个Partition,其实就是将消息拆分成多组进行存储,提高消息写入到Topic中的速度,以及加快被消费者读取的速度

上图中:存在两个生产者,将消息写入到Topic中,由于Topic中存在两个Partition(分片),写入效率提升了一倍。存在两个消费者组,且都可以读取Topic1中的消息,而消费者组1存在两个消费者,所以读取速率是消费者组2的两倍。两个消费者组设计时实现了互不干涉,即不可能存在一条消息同时被两个消费者组读取到的情况

——接下来深入拆解partition分片

  • Offset:

每一个分片中储存着多条消息,每条消息都有着一个唯一的Offset,可以理解为消息的ID,且这串ID在分片内部是递增的

  • Replica:

可以理解为分片的副本,一般来说,数据要有备份,否则如果出现某台服务器的消息队列崩溃了,数据就不存在了。因此要有很好的容灾性,就必须将消息队列里的消息做多个副本。Replica分为Leader和Follower,Follower负责同步Leader队列的数据。当Leader出故障之后,某个Follower就晋升为Leader

  • ISR:

一个分片中存在一个ISR,与Leader信息大致同步的Follower就会存放在ISR中,如果Follower与Leader差距较大,就会被踢出ISR。上述提到的Leader故障了,选择Follower晋升为新Leader也不是乱选的,它一定是ISR中最优的那一个。ISR中的Replica排名,以前是根据与Leader的Offset差距来排的,现在变成了与Leader的时间差距

——谁来负责分配Leader和Follower?(Broker)

上面这幅图代表着Kafka中副本的分布图。途中的Broker代表每一个Kafka节点,所有的Broker节点最终组成了一个集群。整个图表示:整个集群(Cluster)包含了四个Broker机器节点,集群中有两个Topic,分别是Topic1和Topic2,Topic1中存在两个分片,Topic2中只有一个分片。每个分片都是三副本状态。中间有一个Broker同时充当Controller的角色,是整个集群的大脑,负责对副本和其他Broker进行分配

——元数据存储(Zookeeper)

在集群的基础上,还有一个模块是Zookeeper,负责储存集群的元数据信息,比如副本的分配信息和Controller计算好的方案

——最后上一张整体架构图

\

2.2 kafka对业务如何做优化处理?

——Producer方面的优化

  1. 批量发送

上图就理解了,批量发送可以减少IO次数,加强发送能力

  1. 数据压缩

还可以对数据进行压缩,通过减少消息大小的方式提高消息存入Broker中的速度

——Broker方面的优化

  1. 如何存储?——顺序写入

消息的存储是要存进磁盘里的,消息写入磁盘的过程中,需要通过移动磁头来找到对应磁道,磁盘转动找到对应扇区,最后写入。寻找磁道的成本是比较高的,因此采用顺序写入(新消息直接在Partition末尾追加),以减少时间成本

  1. 如何迅速找消息返回给Consumer?

Consumer通过发送FetchRequest请求消息数据,Broker会将指定的Offset处的消息,按照时间窗口和消息大小窗口发送给Consumer,具体寻找消息的细节有以下三种方式

比如现在要寻找Offset = 28的消息

  • 偏移量索引文件:(二分找到小于目标Offset的最大索引值)

  • 时间戳索引文件:(二分找到小于目标时间戳最大的索引位置)

  • 零拷贝:

传统拷贝要经历五步才能将数据从硬盘读到消费者进程,从硬盘空间到内核空间,再到应用空间,最后又经过内核空间到消费者进程。而零拷贝则直接略去了应用空间,直接从硬盘到内核空间到消费者进程。

——Consumer方面的优化

其实就是如何解决Partition在Consumer Group中的分配方式?

  • 手动分配(Low Level)

手动分配的一个好处就是启动比较快,因为对于每一个消费者来说,启动的时候就已经知道了自己应该去消费哪个分片了。就好比对下图中的Consumer Group 1 来说,Consumer 1 去消费分片123,Consumer 2 去消费分片456,Consumer 3 去消费78。这些Consumer再启动时就已经知道分配方案了。但这种分配方式却是危险的,假如Consumer 3 挂掉了,78分片就停止消费了。又假如现在新增了一个Consumer 4 ,就要去停掉整个集群,重新修改配置再上线,以保证Consumer 4也可以消费数据。这样特别麻烦。

  • 自动分配(High Level)

所以Kafka也提供了自动分配的方式,简单来说就是在Broker集群中,对于不同的Consumer Group来讲,都会选择一台Broker当作Coordinator,而Coordinator作用就是帮助Consumer Group进行分片的分配,也叫做分片的Rebalance。使用这种方式,如果Consumer Group中有发生宕机,或者有新的Consumer加入,整个分片和Consumer都会重新分配来达到一个稳定的消费状态