Kafka 源码解析:延时任务调度策略

Kafka 一些组件的命名很是有趣,比如炼狱(purgatory)、死神(reaper)等,在日常开发中也建议大家在类和方法命名上能够以一些能够表达类或方法意图的人或事物的名词进行命名,让项目显得更加的生动。今天我们要分析的组件就是以 purgatory 命名的 DelayedOperationPurgatory,DelayedOperationPurgatory 是一个相对独立的组件,我们可以将其抽取出来用于自己的日常项目中,DelayedOperationPurgatory 主要用于管理延时任务,底层依赖于分层时间轮算法实现。

说到延时任务调度,对于 java 开发者来说,日常用到比较多的可能是 JDK 自带的 Timer、ScheduledThreadPoolExecutor 和 DelayQueue 等,但是对于 Kafka 这类需要频繁执行复杂延时任务的分布式系统来说,这些组件在性能上还稍显不足,所以 Kafka 自定义了分层时间轮算法,提供了 O(m) 时间复杂度(m 为时间轮层级数)的任务插入性能和 O(1) 时间复杂度的任务删除性能,要优于 JDK 自带的基于堆实现的 O(log(n)) 时间复杂度的延时任务调度组件。

阅读全文

Kafka 源码解析:日志数据存储机制

日志数据(亦称消息数据)的存储机制在 Kafka 整个设计与实现中既基础又核心。Kafka 采用本地文件系统对日志数据进行存储,并允许为一个 broker 节点设置多个 log 文件目录,每个 log 目录下存储的数据又按照 topic 分区进行划分,其中包含了一个 topic 分区名下消息数据对应的多组日志和索引文件。

Kafka 定义了 LogSegment 类和 Log 类对日志和索引数据进行管理,并定义了 LogManager 类管理一个 broker 节点下的所有 Log 对象,同时基于 Log 对象提供了对日志数据的加载、创建、删除,以及查询等功能,同时还维护了多个定时任务对日志数据执行清理、删除、刷盘,以及记录 HW 位置等操作,并提供了对 key 重复的消息数据执行压缩的机制。

阅读全文

Kafka 源码解析:网络交互模型

由上一篇分析可知,在 broker 节点启动过程中会创建一个 SocketServer 类型的对象,并调用其 SocketServer#startup 方法执行组件的启动过程。SocketServer 是 Kafka 对外提供网络服务的核心实现类,在 Kafka 运行过程中用于接收来自客户端和其它 broker 节点的网络请求。考虑到性能上的需求,SocketServer 采用了 Reactor 模式,并基于 java NIO 实现。

参考如下示意图,Kafka 为 broker 所在宿主机的每一张网卡创建并绑定了一个 Acceptor 组件,用于接收并处理所有的连接请求;每个 Acceptor 组件维护多个 Processor 线程,其中每个 Processor 拥有专属的 Selector,用于从连接中读取请求和写回响应;每个 Acceptor 组件同时维护多个 Handler 线程,用于处理请求并生成响应传递给 Processor,而 Handler 与 Processor 之间通过请求队列进行通信。

阅读全文

Kafka 源码解析:Broker 节点的启动与关闭

从本篇开始我们分析 Kafka 服务端组件的实现。Kafka 集群由多个 broker 节点构成,每个节点上都运行着一个 Kafka 实例,这些实例之间基于 ZK 来发现彼此,并由集群控制器 KafkaController 统筹协调运行,彼此之间基于 socket 连接进行通信。本篇我们主要分析单个 broker 节点上 Kafka 实例的启动和关闭过程,关于集群整体的协调运行机制将在后面按照组件逐一进行分析。

Kafka 提供了 kafka-server-start.sh 脚本来简化服务的启动操作,脚本中通过调用 kafka.Kafka 类来启动 Kafka 服务,这也是 Kafka 整个服务端的驱动类。在 Kafka 服务启动过程中,首先会解析并封装命令行传递的参数,然后创建负责 Kafka 服务启动和关闭操作的 KafkaServerStartable 类对象,并调用 KafkaServerStartable#startup 方法启动服务。

阅读全文

Kafka 源码解析:消费者运行机制

