4、实时数据流架构:Kafka 核心概念
聊到实时数据流,Kafka 几乎是绕不开的坎。我最早接触 Kafka 是在做行情数据接入的时候,那时候每天要处理几亿条交易记录,传统消息队列根本扛不住。后来换了 Kafka,才算真正解决了问题。
说白了,Kafka 就是一个分布式的消息系统。但它跟传统的消息队列不太一样——它更像是一个日志系统,或者说是一个数据管道。今天我就把 Kafka 最核心的几个概念掰开揉碎了讲清楚。
4.1 Topic:数据的逻辑分类
Topic 是 Kafka 里最基本的概念。你可以把它理解成一个文件夹,或者一个数据流的名字。比如我们做金融数据接入,可能会有 stock_trade、market_depth、order_book 这些 Topic。
每个 Topic 代表一类数据。生产者往 Topic 里写数据,消费者从 Topic 里读数据。就这么简单。
实际项目中的命名规范:
- 用英文小写 + 下划线
- 带上业务域前缀,比如
trade_order、risk_alert - 不要用特殊字符,Kafka 对 Topic 名称有长度限制
我见过有人把 Topic 命名为 test123 或者 data,这种命名方式在生产环境里就是灾难。你想想看,半年后谁还记得这个 Topic 是干嘛的?
4.2 Partition:并行与扩展的基石
一个 Topic 可以分成多个 Partition。Partition 是 Kafka 并行处理的基本单位。
为什么要分区?说白了就是为了水平扩展。一个 Partition 只能被一个消费者线程消费,但多个 Partition 可以被多个消费者并行消费。这样吞吐量就上去了。
每个 Partition 内部是有序的,消息按顺序追加。但不同 Partition 之间不保证顺序。这个特性很重要——如果你需要全局有序,那就只能用一个 Partition,但这样吞吐量就受限了。
我的经验: 在金融场景里,通常按交易标的或者客户 ID 做分区。比如股票行情数据,按股票代码 hash 到不同 Partition。这样同一个股票的数据就在同一个 Partition 里,保证了局部有序。
我曾经遇到过一个坑:某个团队把所有数据都塞到一个 Partition 里,结果消费速度跟不上生产速度,消息越积越多。后来改成 8 个 Partition,问题就解决了。
4.3 Consumer Group:消费的协作机制
Consumer Group 是 Kafka 里一个很巧妙的设计。同一个 Group 里的消费者,共同消费一个 Topic 的消息。每条消息只会被 Group 里的一个消费者处理。
举个例子:假设我们有 4 个 Partition,Group 里有 3 个消费者。那么 Kafka 会尽量平均分配,比如消费者 A 处理 Partition 0 和 1,消费者 B 处理 Partition 2,消费者 C 处理 Partition 3。
如果消费者挂了,Kafka 会自动触发 Rebalance,把它的 Partition 分配给其他消费者。嗯,这里要注意——Rebalance 期间消费会暂停,所以不要频繁启停消费者。
避坑指南: 我曾经在生产环境里遇到过消费者频繁 Rebalance 的问题。原因是消费者处理消息太慢,超过了 max.poll.interval.ms 的默认值。Kafka 以为它挂了,就踢出了 Group。解决方案是调大这个超时时间,或者优化消息处理逻辑。
4.4 消息模型:生产与消费
Kafka 的消息模型其实很简单:生产者把消息发到 Topic 的某个 Partition,消费者从 Partition 里拉取消息。
消息本身是一个键值对。Key 用来决定消息去哪个 Partition(如果没指定 Key,就轮询分配)。Value 是消息体,可以是 JSON、Avro、Protobuf 等格式。
我建议在金融场景里用 Avro 或者 Protobuf,因为它们有 Schema 管理,数据格式变化时不容易出问题。JSON 虽然方便,但字段类型不严格,容易踩坑。
// 生产者示例(Java)
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
producer.send(new ProducerRecord<>("stock_trade", "AAPL", "{\"price\": 150.25, \"volume\": 1000}"));
producer.close();
// 消费者示例(Java)
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "trade_consumer_group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("stock_trade"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}
4.5 数据持久化:Kafka 的存储机制
Kafka 的数据持久化很有意思。它不像传统消息队列那样消费完就删除,而是把数据写到磁盘上,保留一段时间或者达到一定大小后才删除。
每个 Partition 对应一个日志文件。消息追加到文件末尾,顺序写盘。这就是 Kafka 高性能的秘密之一——顺序 I/O 比随机 I/O 快得多。
Kafka 还支持消息的压缩。我建议在金融场景里开启压缩,尤其是行情数据这种高频小消息的场景。压缩率能达到 70% 以上,能省不少磁盘和网络带宽。
关键配置:
log.retention.hours:消息保留时间,默认 168 小时(7 天)log.retention.bytes:消息保留大小,默认 -1(不限)log.segment.bytes:日志段大小,默认 1GBcompression.type:压缩类型,可选 gzip、snappy、lz4、zstd
我建议金融数据保留至少 3 天。因为有时候需要回溯数据做对账或者排查问题。保留太短,出了问题就抓瞎了。
4.6 核心架构图
下面这张图展示了 Kafka 的核心架构。你可以看到 Producer、Consumer、Broker、Topic、Partition 之间的关系。
4.7 总结
Kafka 的核心概念其实不复杂。Topic 是数据的分类,Partition 是并行和扩展的基石,Consumer Group 是消费的协作机制。消息模型就是生产者和消费者通过 Broker 交互。数据持久化靠的是顺序写盘和日志文件。
我个人觉得,理解这些概念的关键在于动手实践。搭一个单机版的 Kafka,写几个生产者和消费者,看看数据是怎么流动的。比看十遍文档都管用。
小建议: 刚开始学 Kafka 的时候,别急着搞集群。先单机跑通,再慢慢加节点。我见过太多人一上来就搞 3 节点集群,结果配置都搞不明白,更别说排查问题了。