Kafka架构
简介Apache Kafka 已经彻底改变了现代分布式系统处理事件流处理和异步通信的方法。在这篇文章里,我们将聊聊 Kafka 的架构、核心概念、怎么用以及一些最佳实践方法。不管你是为了面试准备,还是打算在系统里使用 Kafka,这篇文章都能给你提供足够的帮助。
她知道Apache Kafka是什么吗?Apache Kafka 是一个分布式事件流处理平台,旨在处理高吞吐量、容错性强的实时数据流。其核心是发布-订阅模型,并具备额外的保证和功能等特性,使之非常适合企业级应用。
《一些核心概念》——核心概念
1. 日志式的结构Kafka的核心数据结构是一个只追加的日志系统。这一简单而强大的概念提供如下特性:
- 不可变的记录列表
- 每个分区内的严格排序
- 通过复制保证持久性
- 通过顺序读写实现高性能
日志记录结构:
[记录 0] -> [记录 1] -> [记录 2] -> 等 -> [记录 N(最后一个记录)]
2. 主题和分区(Topic 和 Partition)
基于专家建议的修订翻译为:
基于专家的建议,应该使用“话题”而不是“主题”来翻译“Topics”,因为“话题”在口语中听起来更自然。
确保没有多余的标点符号,因为源文本中没有。
考虑上下文,如果文本使用的情境较为随意,翻译也应反映出这种语气。
话题
在Kafka里,主题是组织的基本单元。它们代表类别或 feed 名(topics),记录发布到这些主题。
分区
每个主题都分为分区,这些分区是Kafka中并行处理的单位。
- 有序的消息序列
- 每个分区都有一个领导者和若干副本
- 分区内消息具有顺序标识符,称为偏移量
// 创建一个具有多个分区的主题,每个分区有3个副本
Properties props = new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
try (Admin admin = Admin.create(props)) {
NewTopic newTopic = new NewTopic("my-topic", 3, (short) 3); // 3个分区,每个分区有3个副本
// 创建一个包含新主题的集合
admin.createTopics(Collections.singleton(newTopic));
}
- 部分
Kafka中分区数据是通过段来物理存储的:
- 每个分区由多个段组成
- 段是磁盘上的文件
- 新消息会被写入活动段
- 较旧的段会被密封并成为不可变的
配置示例如下:
Properties props = new Properties();
// 分段设置
props.put("log.segment.bytes", "1073741824"); // 每段大小为1GB
props.put("log.retention.hours", "168"); // 保留数据7天
生产者-消费者模型
1. 生产者:
(注:此处可根据实际情况添加定义或进一步解释)
生产者通过以下关键特性将消息发布到主题上:
- 分区策略方案
- 确认交付(acks)
- 批量处理和压缩
创建一个 Properties 类型的 producerProps 对象;
将 "localhost:9092" 设置为 producerProps 的 BOOTSTRAP_SERVERS_CONFIG 属性;
将 StringSerializer 类的名称设置为 producerProps 的 KEY_SERIALIZER_CLASS_CONFIG 和 VALUE_SERIALIZER_CLASS_CONFIG 属性;
尝试创建一个 Kafka 生产者对象 producer = new KafkaProducer<>(producerProps); {
创建一个 ProducerRecord 对象 record = new ProducerRecord<>("my-topic", "key", "value");
producer.send(record, (metadata, exception) -> {
如果异常为空 {
System.out.printf("将记录写入主题 %s 分区 %d 偏移量 %d%n",
metadata.topic(), metadata.partition(), metadata.offset());
} else {
打印异常堆栈;
}
});
}
2. 消费者们
以下是一些消费者可以通过读取消息的主题时使用的特性:
- 特性一
- 特性二
(注:请根据具体上下文补充特性描述)
- 可扩展性的消费者分组
- 偏移量管理
- 重新平衡
Properties consumerProps = new Properties(); // 消费者配置
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "my-group");
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
try (Consumer<String, String> consumer = new KafkaConsumer<>(consumerProps)) {
// 消费者订阅了 "my-topic" 主题
consumer.subscribe(Arrays.asList("my-topic")); // 创建一个包含 "my-topic" 的列表
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("收到的记录,键是 %s,值是 %s%n", record.key(), record.value());
}
}
}
要事务性的 Kafka
或
Kafka 风格的事务Kafka 支持跨多个分区的一次性写入:
Properties txnProps = new Properties();
// ... 其他相关配置 ...
txnProps.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "tx-id-1");
try (Producer<String, String> producer = new KafkaProducer<>(txnProps)) {
producer.initTransactions();
try {
producer.beginTransaction();
// 发送消息
ProducerRecord<String, String> record1 = new ProducerRecord<>("topic1", "key1", "value1");
ProducerRecord<String, String> record2 = new ProducerRecord<>("topic2", "key2", "value2");
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
throw e;
// 抛出异常;
}
}
明用例和最佳实践:
泰国何时使用Kafka
- 实时事件流
- 实时数据分析
- 日志收集
- 事件源
2、消息队列的情况
- 解耦系统
- 负载均衡
- 数据流
- 实时处理
- 实时欺诈侦测
- IoT 数据处理服务
- 实时推荐服务
注:根据上下文调整更合适为:
何时避免使用Kafka最终版本:
何时避开Kafka简单消息队列系统
- 当 RabbitMQ 或 ActiveMQ 就已经足够时
- 小型应用
2. 请求-应答模式
- RESTful API
- RPC 调用接口
3. 小数据集
当成本过高,不划算时
性能优化与调整指南 1. 生产者优化策略 批量处理批处理对于提高Kafka生产者的吞吐量至关重要。下面是如何配置及优化批处理的方法:
Properties producerProps = new Properties();
// 增加批处理大小(默认:16384字节)
producerProps.put(ProducerConfig.BATCH_SIZE_CONFIG, "65536"); // 64KB
// 批处理填充等待时间(20毫秒)
producerProps.put(ProducerConfig.LINGER_MS_CONFIG, "20");
// 总缓冲内存(默认:32MB)
producerProps.put(ProducerConfig.BUFFER_MEMORY_CONFIG, "67108864"); // 64MB
// 压缩方式
producerProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
关键考虑:
- 较大的批量虽然可以提升吞吐量,但也会增加延迟时间。
- 增加入留时间可以让更多消息在批量中累积。
- 缓冲内存需要足够大来处理您的批量大小乘以未完成请求的数量。
Kafka 支持多种压缩格式,每种都有不同的特点。其中包括各种优缺点。
// 不同的压缩方式
producerProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "none"); // 不压缩
producerProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "gzip"); // 压缩较高,但会占用更多CPU
producerProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy"); // 平衡压缩
producerProps.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4"); // 较快的压缩
压缩方面的注意事项:
- gzip: 最佳压缩比但更高的CPU占用
- snappy: 良好的压缩与CPU使用平衡
- lz4: 最快的压缩,中等压缩比
- none: 无压缩开销,但网络资源占用更高
根据您的可靠性要求,配置确认设置:
Properties producerProps = new Properties();
// acks=0: 发送即忘
producerProps.put(ProducerConfig.ACKS_CONFIG, "0");
// acks=1: 领导者已确认
producerProps.put(ProducerConfig.ACKS_CONFIG, "1");
// acks=all: 完全一致性确认
producerProps.put(ProducerConfig.ACKS_CONFIG, "all");
// 重试设置
producerProps.put(ProducerConfig.RETRIES_CONFIG, "3");
producerProps.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, "100"); // 重试间隔毫秒配置
2. 消费优化策略
拉取配置
帮助用户更有效地取消息:
Properties consumerProps = new Properties();
// 最小读取字节数(默认:1)
consumerProps.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, "65536"); // 64KB
// 最大等待时间(默认:500)
consumerProps.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, "500");
// 每个分区的最大读取字节数(默认:1MB)
consumerProps.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, "1048576");
// 轮询的最大记录数
consumerProps.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, "500");
获取优化技巧:
注:如果上下文是指特定领域的FETCH操作,请根据具体情境调整翻译。通常情况下,“Fetch Optimisation Tips:”可以译为“抓取优化技巧:”。
- 提高
FETCH_MIN_BYTES
以在高吞吐量场景中提高吞吐量 - 根据延迟需求调整
FETCH_MAX_WAIT_MS
的值 - 平衡
MAX_POLL_RECORDS
以适应处理能力
优化消费群体的行为。
Properties consumerProps = new Properties();
// 会话超时(默认为 10000)
consumerProps.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
// 心跳检查间隔(默认为 3000)
consumerProps.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000");
// 自动提交时间间隔(若启用自动提交)
consumerProps.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "5000");
3. 经纪商优化
主题配置
优化主题相关的设置:
Properties topicConfig = new Properties();
// 分区数量
topicConfig.put("num.partitions", "24");
// 复制因子
topicConfig.put("replication.factor", "3");
// 分段大小
topicConfig.put("segment.bytes", "1073741824"); // 1GB,即1073741824字节
// 刷新消息间隔
topicConfig.put("flush.messages", "1000000");
JVM 调整优化
重要的 Kafka代理的几个JVM设置:
# 代理JVM设置示例
KAFKA_HEAP_OPTS="-Xms6g -Xmx6g" # 设置JVM堆内存大小
KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35" # 优化JVM性能设置
4. 监控和性能表现情况
需要监控的关键性能指标包括:
// 生产者指标信息
Properties producerProps = new Properties();
producerProps.put(ProducerConfig.METRICS_RECORDING_LEVEL_CONFIG, "DEBUG");
// // 以下是一些重要的监控指标:
// - batch-size-avg
// - compression-rate-avg
// - request-latency-avg
// - outgoing-byte-rate
// - record-send-rate
自定义指标报告的实现
public class 自定义指标报告器类 实现 指标报告器 {
@Override
public void 初始化方法(List<KafkaMetric> 指标对象) {
指标对象.forEach(指标 -> {
if (指标.指标名().名称.equals("batch-size-avg") ||
指标.指标名().名称.equals("compression-rate-avg")) {
// 记录指标
System.out.printf("指标名: %s, 值: %f%n",
指标.指标名().名称,
指标.数值());
}
});
}
}
5. 性能测试指南
加载测试例子
public class KafkaLoadTest {
public static void main(String[] args) {
int messageCount = 1000000;
int messageSize = 1024; // 1KB
// 生成测试数据
byte[] message = new byte[messageSize];
Arrays.fill(message, (byte) 'x');
// 生产者配置
Properties props = createOptimizedProducerConfig();
// 测量吞吐量
long startTime = System.currentTimeMillis();
try (Producer<String, byte[]> producer = new KafkaProducer<>(props)) {
for (int i = 0; i < messageCount; i++) {
producer.send(new ProducerRecord<>("test-topic", message));
if (i % 10000 == 0) {
System.out.printf("每发送 %d 条消息%n", i);
}
}
}
long endTime = System.currentTimeMillis();
// 计算并打印结果
double seconds = (endTime - startTime) / 1000.0;
double msgPerSec = messageCount / seconds;
double mbPerSec = (messageCount * messageSize) / (1024.0 * 1024.0 * seconds);
System.out.printf("在 %.2f 秒内,发送了 %d 条消息%n", seconds, messageCount);
System.out.printf("吞吐量为 %.2f 条消息每秒,相当于 %.2f MB每秒%n", msgPerSec, mbPerSec);
}
private static Properties createOptimizedProducerConfig() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
ByteArraySerializer.class.getName());
props.put(ProducerConfig.BATCH_SIZE_CONFIG, "65536");
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
return props;
}
}
6 常见性能问题及常见解决方案
- 高延迟:
- 检查批次大小和 linger.ms 参数
- 监控网络吞吐量
- 检查磁盘 I/O 是否成为瓶颈
2. 低吞吐率:
- 增加批次大小
- 开启压缩
- 调整分区数
3. 记忆问题:
- 监控堆内存使用
- 调整缓冲区的大小
- 查看垃圾回收模式
4. 网络拥堵问题
- 开启压缩
- 优化消息的大小
- 检查网络设置
- Q:Kafka是如何保证消息顺序的? A:Kafka保证在同一个分区内的消息按发送顺序传递。
- Q:ZooKeeper在Kafka中的作用是什么? A:ZooKeeper管理broker元数据、控制器选举和集群成员关系。不过,较新版本的Kafka正逐渐减少对ZooKeeper的依赖。
- Q:解释消费者组的重要性。 A:消费者组可以实现消息的并行处理,同时确保每个消息只被组内的一个消费者处理。
- Q:Broker失败时会发生什么? A:如果一个Broker失败,其分区的领导权会转移到其他Broker上的副本。集群控制器管理此故障转移过程。
- Q:Kafka是如何处理消息保留的? A:Kafka根据时间和大小的保留策略来保留消息,这些策略可以在主题级别进行配置。
Apache Kafka 是一个构建可扩展且具有弹性的事件流处理应用程序的强大平台。了解其核心概念、架构和最佳实践对于成功部署至关重要。无论是构建实时分析系统、事件驱动系统,还是数据流,Kafka 都提供了可靠的消息传递工具和保证,以确保大规模应用下的消息可靠传输。
记得仔细评估您的用例,看看它是否适合Kafka的优势和局限性。虽然Kafka在这方面处理高吞吐量事件流很拿手,但对于基本的队列需求,更简单的消息解决方案会更加合适。
如果你想深入了解谁在背后推动Kafka Segments,可以阅读我写的文章,它提供了详细概述:
参考
- Apache Kafka 文档
- Kafka: 权威指南(O’Reilly)
- 设计事件驱动系统(Confluent)
感谢你读到最后,在你离开前,
- 请给作者点个赞并关注他👏
- 关注我们 X, LinkedIn, YouTube, Discord, Newsletter, 播客
- 在Differ上免费创建一个AI博客。
- 更多内容请看Stackademic.com
共同学习,写下你的评论
评论加载中...
作者其他优质文章