Labs 导读
近年来,互联网技术发展迅猛,各行各业的信息量急剧膨胀。随着云计算和算力网络时代的到来,消息中间件在国内许多行业的关键应用中越来越受到重视。在高并发分布式场景下,合理地利用消息中间件往往能起到突破性能瓶颈与化繁为简的效果。
前期分别从“作用”与“协议”、“传输模式”与“消费模式”对消息中间件技术做了简要的介绍。本期从消息中间件产品角度介绍主流方案的设计与实现。
1、概念介绍
Apache Kafka是一种高吞吐量、分布式、多副本、基于发布/订阅的消息系统,最初由LinkedIn公司开发,使用Scala语言编写,目前是Apache的开源项目。Kafka已成为事件流处理动态数据的事实标准。
1.1 主要特性
- 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒;
- 可扩展性:kafka集群支持热扩展,数据迁移、扩容对用户透明;
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失;
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败);
- 高并发:支持数千个客户端同时读写;
- 分布式架构:Broker、Producer和Consumer都原生自动支持分布式,自动实现负载均衡;
- 支持同步和异步复制两种高可用机制;
- 支持数据批量发送和拉取;
- 零拷贝技术(zero-copy):减少 IO 操作步骤,提高系统吞吐量;
- 其他特性:丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制。
1.2 Kafka优点
- 客户端多语言支持:支持Java、.Net、PHP、Ruby、Python、Go等多种语言;
- 高性能:单机写入TPS约在100万条/秒,消息大小10个字节;
- 分布式架构,并有replica机制,拥有较高的可用性和可靠性,理论上支持消息无限堆积;
- 支持批处理操作;
- 消费者采用Pull方式获取消息。单分区内消息有序,通过控制能够保证所有消息被消费且仅被消费一次;
- 在日志领域比较成熟,被多家公司和多个开源项目使用。
1.3 Kafka缺点
- Kafka单机超过64个分区时,load时会发生明显的飙高现象。队列越多,负载越高,发送消息响应时间变长;
- 使用短轮询方式,实时性取决于轮询间隔时间,对于不能批处理的消息,需考虑消费线程执行效率;
- 需要引入ZooKeeper,部署成本相比其他MQ较高;
- 不能保证消息100%到达,不支持事务消息。
1.4 主要应用场景
- 消息系统:分布式消息系统,解耦生产者和消费者;
- 日志收集:Kafka常与ELK(Logstash、ElasticSearch、Kibana)一起作为业务系统日志收集方案;
- 业务埋点:对于可靠性要求不那么高的埋点数据(如浏览网页、点击、跳转等),可使用Kafka进行传输,消费者端收到消息后可根据需求做实时监控分析或装载到hadoop、数据仓库中做离线分析和挖掘;
- 运营指标:Kafka可用来传输运营监控数据,以便统一归集分析、集中反馈;
- 流式处理:Kafka提供了完整的流式处理类库,可以很方便的被集成至应用程序中,为流式处理框架(Flink、Spark、Storm等)提供可靠的数据来源。
1.5 Kafka为什么这么快?
Kafka可以轻松支持每秒百万级的写入请求,超过了大部分的消息中间件,这种特性使得Kafka在日志处理等海量数据场景得到广泛应用。
- 并行处理:Kafka引入了Partition(分区)的概念,每个Topic 可包含一个或多个Partition,不同Partition可位于不同节点中,从而实现多磁盘并发读写;
- 顺序读写:Kafka中每个Partition是一个有序、不可变的消息序列,新的消息只会被追加到Partition的末尾,而一个Partition又被分为多个Segment,清除旧数据时可直接删除Segment文件,避免随机写;
- 页缓存(Page Cache):Kafka使用页缓存技术减少I/O操作次数,即使Kafka进程重启数据也不会丢失(机器宕机时,页缓存内的数据未及时写入磁盘会导致数据丢失,同步刷盘可以规避该问题,但会影响性能,默认使用异步刷盘机制);
- 零拷贝技术:Kafka使用零拷贝技术,避免数据在内核空间的缓冲区和用户空间的缓冲区之间进行拷贝;
- 批处理:Kafka支持批处理操作,以减少网络I/O操作;
- 数据压缩:Kafka支持Snappy、Gzip、LZ4等算法对数据进行压缩传输。
2、架构设计
图1 架构设计
3、核心概念
- Producer:生产者,用来向Kafka Broker中发送数据(Record);
- Kafka Cluster:Kafka集群,由一台或多台服务器组成;
- Broker:Broker是指部署了Kafka实例的服务器节点,每个服务器上可安装一个或多个Kafka实例。每个Kafka集群内的Broker都有一个不重复的编号(如broker-0、broker-1等);
- Topic:消息主题,用来区分不同类型信息。在每个Broker上可以创建多个Topic;
- Partition:Topic的分区,每个Topic可以有一个或多个 Partition(分区),分区可实现负载均衡,支持并发写入读取,提高Kafka的吞吐量。一个分区内的数据只能被一个线程消费;
- Replication:每一个分区可以有多个副本。当主分区(Leader)故障的时候会选择一个副本(Follower)成为新的Leader。在Kafka中副本的默认最大数量是10个,且副本的数量不能大于Broker的数量,Follower和Leader必须分布在不同的机器上,同一机器上同一分区只能存放一个副本(包括自己)。
- Record:消息记录。每个Record包含了key、value和 timestamp;
- Consumer:消费者,用来读取Kafka中的数据(Record)进行消费;
- Consumer Group:消费者组,一个消费者组可以包含一个或多个消费者。在Kafka的设计中一个分区内的数据只能被消费者组中的某一个消费者消费,同一个消费者组的消费者可以消费某个Topic不同分区的数据;
- Segment:实际存储消息的片段;一个Partition在物理上由一个或者多个Segment构成,每个Segment中保存真实的消息数据。
4、工作流程
图2 工作流程
Kafka一般工作流程如下(根据ACK应答策略会存在部分差异):
- 生产者与Leader直接交互,先从集群获取Topic对应分区的Leader元数据;
- 获取到Leader分区元数据后进行消息发送;
- Kafka Broker对应的Leader分区收到消息后写入文件进行持久化;
- Follower拉取Leader消息,进行数据同步;
- Follower完成消息拉取后给Leader回复ACK确认;
- Leader和Follower分区完成数据同步后,Leader分区给生产者回复ACK确认。
👇 ACK应答机制
通过配置request.required.acks属性来配置ACK策略:
- 0代表生产者往集群发送数据不需要等待集群的返回,不确保消息是否发送成功。安全性最低但是效率最高。
- 1(默认)代表生产者往集群发送数据只要Leader应答就可以发送下一条,只确保Leader发送成功(Leader不需要等待Follower完成数据同步即返回生产者ACK确认)。
- all代表生产者往集群发送数据需要所有的Follower都完成数据同步才会发送下一条,确保Leader发送成功和所有的副本都完成备份。安全性最高,但是效率最低。
5、Kafka数据存储设计
5.1 Topic和数据日志
Topic是同一类别的消息记录(Record)的集合。在Kafka中,一个Topic又可以被划分成多个Partition,分区数据日志文件结构如下:
图3 Topic和数据日志-分区数据日志文件结构
每个Partition是一个有序、不可变的消息序列,新的消息只会被追加到Partition的末尾。在每个Partition中,通过offset(偏移量)标识消息。由此可见,在同一个Partition内消息是有序的,在不同Partition之间,不能保证消息被有序消费。
Kafka可以通过log.retention配置项设定消息日志在集群内的留存时间,默认为168小时(即7天)。
5.2 Partition结构
Partition在服务器上是以文件夹形式存在的,每个Partition文件夹内会有多组Segment文件,每组Segment文件又包含.index、.log、.timeindex三个文件,其中.log是实际存储消息日志的地方,而.index和.timeindex为索引文件,用于检索消息。
Q:为什么有了Partition还要有Segment?
Segment对应一个文件(实现上对应两个文件,一个数据文件,一个索引文件),一个Partition对应一个文件夹,一个Partition内理论上可以包含任意多个Segment。
如果不引入Segment ,所有消息日志都直接写在Partition文件内,会导致Partition文件一直增大。同时,在做data purge时,需要把文件的前面部分给删除,不符合Kafka文件的顺序写优化设计方案。引入Segment后,消息日志被分散在多个Segment中, 每次做data purge,只需要把旧的Segment整个文件删除即可,保证了每个Segment的顺序写。
5.3 Partition的数据文件(offset、MessageSize、data)
Partition中的每条消息包含三个属性:offset、MessageSize、data,其中offset表示消息在这个Partition中的偏移量,offset 不是实际存储位置,而是逻辑上一个值,用来唯一标识Partition内的一条消息,相当于消息id;MessageSize表示消息内容data的大小;data为消息的具体内容。
5.4 数据文件分段Segment(顺序读写、分段命令、二分查找)
Partition物理上由多个Segment文件组成,每个Segment大小相等(大致)。每个Segment数据文件以该段中最小的offset命名,文件扩展名为.log。这样在查找指定offset消息的时候,用二分查找法可以快速定位到该消息在哪个Segment数据文件中。
5.5 数据文件索引(分段索引、稀疏存储)
Kafka为每个Segment建立了索引文件(文件名与数据文件一致,扩展名为.index)。具体是采用稀疏索引方式,每隔一定字节的数据建立一条索引。这样做的好处是减少了索引体积,以便保留在内存中;坏处是查询时命中需要消耗更多时间(相对)。
图4 数据文件索引-稀疏索引
6、生产者&消费者设计
6.1 负载均衡(Partition会均衡分布到不同Broker上)
由于一个Topic可以有多个Partition,不同Partition均衡分布在不同的Broker上。基于该特性,生产者可通过随机、轮询或hash等策略,将消息平均发送至多个Broker中,实现负载均衡。
6.2 批量发送
生产者在本地内存中进行消息聚合,以单次请求发送批量数据的方式,减少网络I/O操作(副作用是一定程度上会影响消息实时性,以时延换取吞吐量)。
6.3 压缩(Snappy、Gzip、LZ4)
生产者通过Snappy、Gzip、LZ4等算法对数据进行压缩传输,减少传输数据量,减轻网络压力(以CPU资源换取网络时延的降低)。
7、总结
Kafka凭借其架构与性能优势,愈来愈受到众厂商的青睐。依托其完善的社区环境,Kafka构建了庞大而成熟的生态,已成为大数据及流计算领域中至关重要的一环。