kafka工作流程
生产过程
- 写入方式:producer采用推(push)的方式将消息发布到broker,每条消息都被追加(append)到分区(partition)中,属于顺序写磁盘,效率高于随机写内存,提升了kafka的吞吐量
- 分区:消息都发送到一个topic中,其本质是一个目录,而topic是由一个partition logs分区日志组成,其中每一个partition中的消息都是有序的,而消息是不断追加到partition log上,其中每一个消息都被赋予了一个唯一的offset。
- 分区的原因:
- 方便在集群中扩展,每个分区可以通过调整以适应它所在的机器,而一个topic由多个分区组成,因此可以适应不同规模的业务
- 提高并发,可以以分区为单位进行读写
- 分区的原则:
- 指定了分区则直接使用
- 未指定分区但是指定key则通过keyhash出一个分区
- 如果分区和key都没有指定分区,则会轮询选出一个分区
- *分区相关java源码:
//DefaultPartitioner类
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
if (keyBytes == null) {
int nextValue = nextValue(topic);
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
- 副本(replication):
- 同一个partition可能会有多个副本,对应 server.properties 配置中的 default.replication.factor=N。
- 一旦broker泵机,其上所有的分区的数据都将不可被读写,副本可以提高kafka数据的可靠性
- 同一个分区会有多个副本,这些副本中会有一个被选为leader,producer和consumer都只会和这一个leader交互,而其他副本作为follower从leader中复制数据
- 写入流程:
- 首先,producer会从zookeeper的 "/brokers/…/state"节点找到该partition的leader
- 然后producer会将消息发送给该leader
- leader将消息写入本地log
- follower从leader pull消息,写入本地log后发送ack给leader
- leader搜到所有isr中的副本的ack后,增加HW(high watermark,最后commit 的offset)并向producer发送ack
broker保存消息
- 存储方式
- 物理上将topic分成一个或多个partition,每个partition物理上对应一个文件夹
- 存储策略:不论消息是否被消费,kafka都会保留所有的消息,同时定期删除,一般有两种方法
- 基于时间:通过设置log.retention.hours配置
- 基于大小:通过设置log.retention.bytes配置
- kafka是通过offset读取消息的,时间复杂度为O(1),所以删除过期文件与提高kafka性能无关
- zookeeper中存储的kafka的元数据:有consumer的元数据如offset、ids等,一个admin节点存储了标记删除的topic节点,同时还储存了诸如broker中的topic及其分区等等数据(见下图:
kafka的消费过程
- kafka提供了两套consumer的API:高级consumer API和低级consumer API
- 高级API
- 优点:高级API写起来简单,不需要管理offset、分区、副本等等
- 缺点:它的优点同时就是它的缺点:高级api不能自行控制offset、不能细化控制分区、副本、zk等等
- 低级API
- 优点:能自行控制offset,自由的读取消息、自行控制分区,进行负载均衡、还降低了对zk的依赖性
- 缺点:太过复杂,全都要自行实现
- 消费者组
- 消费者以消费者组(consumer group)的方式工作的,由一个或多个消费者组成一个组
- 每个组共同消费一个topic
- 每个分区在同一时间只能被group中的某一个消费者读取,但是多个group可以共同消费这个分区
- 某个消费者读取某个分区,叫做某个消费者是某个分区的拥有者
- 通过消费者组这种方法,消费者可以通过水平扩展的方式同时读取大量的消息。
- 如果一个消费者失败了,其他group成员会自动负载均衡读取前失败的消费者读取的分区
- 消费方式
- consumer采用拉(pull)模式从broker中读取数据
- push模式很难适应消费速率不同的消费者,因为消息发送的速率是由broker来决定的,他的目标是尽可能快的传递消息,但是这样很容易照成consumer来不及处理消息,而pull模式则可以根据consumer的消费能力以适当的速率消费消息
- pull模式的不足之处在于,如果kafka没有数据,消费者会陷入循环中,一直等待数据到达。为了避免这种情况,我们在我们的拉请求中有参数,允许消费者请求在等待数据到达的“长轮询”中进行阻塞(并且可选地等待到给定的字节数,以确保大的传输大小)