消息队列(Message Queue)是一种常见的异步通信模式,用于在分布式系统中传递消息和解耦应用组件。它通过将消息发送到队列中,并由接收方从队列中获取和处理消息,实现了消息的异步传递和解耦。
消息队列的基本原理是:发送方(生产者)将消息发送到队列,而接收方(消费者)从队列中获取消息进行处理。消息队列可以保证消息的可靠性、顺序传递、尽力交付和解耦等特性。
面对:
- 系统奔溃
- 服务处理能力有限
- 链路耗时长尾
- 日志如何处理
解决方案:
- 解耦
- 削峰
- 异步
- 日志处理
消息队列(MQ),指保存消息的一个容器,本质是某个队列。但这个队列需要支持高吞吐,高并发,并且高可用。
消息队列-Kafka
创建集群 => 新增Topic => 编写生产者逻辑 => 编写消费者逻辑。
Topic:逻辑队列,不同Topic可以建立不同的Topic。
Cluster:物理集群,每个集群中可以建立多个不同的Topic。
Consumer:消费者,负责消费Topic中的消息。
ConsumerGroup:消费者组,不同组Consumer消费进度互不干涉。
Offset:消息在Partition内的相对位置信息,可以理解为唯一ID,在Partition内部严格递增。
每个分片有多个Replica,Leader Replica将会从ISR中选出。
Kafka架构
ZooKeeper
ZooKeeper是Kafka集群中的关键组件之一,负责存储和管理Kafka集群的元数据信息,包括Broker、Topic和分区等。ZooKeeper还用于领导者选举、故障检测和协调等重要任务。
Producer
Producer将消息发布到Kafka集群,可以批量发送消息以提高性能。Producer将消息发送到指定的Topic,并根据分区策略选择合适的分区进行消息分发。此外,Producer还支持对消息进行压缩,以减小消息的大小,目前支持多种压缩算法。
Producer-批量发送
生产者可以通过批量发送消息来提高系统的性能和效率。批量发送是指将多个消息打包成一个批次,一次性发送到消息队列,而不是逐条发送。
批量发送消息的优势:
- 减少网络开销:批量发送可以减少网络通信的次数,降低了网络的开销。相比于逐条发送,批量发送可以显著提高网络利用率,特别适用于高延迟或低带宽的环境。
- 提高吞吐量:批量发送可以将多条消息合并为一个请求进行发送,减少了发送请求的开销和消耗。这样可以大幅度提高生产者的吞吐量,降低系统的延迟。
- 降低系统开销:批量发送可以减少系统调用、锁竞争等开销,提高系统的效率和性能。对于大规模数据处理和高并发场景,批量发送尤为重要,可以有效减少资源的占用。
实现批量发送的方法:
- 批量构建:生产者在发送消息之前,可先收集多个消息并将其批量构建为一个批次。这样可以减少发送请求的次数,提高网络传输效率。
- 调整批次大小:根据系统的实际情况和性能需求,可以调整批次中消息的数量。过小的批次会增加网络开销,而过大的批次可能会增加延迟。需要根据系统的性能测试和实际场景进行权衡和调优。
- 异步发送:生产者可以将批量发送操作放入异步线程或任务中进行处理,避免阻塞主线程。这样可以提高生产者的响应速度,同时充分利用资源进行批量发送。
- 批量确认:消息队列系统通常提供了批量确认机制,即一次性确认多个消息的到达和处理情况。生产者可以利用批量确认机制来提高确认的效率,减少确认操作的开销。
需要注意的是,在使用批量发送时,要确保消息的顺序和一致性。对于某些业务场景,消息的顺序可能是非常重要的,因此在批量发送时需要保证消息的顺序不被打乱。
通过合理使用批量发送,可以在保证消息可靠性的前提下,提高系统的性能、吞吐量和效率。
Producer-数据压缩
通过压缩,减少消息大小,目前支持Snappy,Gzip,LZ4,ZSTD压缩算法。
生产者可以通过对消息进行压缩来减少消息的大小。压缩可以有效地减少网络传输的数据量,提高系统的传输效率和性能。目前许多消息队列系统都支持多种压缩算法,如Snappy、Gzip、LZ4和ZSTD等。
特点:
- Snappy: Snappy是一种快速、无损的压缩算法,具有较低的压缩和解压缩延迟。它适用于需要较高的压缩速度和较低的延迟要求的场景。
- Gzip: Gzip是一种广泛使用的压缩算法,具有较高的压缩比和可移植性。它适用于对数据压缩比要求较高的场景,但相对于其他算法,压缩和解压缩的速度较慢。
- LZ4: LZ4是一种极快的压缩算法,具有非常低的压缩和解压缩延迟。它适用于对压缩速度和实时性要求较高的场景,但相对于其他算法,压缩比较低。
- ZSTD: ZSTD是一种高性能的压缩算法,具有很好的压缩比和较低的压缩和解压缩延迟。它适用于对压缩比和压缩速度都有要求的场景。
需要注意:
- 压缩算法选择: 根据实际需求和场景,选择合适的压缩算法。不同的算法在压缩比、压缩速度和解压缩速度等方面有所差异。
- 压缩级别: 某些压缩算法(如Gzip)支持不同的压缩级别,可以根据实际情况选择适当的级别。较高的压缩级别通常会带来更高的压缩比,但也会增加压缩和解压缩的时间。
- CPU和内存开销: 压缩和解压缩过程需要消耗一定的CPU资源和内存空间。在选择压缩算法和压缩级别时,要考虑到系统的CPU和内存限制,避免过大的开销影响系统性能。
- 兼容性:在使用压缩功能时,要确保消费者能够正确解压缩消息。消费者必须支持相应的压缩算法,并能够正确处理压缩的消息。
通过合理选择和使用压缩算法,可以在减少网络传输数据量的同时,提高系统的传输效率和性能,特别是在带宽有限或数据量较大的场景下。
Broker
Broker是Kafka集群的核心组件,负责存储和处理消息。每个Broker都是一个独立的服务器实例,存储了一个或多个Topic的消息副本。Broker维护着消息文件的结构,包括日志文件(log)、索引文件(index)、时间戳索引文件(timeindex)等。它还支持顺序写入方式,以提高写入效率。
Broker-数据的存储
消息队列中的消息存储通常由中间件或消息队列代理(Broker)负责。Broker在接收到生产者发送的消息后,将其持久化并存储在磁盘上,以确保消息的可靠性和持久化。
以下是消息存储的一些常见方案:
- 日志文件存储:一种常见的消息存储方式是使用日志文件进行存储。Broker将接收到的消息追加写入日志文件中,并使用索引来记录消息的偏移量。这种方式可以提供较高的写入性能和顺序读取性能。
- 内存映射文件存储:为了提高读取性能,一些消息队列系统采用内存映射文件存储方式。将存储在磁盘上的消息文件映射到进程的虚拟内存空间中,通过内存直接访问数据,避免了频繁的磁盘IO操作。
- 数据库存储:一些消息队列系统使用数据库作为消息的存储介质。消息被转化为数据库中的记录,并使用适当的索引进行管理和检索。数据库存储提供了灵活的查询和管理功能,但相对于日志文件存储,可能会有更高的写入和读取延迟。
- 消息索引:为了支持快速的消息查找和检索,消息队列系统通常使用索引结构来管理消息的偏移量和位置。常见的索引结构包括B树、跳表等。这些索引可以帮助Broker快速定位消息,并提供高效的读取和查询能力。
消息存储对于消息队列来说至关重要。一个可靠的消息存储机制可以确保消息的持久化和可靠性,防止消息丢失或重复传递。同时,存储性能、可伸缩性和容错性也是设计和选择消息存储方案时需要考虑的因素。
此外,消息队列系统通常还提供了数据备份、复制和冗余机制,以增加数据的可靠性和可用性。例如,通过主备复制、分布式存储等方式,确保即使在部分节点故障的情况下,消息仍然可靠地存储和传递。
最佳存储方案的选择应该基于具体的业务需求、性能要求和可扩展性需求进行权衡。
Broker-消息文件结构
数据路径:/Topic/Partition/Segment/(log|index|timeindex|...)
Broker-磁盘结构
移动磁头找到对应磁道,磁盘转动,找到对应扇区,最后写入。寻到成本较高,因此顺序写可以减少寻道所带来的时间成本。
Broker-顺序写
采用顺序写的方式进行写入,以提高写入效率。
Broker-如何找到消息
Consumer通过发送FetchRequest请求消息数据,Broker会将指定Offset处的消息,按照时间窗口和消息大小窗口发送给Consumer。
Broker偏移量索引文件
二分找到目标文件。
Broker时间戳索引文件
二分找到小于目标时间戳最大的索引位置,再通过寻找offset的方式找到最终数据。
Broker-传统数据拷贝
传统的数据拷贝是一种将数据从一个地方复制到另一个地方的方法,常用于消息队列系统中的数据传输。
在消息队列中,数据拷贝一般涉及到两个主要角色:生产者和消费者。生产者负责生成消息并发送到消息队列中,消费者则从消息队列中读取消息并进行处理。
传统的数据拷贝方法相对简单直接,但也存在一些限制和挑战。例如,数据拷贝过程通常需要依赖网络传输,可能会受到网络延迟、带宽限制等因素的影响。此外,传统的数据拷贝方式可能无法满足高并发、大规模消息传输等特殊需求。
随着技术的发展,一些消息队列系统提供了更高级的数据传输方式,如零拷贝、数据复制和复制快照等。这些方法可以提高数据传输的效率和性能,并满足更复杂的应用场景需求。
Broker-零拷贝
Kafka采用零拷贝机制来提高IO效率。通过操作系统级别的特性,Kafka可以直接从磁盘读取数据并发送给客户端,而无需将数据拷贝到用户空间。
Consumer
Consumer是消息的接收端,在Kafka中有两种消费模式:Low Level和High Level。Low Level模式允许开发者手动指定每个Consumer消费哪个分区的消息。而High Level模式则由Kafka自动管理Consumer与分区之间的关系,并进行自动的负载均衡。
Consumer-Low Level
通过动手进行分配,哪一个Consumer消费哪一个Partition完全由业务来决定。
Consumer-Hign Level
消费者(Consumer)的高级概念在消息队列系统中主要指的是高级消费者API或消费者组(Consumer Group)。这些概念提供了更复杂和灵活的方式来处理消息。
- 高级消费者API:传统的消息队列系统提供了基本的消费者API,允许消费者单独从队列中读取消息。而高级消费者API则提供了更多功能,如消息过滤、消息顺序保证、事务支持等。这使得消费者能够更好地控制消息的处理方式,并满足更复杂的业务需求。
- 消费者组(Consumer Group):消费者组是一组具有相同目标的消费者集合。消息队列系统将消息分发给消费者组中的一个消费者,以确保每条消息只被一个消费者处理。消费者组可以提供负载均衡和冗余处理的能力。当一个消费者出现故障或离线时,消息队列系统会自动将未处理的消息重新分配给其他消费者。
使用高级消费者API和消费者组,可以实现以下功能:
- 消息过滤:消费者可以根据特定的条件或标签过滤消息,仅处理符合条件的消息。
- 消息顺序保证:消费者可以按照消息的顺序进行处理,确保消息按照产生的顺序被消费。
- 消息事务:消费者可以在处理消息时使用事务来确保消息的可靠处理和错误回滚。
- 水平扩展:通过使用消费者组,可以将消费者分布到多个实例或节点上,以实现水平扩展和提高处理能力。
通过使用高级消费者API和消费者组,可以更好地控制消息的处理方式,并且提供了更高级的功能来应对不同的业务需求。这些功能使得消息队列系统在大规模、分布式的应用场景中更加强大和灵活。
Consumer Rebalance
在Kafka集群中,当Consumer加入或退出时,会触发Consumer Rebalance机制。Consumer Rebalance会重新分配分区给Consumer,保证消费者消费负载的均衡性。这样可以实现高可靠性和水平扩展性。
Kafka问题总结
- 运维成本高
- 对于负载不均衡的场景,解决方案复杂
- 没有自己的缓存,完全依赖Page Cache
- Controller和Coordinator和Broker在同一进程中,大量IO会造成其性能下降。
消息队列-BMG
兼容Kafka协议,存算分离,云原生消息队列。
架构图
运维操作对比
HDFS写文件流程
随机选择一定数量的DataNode进行写入。
Broker-Partition 状态机
保证对于任意分片在同一时刻只能在一个Broker上存活。
Broker-写文件流程
Broker-写文件 Failover
如果DataNode节点挂了或者其他原因导致我们写文件失败。
Proxy
Proxy(代理)是一种充当客户端和服务器之间的中间层的服务器。它接收来自客户端的请求,并将这些请求转发给目标服务器,同时将响应返回给客户端。Proxy 的存在可以提供一些额外的功能和优势。
有几种常见的 Proxy 类型:
- 反向代理:反向代理位于服务器端,客户端通过发送请求到反向代理,再由代理服务器将请求转发给目标服务器。反向代理隐藏了真实的服务器信息,提供负载均衡、缓存、SSL 加密等功能,并增加了系统的安全性和可伸缩性。
- 正向代理:正向代理则位于客户端端,客户端通过发送请求到代理服务器,再由代理服务器将请求转发给真实的服务器。正向代理常用于绕过访问限制、保护客户端隐私、加速访问等场景。
多机房部署
多机房部署是指将系统资源分布在多个地理位置不同的机房中,以提高系统的可用性、容错性和用户体验。多机房部署可以应对单个机房故障、网络中断或地理位置限制等情况。
在多机房部署中,通常会使用负载均衡器(Load Balancer)来将流量分配到不同的机房。负载均衡器可以根据各个机房的负载情况、地理位置等因素,智能地选择最优的机房进行请求转发。
多机房部署还需要解决数据一致性、跨机房通信、故障恢复等问题。常见的做法包括数据复制与同步、容灾备份、跨机房通信技术(如广域网连接、虚拟专用网络等)以及监控和自动化运维工具的使用。
通过使用代理和多机房部署,可以提高系统的可扩展性、可靠性和用户体验,满足不同的业务需求和应对各种故障情况。
泳道消息
BOE:Bytedance Offline Environment, 是一套完全独立的线下机房环境。
PPE:Product Preview Environment, 产品预览环境。
BOE:多人同时测试,需要等待上一个人测试完成。每多一个测试人员,都需要重新搭建一个相同配置的Topic,造成人力和资源的浪费。
PPE:对于PPE的消费者来说,资源没有生产环境多,所以无法承受生产环境的流量。
解决主干泳道隔离问题以及用到资源重复创建问题。
Databus
- 简化消息队列客户端复杂度
- 解耦业务与Topic
- 缓解集群压力,提高吞吐。
Mirror
使用Mirror通过最终一致的方式,解决跨Region读写问题。
Index
直接在BMQ中将数据结构化,配置索引DDL,异步构建索引后,通过Index Query服务读出数据。
Parquet
Apache Parquet是Hadoop生态圈中一种新型列式存储格式,它可以兼容Hadoop生态圈中大多数计算框架(Hadoop,Spark等),被多种查询引擎支持(Hive,Impala,Drill等)。
直接在BMQ中将数据结构化,通过Parquet Engine,可以使用不同的方式构建Parquet格式文件。
消息队列-RocketMQ
RocketMQ高级特性
事务场景
在某些业务场景下,需要保证消息的可靠性传输,并且要支持事务的 ACID 特性。RocketMQ提供了事务消息的机制来满足这种需求。事务消息允许应用程序将消息发送到Broker,并在本地执行预处理操作。如果预处理操作成功,则提交事务,消息变为可消费状态;如果预处理操作失败,则回滚事务,消息被删除。
事务消息
事务消息是一种具有原子性、一致性、隔离性和持久性特性的消息。RocketMQ通过TransactionProducer接口提供了事务消息的发送方式,开发者可以在该接口实现类中编写消息的本地事务逻辑,在执行本地事务后,根据事务的状态和结果来提交或回滚消息。
延迟发送
在某些场景下,需要延迟发送消息,比如订单超时未支付的提醒通知。RocketMQ提供了延迟发送消息的功能,可以指定消息的延迟级别,从而控制消息在指定时间后才能被消费者消费。
延迟消息
延迟消息是指在消息发送后,需要经过一定时间间隔后才变为可消费状态的消息。RocketMQ允许在发送消息时设置延迟级别,消息将在指定的延迟时间后才可以被消费。
处理失败
在消息队列中,当消息的消费发生异常或失败时,需要一种机制来处理这些失败的消息。RocketMQ提供了处理失败消息的机制,将失败的消息发送到特定的处理队列中进行处理,以便后续的重试或人工干预。
消费重试和死信队列
当消息消费失败时,RocketMQ会根据配置进行自动重试,将消息重新发送给消费者进行消费。如果重试多次后仍然失败,RocketMQ会将这些消息转移到死信队列(DLQ),供开发人员进行人工干预和处理。
今日总结
今天学习了消息队列的相关知识,包括消息队列的定义、应用场景、解决的问题,以及Kafka、RocketMQ、BMQ等常见消息队列中间件的基本架构、实现机制和特性。掌握了消息队列的作用是通过异步传递实现系统解耦,可以用来应对系统故障、流量削峰、数据分发等场景。了解了不同消息队列在架构设计、性能表现、功能特性等方面的区别。这为我们合理选择和应用消息队列中间件提供了基础。后续还需要继续深入学习每个消息队列的实现细节,并结合实际业务场景进行选择和应用。