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

Kafka的架构原理,你真的理解吗?

标签:
MongoDB

Apache Kafka 最早是由 LinkedIn 开源出来的分布式消息系统,现在是 Apache 旗下的一个子项目,并且已经成为开源领域应用最广泛的消息系统之一。

webp

Kafka 社区非常活跃,从 0.9 版本开始,Kafka 的标语已经从“一个高吞吐量,分布式的消息系统”改为"一个分布式流平台"。

Kafka 和传统的消息系统不同在于:

Kafka是一个分布式系统,易于向外扩展。

它同时为发布和订阅提供高吞吐量。

它支持多订阅者,当失败时能自动平衡消费者。

消息的持久化。

Kafka 和其他消息队列的对比:

webp

入门实例

生产者

代码如下:

importjava.util.Properties;

importorg.apache.kafka.clients.producer.KafkaProducer;

importorg.apache.kafka.clients.producer.ProducerRecord;

publicclassUserKafkaProducerextendsThread

{

privatefinalKafkaProducer producer;

privatefinalString topic;

privatefinalProperties props =newProperties();

publicUserKafkaProducer(String topic)

{

props.put("metadata.broker.list","localhost:9092");

props.put("bootstrap.servers","master2:6667");

props.put("retries",0);

props.put("batch.size",16384);

props.put("linger.ms",1);

props.put("buffer.memory",33554432);

props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer");

props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer");

producer =newKafkaProducer(props);

this.topic = topic;

}

@Override

publicvoidrun(){

intmessageNo =1;

while(true)

{

String messageStr =newString("Message_"+ messageNo);

System.out.println("Send:"+ messageStr);

//返回的是Future<RecordMetadata>,异步发送

producer.send(newProducerRecord(topic, messageStr));

messageNo++;

try{

sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

消费者

代码如下:

Properties props =newProperties();

/* 定义kakfa 服务的地址,不需要将所有broker指定上 */

props.put("bootstrap.servers","localhost:9092");

/* 制定consumer group */

props.put("group.id","test");

/* 是否自动确认offset */

props.put("enable.auto.commit","true");

/* 自动确认offset的时间间隔 */

props.put("auto.commit.interval.ms","1000");

props.put("session.timeout.ms","30000");

/* key的序列化类 */

props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

/* value的序列化类 */

props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");

/* 定义consumer */

KafkaConsumer consumer =newKafkaConsumer<>(props);

/* 消费者订阅的topic, 可同时订阅多个 */

consumer.subscribe(Arrays.asList("foo","bar"));

/* 读取数据,读取超时时间为100ms */

while(true) {

ConsumerRecords records = consumer.poll(100);

for(ConsumerRecord record : records)

System.out.printf("offset = %d, key = %s, value = %s", record.offset(), record.key(), record.value());

}

Kafka 架构原理

对于 Kafka 的架构原理,我们先提出如下几个问题:

Kafka 的 topic 和分区内部是如何存储的,有什么特点?

与传统的消息系统相比,Kafka 的消费模型有什么优点?

Kafka 如何实现分布式的数据存储与数据读取?

Kafka 架构图

webp

Kafka 名词解释

在一套 Kafka 架构中有多个 Producer,多个 Broker,多个 Consumer,每个 Producer 可以对应多个 Topic,每个 Consumer 只能对应一个 Consumer Group。

整个 Kafka 架构对应一个 ZK 集群,通过 ZK 管理集群配置,选举 Leader,以及在 Consumer Group 发生变化时进行 Rebalance。

webp

Topic 和 Partition

在 Kafka 中的每一条消息都有一个 Topic。一般来说在我们应用中产生不同类型的数据,都可以设置不同的主题。

一个主题一般会有多个消息的订阅者,当生产者发布消息到某个主题时,订阅了这个主题的消费者都可以接收到生产者写入的新消息。

Kafka 为每个主题维护了分布式的分区(Partition)日志文件,每个 Partition 在 Kafka 存储层面是 Append Log。

任何发布到此 Partition 的消息都会被追加到 Log 文件的尾部,在分区中的每条消息都会按照时间顺序分配到一个单调递增的顺序编号,也就是我们的 Offset。Offset 是一个 Long 型的数字。

我们通过这个 Offset 可以确定一条在该 Partition 下的唯一消息。在 Partition 下面是保证了有序性,但是在 Topic 下面没有保证有序性。

webp

在上图中我们的生产者会决定发送到哪个 Partition:

如果没有 Key 值则进行轮询发送。

如果有 Key 值,对 Key 值进行 Hash,然后对分区数量取余,保证了同一个 Key 值的会被路由到同一个分区;如果想队列的强顺序一致性,可以让所有的消息都设置为同一个 Key。

消费模型

消息由生产者发送到 Kafka 集群后,会被消费者消费。一般来说我们的消费模型有两种:

推送模型(Push)

拉取模型(Pull)

基于推送模型的消息系统,由消息代理记录消费状态。消息代理将消息推送到消费者后,标记这条消息为已经被消费,但是这种方式无法很好地保证消费的处理语义。

比如当我们已经把消息发送给消费者之后,由于消费进程挂掉或者由于网络原因没有收到这条消息,如果我们在消费代理将其标记为已消费,这个消息就永久丢失了。

如果我们利用生产者收到消息后回复这种方法,消息代理需要记录消费状态,这种不可取。

如果采用 Push,消息消费的速率就完全由消费代理控制,一旦消费者发生阻塞,就会出现问题。

Kafka 采取拉取模型(Poll),由自己控制消费速度,以及消费的进度,消费者可以按照任意的偏移量进行消费。

比如消费者可以消费已经消费过的消息进行重新处理,或者消费最近的消息等等。

网络模型

Kafka Client:单线程 Selector

webp

单线程模式适用于并发链接数小,逻辑简单,数据量小的情况。在 Kafka 中,Consumer 和 Producer 都是使用的上面的单线程模式。

这种模式不适合 Kafka 的服务端,在服务端中请求处理过程比较复杂,会造成线程阻塞,一旦出现后续请求就会无法处理,会造成大量请求超时,引起雪崩。而在服务器中应该充分利用多线程来处理执行逻辑。

Kafka Server:多线程 Selector

webp

在 Kafka 服务端采用的是多线程的 Selector 模型,Acceptor 运行在一个单独的线程中,对于读取操作的线程池中的线程都会在 Selector 注册 Read 事件,负责服务端读取请求的逻辑。

成功读取后,将请求放入 Message Queue共享队列中。然后在写线程池中,取出这个请求,对其进行逻辑处理。

这样,即使某个请求线程阻塞了,还有后续的线程从消息队列中获取请求并进行处理,在写线程中处理完逻辑处理,由于注册了 OP_WIRTE 事件,所以还需要对其发送响应。

高可靠分布式存储模型

在 Kafka 中保证高可靠模型依靠的是副本机制,有了副本机制之后,就算机器宕机也不会发生数据丢失。

高性能的日志存储

Kafka 一个 Topic 下面的所有消息都是以 Partition 的方式分布式的存储在多个节点上。

同时在 Kafka 的机器上,每个 Partition 其实都会对应一个日志目录,在目录下面会对应多个日志分段(LogSegment)。

LogSegment 文件由两部分组成,分别为“.index”文件和“.log”文件,分别表示为 Segment 索引文件和数据文件。

这两个文件的命令规则为:Partition 全局的第一个 Segment 从 0 开始,后续每个 Segment 文件名为上一个 Segment 文件最后一条消息的 Offset 值,数值大小为 64 位,20 位数字字符长度,没有数字用 0 填充。

如下,假设有 1000 条消息,每个 LogSegment 大小为 100,下面展现了 900-1000 的索引和 Log:

webp



作者:java菜
链接:https://www.jianshu.com/p/d7f6c2f01cd8


点击查看更多内容
TA 点赞

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

评论

作者其他优质文章

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

100积分直接送

付费专栏免费学

大额优惠券免费领

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

举报

0/150
提交
取消