为了账号安全,请及时绑定邮箱和手机立即绑定

Apache Kafka深度解析:架构、核心概念及实战技巧

标签:
大数据 架构
通往异步通信之道

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));  
    }
  1. 部分

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
  1. 实时事件流

2、消息队列的情况

  • 解耦系统
  • 负载均衡
  • 数据流
  1. 实时处理
  • 实时欺诈侦测
  • IoT 数据处理服务
  • 实时推荐服务
丹麦不要使用Kafka何时
丹麦避开Kafka何时

注:根据上下文调整更合适为:

何时避免使用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 常见性能问题及常见解决方案
  1. 高延迟:
  • 检查批次大小和 linger.ms 参数
  • 监控网络吞吐量
  • 检查磁盘 I/O 是否成为瓶颈

2. 低吞吐率:

  • 增加批次大小
  • 开启压缩
  • 调整分区数

3. 记忆问题:

  • 监控堆内存使用
  • 调整缓冲区的大小
  • 查看垃圾回收模式

4. 网络拥堵问题

  • 开启压缩
  • 优化消息的大小
  • 检查网络设置
面试时常问的问题
  1. Q:Kafka是如何保证消息顺序的? A:Kafka保证在同一个分区内的消息按发送顺序传递。
  2. Q:ZooKeeper在Kafka中的作用是什么? A:ZooKeeper管理broker元数据、控制器选举和集群成员关系。不过,较新版本的Kafka正逐渐减少对ZooKeeper的依赖。
  3. Q:解释消费者组的重要性。 A:消费者组可以实现消息的并行处理,同时确保每个消息只被组内的一个消费者处理。
  4. Q:Broker失败时会发生什么? A:如果一个Broker失败,其分区的领导权会转移到其他Broker上的副本。集群控制器管理此故障转移过程。
  5. Q:Kafka是如何处理消息保留的? A:Kafka根据时间和大小的保留策略来保留消息,这些策略可以在主题级别进行配置。
结论

Apache Kafka 是一个构建可扩展且具有弹性的事件流处理应用程序的强大平台。了解其核心概念、架构和最佳实践对于成功部署至关重要。无论是构建实时分析系统、事件驱动系统,还是数据流,Kafka 都提供了可靠的消息传递工具和保证,以确保大规模应用下的消息可靠传输。

记得仔细评估您的用例,看看它是否适合Kafka的优势和局限性。虽然Kafka在这方面处理高吞吐量事件流很拿手,但对于基本的队列需求,更简单的消息解决方案会更加合适。

如果你想深入了解谁在背后推动Kafka Segments,可以阅读我写的文章,它提供了详细概述:

https://levelup.gitconnected.com/rocksdb-in-apache-kafka-and-beyond-a-technical-deep-dive-9f00b1a4ebff

参考

  • Apache Kafka 文档
  • Kafka: 权威指南(O’Reilly)
  • 设计事件驱动系统(Confluent)
学霸圈 🎓

感谢你读到最后,在你离开前,

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消