与上一篇介绍的 KafkaProducer 一样,Kafka 消费者 KafkaConsumer 同样是 Kafka 与开发者交互的媒介之一,负责从 Kafka 集群拉取消息给应用程序消费,并提交已经消费完成的 offset 值。此外,考虑到消费者上下线、topic 分区数目变更等情况,KafkaConsumer 还需要负责与服务端交互执行分区再分配操作,以保证消费者能够更加均衡的消费 topic 分区,从而提升消费的性能。

Kafka 定义了 group 的概念,将多个消费者实例组织成为一个 group,以丰富 Kafka 的应用场景。一个 group 名下可以包含任意数量的消费者实例,并从这些消费者中选择一个消费者担任 group 中的 Leader 消费者角色,负责管理 group 和其它 Follower 角色消费者的状态。当有消费者加入或离开当前 group 时,Group Leader 会依据集群确定的分区分配策略,为 group 名下所有消费者重新分配分区,以保证消息消费的均衡性。

阅读全文

Kafka 源码解析:生产者运行机制

Kafka 生产者 KafkaProducer 是 Kafka 与开发者交互的媒介之一,肩负接收用户自定义消息(这里的消息指代往 Kafka 发送的各类数据),并投递给目标 topic 分区的职责。在设计上为了提升消息吞吐量,考量降低与服务端交互的压力等,每次发送消息的请求并非是直接与 Kafka 集群进行交互,而是一个异步的过程。

当调用 KafkaProducer#send 方法发送消息时,实际上只是将消息缓存到了本地的消息收集器中,Kafka 定义了一个 RecordAccumulator 收集器用于收集用户提交的消息数据,同时又在后台维护了一个 Sender 线程,以异步的方式不断将收集器中缓存的消息定期定量地投递给 Kafka 集群。

阅读全文

Kafka 源码解析:架构与核心概念

Apache Kafka 作为分布式消息引擎系统,已经被各大互联网公司广泛引入到生产环境中,主要用于消息的发布订阅、日志数据的采集等,以充当一个公司的数据总线角色。因其具备优良的性能和近乎实时的消息投递能力,并且能够保证消息的顺序性、持久性和完整性(不丢消息),同时引入 topic、partition,以及 group 等精妙的设计理念,所以自开源以来社区一直非常活跃。大厂在引入 Kafka 时,一般会结合公司自身的业务特点在具体落地形式上有所区别(包括在 Kafka 原有基础上扩展和优化,或沿用 Kafka 的设计思想重新设计实现等),但是在思想上仍然是相通的,所以了解 Kafka 的核心设计与实现可以对这些系统举一反三。

阅读全文

基于 CAS 机制的 ConcurrentHashMap 实现内幕

曾经写过一篇《基于锁分段机制的 ConcurrentHashMap 实现内幕》的文章,介绍了在 JDK 1.7 之前 ConcurrentHashMap 的实现机制。文章的结尾我们提及到在 JDK 1.8 之后,ConcurrentHashMap 在实现上抛弃了锁分段机制,转而采用 CAS(Compare-And-Swap) 策略,并和 HashMap 一样引入了红黑树的支持。本文我们将基于 JDK 1.8 源码,分析基于 CAS 机制的 ConcurrentHashMap 实现。

阅读全文

JMockit:单元测试利器

单元测试(UT: Unit Test)是保证服务质量的基础。在实际项目的 UT 开发中,我们通常需要执行第三方服务调用、连接数据库等操作,为了让 UT 能够正常运行起来,我们需要执行大量的环境准备工作,这些工作有时比 UT 本身还要费时费力很多,而 mock 机制则能够帮助我们绕开这些必要但不一定要真正需要去做的事情,隔离 UT 与服务的依赖,模拟我们期望的行为和数据。

阅读全文

CGLib:The Missing Guide

CGLib(Code Generation Library) 是一个强大、高效,以及高质量的字节码生成库,能够在运行时动态生成字节码,从而实现一些比较极客的功能。CGLib 被许多开源软件采用,我们在写一些基础库时也很青睐,但是官方给到的文档比较简单,所以本文参考 CGLib: The missing manual,并结合自己的使用经验,总结了一个中文版本。

阅读全文