背景介绍
存在问题
- 系统奔溃——无法加载页面数据
- 服务处理能力有限——系统无法短时间处理几百张订单
- 链路耗时长尾——提交订单后时延太长
- 日志如何处理——处理bug后如何修复原来的数据
解决方案
- 解耦:将用户所有的行为记录发送到消息队列中,消息队列传递到存储服务里,再由存储服务拉取所有请求进行消费(生产——消费模型)。此时如果存储服务宕机了,行为记录仍会存储到消息队列里,不会影响用户购买的流程。
- 削峰:将所有的发起订单请求发送到消息队列中,系统从消息队列里提取数据处理订单,但是每次只获取10个请求进行处理,由此可规避多个线程同时挤占网络资源而导致的拥塞。
- 异步:将所有的发起订单请求发送到消息队列中,从消息队列里提取数据,采用异步的方式完成订单记录+1、库存记录-1和通知商家三个操作,当用户提交订单后会很快进入订单处理页面,然后花一小段时间等待库存处理,用界面的更换说明进度,缓解用户的焦急情绪。
- 日志处理:可以先将日志放到消息队列里。即Log->消息队列->LogStash->ES->Kibana。
MQ——含义
消息队列(MQ)是保存消息的容器,本质上是一个高吞吐、高并发、高可用的队列。常用于生产——消费模型中信息的传递。
MQ——前世今生
- Kafka:分布式的、分区的、多副本的日志提交服务,在高吞吐场景下发挥较为出色
- RocketMQ:低延迟、强一致、高性能、高可靠、万亿级容量和灵活的可扩展性,在一些实时场景中运用较广
- Pulsar:下一代云原生分布式消息流平台,集消息、存储、轻量化函数式计算为一体、采用存算分离的架构设计
- BMQ:和Pulsar架构类似,存算分离,初期定位是承接高吞吐的离线业务场景,逐步替换掉对应的Kafka集群
MQ——Kafka
使用场景
常用于离线消息处理:日志信息、Metrics数据(Metrics:程序状态的采集,评判程序状态是否健康)、用户行为(搜索、点赞、评论、收藏)等。
使用方法
创建集群->新增Topic->编写生产者逻辑->编写消费者逻辑
基本概念
Cluster视角
- Topic:逻辑队列,每一个业务场景就是一个Topic
- Cluster:物理集群,每个集群中可以建立多个不同的Topic
- Producer:生产者,负责将业务消息发送到Topic中
- Consumer:消费者,负责消费Topic中的消息
- ConsumerGroup:消费者组,不同组Customer消费进度互不干涉
- Broker:一个独立的kafka服务器被称为broker。broker接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker为消费者提供服务,对读取分区的请求作出响应,返回已经提交到磁盘上的消息。
Topic内部视角(高可用)
- Partition:Topic的分区,不同分区的消息可以并发处理,以此来提高单个Topic的处理能力
- Offset:消息在partition内的相对位置信息,可以理解为唯一ID,在partition内部严格递增(保证消息有序)。
- Replica:每个partition分片有多个Replica,Leader Replica(对外写入/读取)将从ISR中选出,Follower Replica会总是缩小和Leader Replica之间的差距,如果Leader Replica发生了宕机,就可以从下面的副本中找到可以替换它的,继续为生产者消费者服务——高可用功能的实现。
数据复制
Controller:负责对集群中的所有副本和Broker进行分配。
Kafka架构
ZooKeeper:和Controller配合,负责存储集群元信息,包括分区分配信息等
消息流程(高吞吐)
Producer——批量发送
Batch:一次发送多条message。批量发送可以减少IO次数,从而加强发送能力。
Producer——数据压缩
通过压缩,减少消息大小,目前支持Snappy、Gzip、LZ4、ZSTD压缩算法。
Broker——数据的存储
Broker消息文件结构
副本最终都会以日志的形式写到磁盘上。
Broker磁盘结构
移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻道成本比较高,因此顺序写可以减少寻道所带来的时间成本。
Broker——顺序写
对每一个message进行末尾追加,采用顺序写的方式进行写入,以提高写入效率。
Broker——寻找消息
Customer通过发送FetchRequest请求消息数据,Broker会将指定Offset处的消息,按照时间窗口和消息大小窗口发送给Customer。
Broker偏移量索引文件
Kafka的索引采用稀疏索引的方式构建,可以通过二分找到小于目标offset的最大索引位置。
Broker时间戳索引文件
二分找到小于目标时间戳的最大索引位置,再通过寻找offset的方式找到最终数据。
Broker——传统数据拷贝
(内核态IO操作)从磁盘提取数据,通过内核空间拷贝读取,拷贝到应用空间,再通过Socket缓存拷贝读取和NIC网卡内存拷贝读取,网卡拷贝传递到消费者进程。
Broker——零拷贝
从磁盘提取数据,通过内核空间拷贝读取,内核空间把整个数据拷贝发送给NIC网卡内存,网卡读取后直接拷贝传递到消费者进程。(相比内核态减少了3次拷贝)
Consumer——消息的接收端
如何解决Partition在Consumer Group中的分配问题?
- 手动分配
- 自动分配
Consumer——手动分配
生产者启动后,在代码里配置好Consumer要消费的partition。通过手动进行分配,哪一个Consumer消费哪一个Partition完全由业务来决定。
- 优点:比较快
- 缺点:
- 一旦一个Consumer挂掉了,数据流就直接断掉了
- 再添加一个Consumer 4时,需要等待Consumer 1、2、3完成相应Partition的消费才可以轮到4。这当中会有机器的启停,启停是停止消费的状态,会带来数据中断的问题。
Consumer——自动分配
对于不同的Consumer Group,会有一台Coordinator自动分配。 Rebalance:Coordinator会自动感知Consumer的宕机,从而处理数据流。如果有新Consumer的加入,也会重新计算分片。
总结——Kafka提高吞吐和稳定性的功能
- Producer:批量发送,数据压缩
- Broker:顺序写,消息索引,零拷贝
- Consumer:Rebalance
回顾——Kafka的缺点
- 数据复制,重启操作:如果集群需要升级,就会需要重新启动。重启Broker上的Leader节点时,会从副本中选择一个Follower作为新的Leader。如果关闭Broker1,会选择Broker2作为新的Leader。但是在重启过程中,数据写入仍在进行,在Broker1的重启过程中,Broker2还在接收新的数据,这就会导致Broker1和Broker2执行复制后的数据存在差异。而且回切也会带来时间成本。
- 替换、扩容、缩容:只要有节点变动,就会有数据复制所带来的时间成本问题。
- 负载不均衡:可能某一时刻Partition1的数据较多,Partition3的数据较少,需要迁移数据使得负载均衡。为了解决负载所带来的IO信道问题选择迁移数据,而迁移会带来数据复制问题,数据复制又引出新的IO问题。所以这时需要权衡两台机器的IO,设计出极为复杂的负载均衡策略(成本高、策略复杂)。
总结——Kafka的问题
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Controller和Coordinator和Broker在同一进程中,大量IO会造成其性能下降
MQ——BMQ
BMQ——含义
兼容Kafka协议,存算分离,云原生消息队列
与Kafka对比
- 架构:与Kafka的不同点在于加入了Proxy Cluster,Controller和Coordinator独立出来部署,底层新增了分布式存储系统解决IO问题(Distributed Storage System、MetaStorage System)
- 运维操作:重启、替换、扩容、缩容后可直接对外服务,秒级完成
HDFS写文件流程
随机选择一定数量的DataNode进行写入
BMQ文件结构
每一个segment随机选择3个节点,同一个partition的所有segment是分散的,解决负载不均衡问题。
Broker-partition状态机
保证对于任意分片在同一时刻只能在一个Broker上存活
Broker——写文件流程
Messages->数据校验(检查参数是否合法)->Buffer(实现高吞吐,但是会降低可靠性)->Write Thread(异步的,写入数据)->Storage
Broker——写文件Failover
如果DataNode节点挂了或者其他原因导致写文件失败,更换可用节点重新写入。
Proxy
Fetch Request->Wait(没有数据会返回0)->Cache(如果Hit就return data)->Storage(如果miss就穿透内存直接打到磁盘上,磁盘会执行Open File、Seek、Read)
多机房部署
不管哪个机房断电,其他机房都会承载它的流量,防止故障问题。
BMQ——高级特性
Databus、Mirror
泳道消息
- 开发流程:开发->BOE(一套完全独立的线下机房环境)->PPE(产品预览环境)->Prod
- BOE:多个人同时测试,需要等待上一个人测试完成
- BOE 并发测试:每多一个测试人员,都需要重新搭建一个相同配置的Topic
- PPE 验证:对于PPE的消费者来说,资源没有生产环境多,所以无法承受生产环境的流量。
- 泳道解决问题:解决主干泳道流量隔离以及泳道资源重复创建问题。(例:主干、泳道1、泳道2)
Databus
- 简化消息队列客户端复杂度
- 解耦业务与Topic
- 缓解集群压力,提高吞吐
Mirror
- 解决跨Region读写问题?通过多机房部署方式可以吗?(不可以,Proxy会去访问每一个Broker,写入消息时延很大)
- 使用Mirror通过最终一致的方式,解决跨Region读写问题。
index
如果希望通过写入的LogId、UserId或者其他的业务字段进行消息的查询,可以采用:直接在BMQ中将数据结构化,配置索引DDL,异步构建索引后,通过Index Query读出数据。
Parquet
- Apache Parquet是Hadoop生态圈中一种新型列式存储格式,它可以兼容Hadoop生态圈中大多数计算框架(Hadoop、Spark等),被多种查询引擎支持(Hive、Impala、Drill等)。
- 列式存储:按列遍历并存储数据
- 直接在BMQ中将数据结构化,通过Parquet Engine,可以使用不同的方式构建Parquet格式文件。
MQ——RocketMQ
使用场景:低延时场景(如针对电商业务线的注册、订单、库存、物流等,也包括许多业务峰值时刻,如秒杀活动、周年庆、定期特惠等)
和Kafka的对比
RocketMQ多了一个Tag标签字段,将Kafka的Partition称为ConsumerQueue,多了一个Producer Group生产者集群。
基本概念
架构:引入一个NameServer的概念,向生产者和消费者提供路由信息。副本扩大为机器的角度。(例:本机器是master,后面的机器是master的副本)
存储模型
CommitLog存储所有生产者数据,Dispatch发送到ConsumerQueue。其他和Kafka一致。
高级特性
事务场景
- 最终一致性:发起订单-> 库存记录-1 -> 消息队列 -> 订单记录+1和通知商家
- 没有确认会回查
延迟发送
- 提前编辑菜单->消息队列->定时发送->接收菜单
- CommitLog会将延迟消息传递到ScheduleMessage处理,实现延迟发送。
消费重试和死信队列
- 延期投递实现消费重试。
- 超过重试次数会发送到死信队列ScheduleTopic,不再重发。