Kafka是最初由Linkedin公司开发,是一个分布式、支持分区的(partition)、多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop的批处理系统、低延迟的实时系统、storm/Spark流式处理引擎,web/nginx日志、访问日志,消息服务等等,用scala语言编写,Linkedin于2010年贡献给了Apache基金会并成为顶级开源 项目。
Kafka 定义
-
Apache Kafka 是一个分布式流处理平台,由 LinkedIn 公司开发和维护,后捐赠给 Apache 软件基金会。
-
Kafka 主要用于构建实时数据管道和流应用,可以处理网站点击流、日志、传感器数据等。
优点
-
高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒
-
可扩展性:kafka集群支持热扩展
-
持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
-
容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
-
高并发:支持数千个客户端同时读写
应用场景
-
日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。
-
消息系统:解耦和生产者和消费者、缓存消息等。
-
用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
-
运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
-
流式处理:比如spark streaming和storm
-
事件源
基础架构与名词解释
-
Broker:Kafka 集群中的服务器节点。
-
Topic:消息分类,Kafka 中发布和订阅的消息类别。
-
Producer:消息生产者,负责向 Kafka 发送消息。
-
Consumer:消息消费者,负责从 Kafka 接收消息。
-
Consumer Group:消费者组,同一组内的消费者共享消息的消费。
-
Partition:分区,每个 Topic 可以分为多个分区,分区是 Kafka 实现数据冗余和伸缩性的关键。
-
Zookeeper:kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性。
-
Message:每一条发送的消息主体。
Kafka 工作原理
(1)发送数据
我们看上面的架构图中,producer就是生产者,是数据的入口。注意看图中的红色箭头,Producer在写入数据的时候永远的找leader,不会直接将数据写入follower!那leader怎么找呢?写入的流程又是什么样的呢?我们看下图:
在这里插入图片描述
发送的流程就在图中已经说明了,就不单独在文字列出来了!需要注意的一点是,消息写入leader后,follower是主动的去leader进行同步的!producer采用push模式将数据发布到broker,每条消息追加到分区中,顺序写入磁盘,所以保证同一分区内的数据是有序的!写入示意图如下:
在这里插入图片描述
上面说到数据会写入到不同的分区,那kafka为什么要做分区呢?相信大家应该也能猜到,分区的主要目的是:
-
方便扩展:因为一个topic可以有多个partition,所以我们可以通过扩展机器去轻松的应对日益增长的数据量。
-
提高并发:以partition为读写单位,可以多个消费者同时消费数据,提高了消息的处理效率。
熟悉负载均衡的朋友应该知道,当我们向某个服务器发送请求的时候,服务端可能会对请求做一个负载,将流量分发到不同的服务器,那在kafka中,如果某个topic有多个partition,producer又怎么知道该将数据发往哪个partition呢?kafka中有几个原则:
-
partition在写入的时候可以指定需要写入的partition,如果有指定,则写入对应的partition。
-
如果没有指定partition,但是设置了数据的key,则会根据key的值hash出一个partition。
-
如果既没指定partition,又没有设置key,则会轮询选出一个partition。
保证消息不丢失是一个消息队列中间件的基本保证,那producer在向kafka写入消息的时候,怎么保证消息不丢失呢?其实上面的写入流程图中有描述出来,那就是通过ACK应答机制!在生产者向队列写入数据的时候可以设置参数来确定是否确认kafka接收到数据,这个参数可设置的值为0、1、all。
-
0代表producer往集群发送数据不需要等到集群的返回,不确保消息发送成功。安全性最低但是效率最高。
-
1代表producer往集群发送数据只要leader应答就可以发送下一条,只确保leader发送成功。
-
all代表producer往集群发送数据需要所有的follower都完成从leader的同步才会发送下一条,确保leader发送成功和所有的副本都完成备份。安全性最高,但是效率最低。
最后要注意的是,如果往不存在的topic写数据,能不能写入成功呢?kafka会自动创建topic,分区和副本的数量根据默认配置都是1。
(2)保存数据
Producer将数据写入kafka后,集群就需要对数据进行保存了!kafka将数据保存在磁盘,可能在我们的一般的认知里,写入磁盘是比较耗时的操作,不适合这种高并发的组件。Kafka初始会单独开辟一块磁盘空间,顺序写入数据(效率比随机写入高)。
(1)Partition 结构
前面说过了每个topic都可以分为一个或多个partition,如果你觉得topic比较抽象,那partition就是比较具体的东西了!Partition在服务器上的表现形式就是一个一个的文件夹,每个partition的文件夹下面会有多组segment文件,每组segment文件又包含.index文件、.log文件、.timeindex文件(早期版本中没有)三个文件, log文件就实际是存储message的地方,而index和timeindex文件为索引文件,用于检索消息。
在这里插入图片描述
如上图,这个partition有三组segment文件,每个log文件的大小是一样的,但是存储的message数量是不一定相等的(每条的message大小不一致)。文件的命名是以该segment最小offset来命名的,如000.index存储offset为0~368795的消息,kafka就是利用分段+索引的方式来解决查找效率的问题。
(2)Message结构
上面说到log文件就实际是存储message的地方,我们在producer往kafka写入的也是一条一条的message,那存储在log中的message是什么样子的呢?消息主要包含消息体、消息大小、offset、压缩类型……等等!我们重点需要知道的是下面三个:
-
offset:offset是一个占8byte的有序id号,它可以唯一确定每条消息在parition内的位置!
-
消息大小:消息大小占用4byte,用于描述消息的大小。
-
消息体:消息体存放的是实际的消息数据(被压缩过),占用的空间根据具体的消息而不一样。
(3)存储策略
无论消息是否被消费,kafka都会保存所有的消息。那对于旧数据有什么删除策略呢?
-
基于时间,默认配置是168小时(7天)。
-
基于大小,默认配置是1073741824。
需要注意的是,kafka读取特定消息的时间复杂度是O(1),所以这里删除过期的文件并不会提高kafka的性能!
(3)消费数据
消息存储在log文件后,消费者就可以进行消费了。在讲消息队列通信的两种模式的时候讲到过点对点模式和发布订阅模式。Kafka采用的是发布订阅模式,消费者主动的去kafka集群拉取消息,与producer相同的是,消费者在拉取消息的时候也是找leader去拉取。
多个消费者可以组成一个消费者组(consumer group),每个消费者组都有一个组id!同一个消费组者的消费者可以消费同一topic下不同分区的数据,但是不会组内多个消费者消费同一分区的数据!!!我们看下图:
在这里插入图片描述
图示是消费者组内的消费者小于partition数量的情况,所以会出现某个消费者消费多个partition数据的情况,消费的速度也就不及只处理一个partition的消费者的处理速度!如果是消费者组的消费者多于partition的数量,那会不会出现多个消费者消费同一个partition的数据呢?上面已经提到过不会出现这种情况!多出来的消费者不消费任何partition的数据。所以在实际的应用中,建议消费者组的consumer的数量与partition的数量一致!
在保存数据的小节里面,我们聊到了partition划分为多组segment,每个segment又包含.log、.index、.timeindex文件,存放的每条message包含offset、消息大小、消息体……我们多次提到segment和offset,查找消息的时候是怎么利用segment+offset配合查找的呢?假如现在需要查找一个offset为368801的message是什么样的过程呢?我们先看看下面的图:
在这里插入图片描述
-
先找到offset的368801message所在的segment文件(利用二分法查找),这里找到的就是在第二个segment文件。
-
打开找到的segment中的.index文件(也就是368796.index文件,该文件起始偏移量为368796+1,我们要查找的offset为368801的message在该index内的偏移量为368796+5=368801,所以这里要查找的相对offset为5)。由于该文件采用的是稀疏索引的方式存储着相对offset及对应message物理偏移量的关系,所以直接找相对offset为5的索引找不到,这里同样利用二分法查找相对offset小于或者等于指定的相对offset的索引条目中最大的那个相对offset,所以找到的是相对offset为4的这个索引。
-
根据找到的相对offset为4的索引确定message存储的物理偏移位置为256。打开数据文件,从位置为256的那个地方开始顺序扫描直到找到offset为368801的那条Message。
这套机制是建立在offset为有序的基础上,利用segment+有序offset+稀疏索引+二分查找+顺序查找等多种手段来高效的查找数据!至此,消费者就能拿到需要处理的数据进行处理了。那每个消费者又是怎么记录自己消费的位置呢?在早期的版本中,消费者将消费到的offset维护zookeeper中,consumer每间隔一段时间上报一次,这里容易导致重复消费,且性能不好!在新的版本中消费者消费到的offset已经直接维护在kafk集群的__consumer_offsets这个topic中!
消息存储
-
Kafka 使用分区来存储消息,每个分区中的数据是有序的。
-
消息被追加到分区的末尾,称为日志(log)。
消息复制
-
Kafka 通过副本(Replica)实现消息的冗余和高可用性。
-
每个分区都有一个或多个副本,其中一个被选为领导者(Leader),其余为追随者(Follower)。
-
生产者将消息发送到领导者副本,消费者从领导者副本读取消息。
-
追随者副本从领导者副本异步复制数据,以保持与领导者的同步。
消息消费
-
消费者通过消费者组订阅一个或多个 Topic。
-
每个消费者组中的消费者共同读取一个 Topic 的所有分区,每个分区只被一个消费者组中的一个消费者读取。
-
消费者读取消息的位置由消费者偏移量(Offset)决定。
Kafka 核心配置
Broker 配置
-
broker.id
:每个 Broker 的唯一标识符。 -
listeners
:Broker 监听的地址和端口。 -
log.dirs
:Kafka 日志文件的存储目录。
Topic 配置
-
num.partitions
:Topic 的分区数。 -
replication.factor
:每个分区的副本数。
Producer 配置
-
bootstrap.servers
:Producer 连接 Kafka 集群的地址列表。 -
acks
:确认机制,all
表示所有副本都收到消息后才确认。 -
retries
:发送失败时的重试次数。
Consumer 配置
-
bootstrap.servers
:Consumer 连接 Kafka 集群的地址列表。 -
group.id
:消费者组 ID。 -
auto.offset.reset
:当消费者第一次启动时,从哪里开始读取消息(例如earliest
或latest
)。
Kafka 特性
高吞吐量
-
Kafka 通过磁盘顺序读写、分区和副本机制实现高吞吐量。
高可用性
-
Kafka 通过副本机制实现数据冗余和高可用性。
-
当领导者副本出现故障时,追随者副本可以升级为新的领导者。
分布式
-
Kafka 是一个分布式系统,可以处理大量的数据。
-
Kafka 集群可以水平扩展以处理更多的数据。
持久化
-
Kafka 将消息持久化到磁盘,即使系统崩溃也不会丢失数据。
实时性
-
Kafka 支持实时处理数据流。
Kafka 监控与运维
监控
-
使用 JMX、Kafka 自带的监控指标或第三方监控工具(如 Prometheus、Grafana)监控 Kafka 集群的状态。
运维
-
定期备份 Kafka 数据。
-
管理 Kafka 的配置和版本升级。
-
处理 Kafka 集群中的故障和性能问题。
Kafka 与其他系统的集成
与 Flink、Spark Streaming 的集成
-
Kafka 可以作为 Flink 和 Spark Streaming 等流处理框架的数据源和数据目标。
与数据库的集成
-
Kafka Connect 可以实现 Kafka 与各种数据库(如 MySQL、PostgreSQL、MongoDB 等)之间的双向数据同步。
与其他消息队列的集成
-
Kafka 可以与其他消息队列(如 RabbitMQ、ActiveMQ 等)进行集成,实现数据的交换和同步。