这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
简介
消息队列本质上就是一个能够存放数据(消息)的队列。
并通过额外实现保证了
- 高吞吐
- 高并发
- 高可用
三大特性。
消息队列适用的场景
系统崩溃
场景
如果记录存储的数据库崩溃,会导致整个链路被阻塞,无法继续进行。
解决方案
解耦
理想的消息队列具有高可用的特性,崩溃概率极小。
所以将记录存储的任务交给消息队列。
主要的链路流程只负责发送消息给消息队列,这样即使存储服务崩溃,只要消息队列不崩溃,就影响不到主链路流程。
服务处理能力有限
场景
秒杀场景下,成百上千万个请求可能同时访问服务器某一接口,但是服务器同一时间只能承载10个订单。
解决方案
削峰
理想的消息队列具有高并发特性,可以同一时间处理超大量请求。
所以可以将大量的订单请求发送到消息队列,然后服务器根据自己处理能力,从消息队列中取出消息进行处理。
也就将极大的、难以处理的流量削弱为服务器可承载的、小的流量。
链路耗时长尾
场景
链路逻辑的后段(通知商家)耗时过长,拖慢了整个链路耗时,影响用户体验。
解决方案
异步
将链路逻辑划分为两段,a段和b段,a段将b段所用数据发送给消息队列后,直接返回,因为不用等待长尾逻辑处理完成,所以极大的缩短了链路耗时。
b段从消息队列中取出消息,慢慢处理,因为不在a段链路中。即使耗时长,也不会影响用户体验。
日志如何处理
场景
存储的日志丢失。
解决方案
日志处理
理想的消息队列具有高可用的特性。
所以直接将日志交给消息队列进行处理,不用担心丢失,且消息队列日志处理相关生态完善,可以对日志进行定制化分析处理
Kafka
概念
Topic
简介
逻辑队列,一个业务场景就是一个Topic。比如可以将评论业务作为一个Topic,有人评论,就将评论信息放入这个Topic(逻辑队列),等待被消费。
Cluster
简介
所有物理主机组成的物理集群。
Producer
简介
生产者,负责将消息放到指定的Topic中。
Consumer
简介
消费者,负责消费生产者放到Topic中的消息。
ConsumerGroup
简介
消费者组,一个消费者组包含多个消费者,并且与一个Topic绑定。
Partition
简介
一个topic 有多个分区,可以交给多个不同的 Consumer (一个消费者组)处理,提高并发能力。
Broker
简介
物理主机
Offset
简介
消息在Partition中的相对位置信息,可以理解为唯一标识,并且严格递增。
Replica
简介
一个分区有多个副本,并且分布在不同broker上。
其中,Leader 副本负责处理所有请求,其余副本只需要定时同步 Leader 副本的数据给自己。
当 Leader 副本因为Broker宕机等缘故挂掉,会在符合 ISR 要求的、同步原有数据最及时的 Follower 副本中选举出新的 Leader 副本
ISR (In-Sync Replicas) 老版本是根据Offset来判断更新是否及时,新版本是根据时间戳进行判断。
Controller
简介
从 Broker 中选举出来的,用于帮助记录节点元数据的服务器。
架构
Zookeeper 与 Controller 负责存储集群元信息,包括分区分配信息。
消息从生产到消费的流程
整体流程
Producer 发送消息到 Broker
Producer 将消息压缩后,以批处理的方式一次性发送一组消息发送到 Broker
Broker 存储消息到磁盘
Broker将副本的日志文件以顺序写的方式写入磁盘来提高性能。
消息的在磁盘上的存储格式是.log、.index、.timeindex文件,一个LogSegment 包含 多个消息,且LogSegment以第一个消息的 .log 文件名来命名。
Consumer 消费消息
整体流程
Consumer 发送给 Broker 一个 FetchRequest 请求,Broker收到请求后,返回 Consumer 要消费的信息给 Consumer
Broker 如何查找消息?
偏移量索引文件 .index
通过偏移量索引文件查找消息,首先查找LogSegment,而LogSegment以第一个消息的 .log 文件名来命名。可以通过二分查找LogSegment,找到消息存放的物理地址,再遍历Batch得到具体的消息。
时间戳索引文件 .timeindex
、
时间戳索引是建立在偏移量索引之上的二级索引,查找消息时,先根据时间戳,查找到对应的 LogSegment,在根据LogSegment 查找到消息。
零拷贝优化
问题
Broker 发送消息到 Consumer,需要从磁盘读取读取,然后通过网络发送到 Consumer。
但普通的数据拷贝方式,需要先将消息拷贝到内核空间,再拷贝到应用空间,最后拷贝到 Socket Buffer 中,发送给NIC Buffer,交由网卡发出,中间需要多次拷贝。
解决方案
零拷贝方案直接省略了将消息拷贝到用户态这一过程,而是在内核态,直接拷贝到 NIC Buffer 中,通过网卡发送到Consumer。
这样做,省下了多次内存拷贝的开销,同时避免了频繁切换应用态、内核态。
如何处理 Partition 在 ConsumerGroup 如何分配
手动配置
简介
生产者往消息队列中放入消息的时候,显式指定哪些 Consumer 处理Partition
缺点*、
- 不能自动容灾
- 当所有指定的 Consumer 挂掉,它处理的 Partition 就没有 Consumer 处理了。
- 不支持动态扩缩容
- 当增加一台机器时,肯定要修改 Consumer 与 Partition 的对应关系,但一旦修改,必须重启修改的机器才能生效。
自动分配
简介
每个 ConsumerGroup 选举一台 Broker 作为 Coordinator (协调者),由 Coordinator 去控制 Consumer 与 Partition 之间的分配方案。
加入/删除一个 Broker 后,Coordinator 重新划分分配方案的过程,叫做 Rebalance。
详细过程
略。
Kafka 性能好的原因
- Producer: 批量发送、数据压缩
- Broker: 顺序写、消息索引、零拷贝
- Consumer: Rebalance
缺点
- 运维成本高。
- 对一个 Leader 节点重启,会选举出一个新的 Leader 节点,旧的 Leader 节点上线后,会尽快与 新的 Leader 节点进行同步。同步到符合 ISR 要求后,会将旧的 Leader 节点改为 Leader 节点。 这样做是避免 100 个节点,99个重启,剩余一个节点在重启时承担99个节点的任务,重启后还要承担的问题。
- 对于替换、扩容、缩容等凡是涉及到节点下线的维护操作,都会有这个过程。
- 整个过程需要几个小时时间,且为了保证业务正常运行,不能并行下线,只能倒班,导致时间成本过高,可能花费数周时间。
- 对于负载不均衡的场景,设计负载均衡算法复杂,成本高。
使用场景
- 离线的消息处理
- 日志信息
- Metrics数据处理 (程序的运行状态信息)
- 用户行为(搜索、点赞、评论、收藏)
BMQ
简介
字节研发的,为了解决 Kafka 种种缺点诞生的消息队列。
它的特性有:
- 兼容 Kafka 协议
- 不改变API,就可以切换到BMQ。
- 存算分离
- Kafka 中消息存放在 kafka 中,BMQ 可以存放在分布式文件系统等存储系统中。
- 云原生消息队列
架构
与 kafka 区别点
- 新增了 Proxy Cluster
- Distributed Storage System 分布式文件系统 DFS
- COntroller、Coordinator 独立部署
- Meta Storage System 类似于 Zookper, 保存 Broker 元数据
写流程
Producer 将消息经过 Proxy Cluster 发送给 Broker Cluster,由 Broker Cluster 进行处理。其中,Proxy Cluster 不处理写请求。
读流程
Producer 将消息经过发送给 Proxy Cluster,Proxy Cluster直接到 Distributed Storage System 中读取数据。
优势(如何解决 Kafka 问题)
- 运维成本极低
- 由于 BMQ 存算分离的特性,Broker 仅仅存放 Distributed Storage System 中位置标识。要进行重启、扩容、缩容操作,直接修改标识就可以,操作速度是秒级的。
RocketMQ
简介
阿里研发的一款低延迟、高可用、高并发、高可靠的分布式消息队列,适用于电商场景。
基本概念
对比 Kafka,RocketMQ 的变化主要有
- 新增了Tag,在Topic下划分了新的层次,适用于更多场景。
- 新增了生产者集群,为了实现事务。
架构模型
差异特性:
- NameServer
- 保存了 ConsumerQueue 在哪个 Broker 中的对应关系,想要找消息在哪个 Broker 时,可以直接通过NameServer进行查找,效率更高。
- Broker 的 副本划分
- Kafka 中一个 Broker 有多个副本,其中既有 Leader 副本,也有 Follow 副本,而 RocketMQ 中,一个Broker 只能有一种副本角色,要不一个 Broker 全是 Leader 副本,要不就全是 Follow 副本。
存储模型
一个 Broker 有唯一一个 CommitLog,生产者提交的消息都会发送到 CommitLog 上,由 Broker 根据消息与分区对应关系 Dispathc (分发) 出去给 ConsumerQueue (分区),并存储。
ConsumerQueue 存储的是索引,而不是具体消息。
事务 (两阶段提交)
事务场景
对于下单业务场景,使用异步将消息发送给消息队列,就算下单成功。
但必须保证 库存扣减 与 发送消息到消息队列 两个逻辑,操作前后,系统具有一致性。
为此,需要开启事务来保证。
事务实现(事务消息)
- Producer 发送给 Server 半事务消息
- Server 返回给 Producer 成功响应
- Producer 执行本地事务
- Producer 根据本地事务执行结果发送提交或回滚消息
- Server 没有收到4消息,会进行 check back (回查) 指令给 Producer
- Producer 检查本地事务执行状态
- 根据检查的结果,Producer 发送 Commit、Roolback 消息给 Server
- 进行判断,如果是 Commit 会将消息存储下来,交由生产者消费,如果是 Rollback,不会发送给消费者。
延迟发送
简介
消息的定时发送(消息发送到 Broker 后,一定时间后,才能被消费。)
实现
- 发送延迟消息时,Producer 会将消息发送到 CommitLog
- CommitLog 识别到延迟消息后,发送消息到特定的分区(ConsumerQueue)ScheduleTopic
- 由 ScheduleMessage (定时线程)在达到指定时间后,从ScheduleTopic取出消息,按照普通发送消息流程发送给CommitLog
- 定时消息已到达指定时间,可以被消费。
消息重试和死信队列
简介
Consumer 消费消息时,消息处理失败的处理流程。
实现流程
- Consumer 尝试消费 Topic 中数据
- Consumer 消费失败,进入重试流程
- 判断重试次数是否超过最大限制
- 是
- 加入死信队列
- 否
- 加入特定的定时分区 SchedduleTopic
- 定时分区会将该消费消息延时发送给 RetryTopic
- Consumer 识别到 RetryTopic 有重试消费消息,会进行重试
应用场景
低延迟场景
- 电商业务
- 注册
- 订单
- 库存
- 物流
- 秒杀