前言
从Kafka的角度来看:
- Kafka是一个分布式的、基于发布-订阅模式的消息队列系统,用于构建实时数据流管道和流应用程序。
- Kafka使用主题(Topic)来组织和存储消息,生产者(Producer)向主题发布消息,消费者(Consumer)从主题订阅并消费消息。
- Kafka具有高吞吐量、低延迟、高可扩展性和高可用性等特点,能够处理大规模的实时数据流。
- Kafka采用分区(Partition)机制,将主题划分为多个分区,以实现并行处理和负载均衡。
- Kafka使用ZooKeeper进行分布式协调和元数据管理,保证系统的一致性和可靠性。
- Kafka提供了丰富的客户端API,支持多种编程语言,方便应用程序与Kafka集群进行交互。
从源码的角度来看:
- Kafka源码采用Scala和Java编程语言编写,遵循面向对象和函数式编程的设计原则。
- 源码的核心组件包括:
- Broker:Kafka集群中的服务器节点,负责接收、存储和分发消息。
- Producer:消息生产者,负责将消息发布到Kafka集群中的主题。
- Consumer:消息消费者,负责从Kafka集群中订阅和消费消息。
- Controller:Kafka集群中的控制器,负责管理和协调集群的状态和元数据。
- ZooKeeper:分布式协调服务,用于管理Kafka集群的元数据和协调节点之间的通信。
- 源码采用模块化设计,将系统功能划分为多个独立的模块,如网络通信、消息存储、消息分发、副本管理等。
- 源码广泛使用并发编程技术,如多线程、异步I/O、锁和同步机制等,以实现高性能和高并发处理。
- 源码重视可扩展性和可插拔性,提供了多个可配置的组件和插件机制,允许用户根据需求进行定制和扩展。
- 源码注重性能优化,采用零拷贝、批量处理、内存映射等技术,以提高系统的吞吐量和降低延迟。
- 源码遵循严格的编码规范和设计原则,如SOLID原则、设计模式等,保证代码的可读性、可维护性和可测试性。
副本管理(Replica Management)
核心类:ReplicaManager.scala
职责:
- 管理分区的主副本和从副本:在Kafka中,每个分区有一个领导者(Leader)和多个跟随者(Follower)。
ReplicaManager
负责维护这些副本的状态和关系。 - 处理副本同步和故障转移:确保所有副本的数据一致性,并在领导者发生故障时快速选举新的领导者,以保持系统的高可用性。
关键方法:
becomeLeaderOrFollower(correlationId: Int, ...)
: 这个方法用于将某个副本角色转变为领导者或跟随者。它返回当前分区的领导者和跟随者的状态列表。fetchMessages(timeout: Long, ...)
: 该方法处理从副本中拉取消息的请求,返回指定超时时间内的消息数据。
关键概念:
- 领导者选举:当现有领导者不可用时,
ReplicaManager
会触发领导者选举,确保分区始终有一个有效的领导者。 - 数据同步:
ReplicaManager
确保所有从副本与领导者的数据保持同步,通常通过ISR(In-Sync Replica)机制实现。
控制器(Controller)
核心类:KafkaController.scala
职责:
- 集群元数据管理:负责维护和更新整个Kafka集群的元数据,包括主题、分区、副本等信息。
- 处理Broker的上下线:监控Broker的状态,当有Broker上线或下线时,更新集群的元数据,并执行相应的操作(如重新分配分区)。
- 管理分区的领导者选举:在需要时(如Broker故障),负责触发并管理分区领导者的重新选举。
关键方法:
onBrokerStartup(id: Int): Unit
: 处理Broker启动时的逻辑,包括将其加入集群、重新分配分区等。onBrokerFailure(id: Int): Unit
: 处理Broker故障时的逻辑,重新选举分区的领导者,并将受影响的分区迁移到其他Broker。
关键概念:
- Zookeeper集成:早期Kafka使用Zookeeper进行控制器的选举和元数据管理,尽管在最新版本中,Kafka已经逐步移除了对Zookeeper的依赖。
- 高可用性:控制器本身需要具备高可用性,通常通过选举机制确保集群中只有一个活跃的控制器。
网络层(Network Layer)
核心类:SocketServer.scala
职责:
- 处理客户端连接和请求:负责接收来自生产者、消费者以及其他客户端的网络连接和请求。
- 实现高效的非阻塞I/O:通过NIO(Non-blocking I/O)实现高效的网络通信,支持大规模的并发连接。
关键方法:
acceptNewConnections(): Unit
: 接受新的网络连接请求,并初始化相应的处理器。processNewResponses(): Unit
: 处理来自服务器的响应,将其发送回客户端。
关键概念:
- 协议通信:支持Kafka自定义的二进制协议,确保高效的数据传输。
- 性能优化:通过非阻塞I/O、批处理等技术优化网络性能,减少延迟和提升吞吐量。
消息协议(Message Protocol)
核心包:org.apache.kafka.common.protocol
职责:
- 定义客户端和服务器之间的通信协议:包括请求和响应的格式、序列化和反序列化方式。
- 确保数据的一致性和兼容性:通过版本控制和协议升级机制,保证不同版本的客户端和服务器之间可以顺利通信。
关键概念:
- 请求/响应模型:Kafka采用请求/响应模式进行客户端和服务器之间的通信,支持多种类型的请求(如生产、消费、元数据查询等)。
- 序列化机制:使用高效的序列化协议(如Kafka自己的二进制协议)减少网络传输的开销。
生产者(Producer)
核心类:KafkaProducer.java
职责:
- 实现消息发送逻辑:负责将生产者发送的消息传递给Kafka集群。
- 优化机制:包括分区选择、批处理、压缩等,以提升发送效率和减少网络开销。
关键方法:
public Future<RecordMetadata> send(ProducerRecord<K, V> record)
: 发送一条消息到指定的主题和分区,并返回一个Future
对象,可以用于跟踪发送结果。
关键概念:
- 分区策略:生产者可以根据键值、轮询或自定义策略选择消息要发送的分区。
- 批处理和压缩:将多条消息批量发送,并对批量数据进行压缩,减少网络传输次数和带宽使用。
消费者(Consumer)
核心类:KafkaConsumer.java
职责:
- 实现消息消费逻辑:负责从Kafka集群中拉取并处理消息。
- 管理机制:包括分区分配、偏移量管理、心跳机制等,确保消费的可靠性和高效性。
关键方法:
public ConsumerRecords<K, V> poll(Duration timeout)
: 从Kafka拉取指定超时时间内的消息记录。
关键概念:
- 消费者组:多个消费者可以组成一个消费者组,协作消费不同的分区,实现横向扩展。
- 自动和手动提交偏移量:消费者可以选择自动提交已消费的偏移量,或手动控制偏移量的提交,以实现可靠性。
- 心跳机制:保持与Kafka集群的连接状态,防止被误认为是故障并触发重新分配分区。
流处理(Stream Processing)
核心包:org.apache.kafka.streams
职责:
- 实现流处理DSL(Domain Specific Language):提供用于定义流处理拓扑的高级API,简化流处理应用的开发。
- 高级功能:包括状态存储、窗口操作、时间处理等,支持复杂的流处理逻辑。
关键概念:
- 状态ful处理:支持有状态的操作,如聚合、连接等,通过内置的状态存储机制(如RocksDB)保持中间状态。
- 时间语义:支持事件时间和处理时间,允许基于时间窗口进行数据分组和聚合。
连接器框架(Connectors Framework)
核心包:org.apache.kafka.connect
职责:
- 提供数据导入导出的框架:简化与外部系统(如数据库、文件系统、消息队列等)的集成,支持数据的批量导入和导出。
- 支持自定义Source和Sink连接器:允许用户根据具体需求开发自定义的连接器,扩展Kafka Connect的功能。
关键概念:
- Source连接器:从外部系统读取数据并写入Kafka主题。
- Sink连接器:从Kafka主题读取数据并写入到外部系统。
- 分布式和独立模式:支持运行在分布式集群或单节点的独立模式,根据需要选择合适的部署方式。
安全模块(Security Module)
核心包:org.apache.kafka.common.security
职责:
- 实现认证和授权机制:确保只有经过认证的客户端和用户可以访问Kafka集群,并根据权限控制其操作。
- 支持多种安全协议:包括SSL(Secure Sockets Layer)、SASL(Simple Authentication and Security Layer)等,提供多层次的安全保障。
关键概念:
- 认证(Authentication):验证客户端和服务器的身份,常用方法包括SSL证书认证和SASL机制(如GSSAPI、PLAIN等)。
- 授权(Authorization):基于ACL(Access Control Lists)控制用户对主题、集群和其他资源的访问权限。
- 加密传输:通过SSL/TLS对客户端和服务器之间的通信进行加密,防止数据被窃听和篡改。
下面挑我熟悉的几个讲讲
Kafka的分区日志的存储机制:
- 分区日志基本概念
特点:
- 顺序写入
- 不可变性
- 分段存储
- 偏移量索引
- 消息压缩
类似:
- 类似于写日记
- 每天一页(segment)
- 有目录(index)
- 按日期查找(offset)
- 分段存储(Segment)
结构:
segment1: [0-999] # 已封存
segment2: [1000-1999] # 已封存
segment3: [2000-...] # 活跃段
文件组成:
- .log (数据文件)
- .index (偏移量索引)
- .timeindex (时间索引)
示例:
/topic-0/
00000000000000000000.log
00000000000000000000.index
00000000000001000000.log
00000000000001000000.index
- 索引机制
稀疏索引:
offset: position
0: 0
100: 4096
200: 8192
查找过程:
1. 定位segment
2. 找最近索引项
3. 顺序扫描
示例代码:
class Index {
// 物理位置
private long position;
// 消息大小
private int size;
// 时间戳
private long timestamp;
}
- 写入流程
class LogSegment {
private FileChannel fileChannel;
private Index index;
public void append(Message message) {
// 写入消息
long position = fileChannel.position();
fileChannel.write(message.toByteBuffer());
// 更新索引
if(shouldIndex(position)) {
index.add(message.offset(), position);
}
}
}
- 读取流程
class LogSegment {
public Message read(long offset) {
// 查找索引
Index.Entry entry = index.find(offset);
// 定位文件位置
fileChannel.position(entry.position);
// 读取消息
ByteBuffer buffer = ByteBuffer.allocate(entry.size);
fileChannel.read(buffer);
return Message.parse(buffer);
}
}
- 压缩策略
日志压缩类型:
1. 删除压缩
- 删除过期消息
- 保留最新状态
2. 合并压缩
- 合并相同key消息
- 只保留最新值
示例:
原始日志:
key1:v1, key2:v1, key1:v2, key2:v2
压缩后:
key1:v2, key2:v2
- 清理策略
class LogCleaner {
public void clean() {
// 基于时间清理
deleteSegmentsBefore(System.currentTimeMillis() - retention);
// 基于大小清理
while(size() > maxSize) {
deleteOldestSegment();
}
}
}
- 文件格式
消息格式:
+----------------+----------------+----------------+
| Length | Header | Payload |
+----------------+----------------+----------------+
| 4 bytes | variable | variable |
索引格式:
+----------------+----------------+----------------+
| Offset | Position | Size |
+----------------+----------------+----------------+
| 8 bytes | 8 bytes | 4 bytes |
- 性能优化
class LogWriter {
// 内存映射
private MappedByteBuffer buffer;
// 批量写入
public void appendBatch(List<Message> messages) {
ByteBuffer batch = prepareBatch(messages);
buffer.put(batch);
}
// 预分配空间
private void preallocate(long size) {
fileChannel.truncate(size);
}
}
- 容错机制
class LogRecovery {
public void recover() {
// 检查文件完整性
verifyChecksum();
// 重建索引
rebuildIndex();
// 截断损坏部分
truncateCorrupted();
}
}
- 并发控制
class LogManager {
private final Lock writeLock = new ReentrantLock();
private final Map<Integer, LogSegment> segments;
public void write(Message msg) {
writeLock.lock();
try {
activeSegment().append(msg);
} finally {
writeLock.unlock();
}
}
}
- 监控指标
class LogMetrics {
// 大小监控
public long totalSize();
// 消息数量
public long messageCount();
// 段文件数量
public int segmentCount();
// 写入延迟
public void recordAppendLatency(long latency);
}
关键设计考虑:
- 顺序写入提高性能
- 分段管理便于清理
- 索引加速查找
- 压缩节省空间
- 不变性保证一致性
Kafka Streams
- 基本定位
Kafka:
- 分布式消息队列
- 存储层和传输层
- 消息持久化
Kafka Streams:
- 流处理计算框架
- 计算层
- 实时数据处理
- 架构对比
Kafka架构:
Producer -> Broker(Topic/Partition) -> Consumer
Kafka Streams架构:
Source Topic -> Stream Processing -> Sink Topic
↓
State Store(状态存储)
- 核心概念
// Kafka核心概念
- Topic (主题)
- Partition (分区)
- Producer (生产者)
- Consumer (消费者)
// Kafka Streams核心概念
- KStream (记录流)
- KTable (变更日志)
- GlobalKTable (全局表)
- Processor (处理器)
- 代码示例
// Kafka Producer
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("topic", "key", "value"));
// Kafka Consumer
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("topic"));
// Kafka Streams
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream("input-topic");
stream.filter((key, value) -> value.length() > 5)
.mapValues(value -> value.toUpperCase())
.to("output-topic");
- 状态管理
// Kafka: 无状态
Consumer.poll() -> process -> Consumer.commit()
// Kafka Streams: 有状态
builder.stream("input")
.groupByKey()
.count() // 状态存储
.toStream()
.to("output");
- 处理语义
Kafka:
- At least once
- At most once
- Exactly once (事务)
Kafka Streams:
- 自动exactly-once语义
- 状态存储容错
- 自动重平衡
- 应用场景
Kafka适用:
- 消息队列
- 日志收集
- 事件总线
- 数据管道
Kafka Streams适用:
- 实时计算
- 数据清洗
- 实时ETL
- 流式统计
- 配置示例
# Kafka配置
bootstrap.servers=localhost:9092
acks=all
retries=3
# Kafka Streams配置
application.id=my-stream-app
bootstrap.servers=localhost:9092
state.dir=/tmp/kafka-streams
processing.guarantee=exactly_once
- 处理示例
// 复杂流处理
StreamsBuilder builder = new StreamsBuilder();
// 读取输入流
KStream<String, Order> orders = builder.stream(
"orders",
Consumed.with(Serdes.String(), orderSerde)
);
// 处理逻辑
orders.groupByKey()
.windowedBy(TimeWindows.of(Duration.ofMinutes(5)))
.aggregate(
() -> 0L,
(key, order, total) -> total + order.getAmount(),
Materialized.as("order-store")
)
.toStream()
.to("order-totals");