🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # partition是干嘛的 是做数据分组策略的 # partition分组策略 hashpartition默认实现 期望数据平均分配到多个partition上 # 设置副本数 每一个partition我们都可以为他设置一个副本数,解决某个borker挂掉的数据丢失问题 # 设置了多个副本,生产数据的时候,发给那个partition? 会从多个副本中选出一个leader,由leader接收,并由leader提供数据消耗 由leader提供完整的数据同步到下面的slave 消费数据的时候也找leader leader提供数据写入和数据读取服务 # 图解 下图的topic创建了4个分片,每个分片有2个副本 ![](https://box.kancloud.cn/b289647213548a9fff39f2b9c52cf3ee_774x499.png) 1. 连接broker-list中任意一台broker服务器 ~~~ metadata.broker.list ~~~ 2. 发送数据时,需要知道topic对应的partition个数及leader所在节点 解决:由borker提供,每一个broker都能提供一份元数据信息,包含哪些broker是存活,那个topic有多少分片,那个分片是leader 3. 数据生产 数据发送到那个分片的leader是由producer的代码指定的 轮询,随机,hash等等策略.默认hashpartition 4. 数据通过socket连接,直接发送到partition所在的broker # 自定义分区 ## 过时API 义一个类实现Partitioner接口,重写里面的方法 ~~~ import java.util.Map; import kafka.producer.Partitioner; public class CustomPartitioner implements Partitioner { public CustomPartitioner() { super(); } @Override public int partition(Object key, int numPartitions) { // 控制分区 return 0; } } ~~~ 然后我们生产数据的时候指定这个分区策略 ~~~ Properties props = new Properties(); /* * 可选配置,如果不配置,则使用默认的partitioner partitioner.class * 默认值:kafka.producer.DefaultPartitioner * 用来把消息分到各个partition中,默认行为是对key进行hash。 */ props.put("partitioner.class", "com.jdxia.kafka.MyLogPartitioner"); //props.put("partitioner.class", "kafka.producer.DefaultPartitioner"); ~~~ ## 新API ~~~ import org.apache.kafka.clients.producer.Partitioner; import org.apache.kafka.common.Cluster; import java.util.Map; public class CustomPartitioner implements Partitioner { @Override public int partition(String s, Object o, byte[] bytes, Object o1, byte[] bytes1, Cluster cluster) { //控制分区 return 0; } @Override public void close() { } @Override public void configure(Map<String, ?> map) { } } ~~~ # Consumer与topic关系 本质上kafka只支持Topic; * 每个group中可以有多个consumer,每个consumer属于一个consumer group; 通常情况下,一个group中会包含多个consumer,这样不仅可以提高topic中消息的并发消费能力,而且还能提高"故障容错"性,如果group中的某个consumer失效那么其消费的partitions将会有其他consumer自动接管。 * 对于Topic中的一条特定的消息,只会被订阅此Topic的每个group中的其中一个consumer消费,此消息不会发送给一个group的多个consumer; 那么一个group中所有的consumer将会交错的消费整个Topic,每个group中consumer消息消费互相独立,我们可以认为一个group是一个"订阅"者。 * 在kafka中,一个partition中的消息只会被group中的一个consumer消费(**同一时刻**); 一个Topic中的每个partions,只会被一个"订阅者"中的一个consumer消费,不过一个consumer可以同时消费多个partitions中的消息。 * kafka的设计原理决定,对于一个topic,同一个group中不能有多于partitions个数的consumer同时消费,否则将意味着某些consumer将无法得到消息。 **kafka只能保证一个partition中的消息被某个consumer消费时是顺序的;事实上,从Topic角度来说,当有多个partitions时,消息仍不是全局有序的** # kafka消息的分发 **Producer客户端负责消息的分发** * kafka集群中的任何一个broker都可以向producer提供metadata信息,这些metadata中包含"集群中存活的servers列表"/"partitions leader列表"等信息; * 当producer获取到metadata信息之后, producer将会和Topic下所有partition leader保持socket连接; * 消息由producer直接通过socket发送到broker,中间不会经过任何"路由层",事实上,消息被路由到哪个partition上由producer客户端决定; 比如可以采用"random""key-hash""轮询"等,如果一个topic中有多个partitions,那么在producer端实现"消息均衡分发"是必要的。 * 在producer端的配置文件中,开发者可以指定partition路由的方式。 Producer消息发送的应答机制 设置发送数据是否需要服务端的反馈,有三个值0,1,-1 0: producer不会等待broker发送ack 1: 当leader接收到消息之后发送ack -1: 当所有的follower都同步消息成功后发送ack ~~~ request.required.acks=0 ~~~ # Consumer的负载均衡 当一个group中,有consumer加入或者离开时,会触发partitions均衡.均衡的最终目的,是提升topic的并发消费能力,步骤如下: 1. 假如topic1,具有如下partitions: P0,P1,P2,P3 2. 加入group中,有如下consumer: C1,C2 3. 首先根据partition索引号对partitions排序: P0,P1,P2,P3 4. 根据consumer.id排序: C0,C1 5. 计算倍数: M = [P0,P1,P2,P3].size / [C0,C1].size,本例值M=2(向上取整) 6. 然后依次分配partitions: `C0 = [P0,P1]`,`C1=[P2,P3]`,即`Ci = [P(i * M),P((i + 1) * M -1)]` # kafka文件存储机制 ## Kafka文件存储基本结构 * 在Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1,和数组索引一样。 * 每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中。**但每个段segment file消息数量不一定相等**,这种特性方便old segment file快速被删除。默认保留7天的数据。 ![](https://box.kancloud.cn/b8db8fc02c6e0e0b578eb9170966da49_495x240.png) * 每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。(什么时候创建,什么时候删除) ![](https://box.kancloud.cn/613a40aaf7cf1d2848deb294ece2e857_533x270.png) ## Kafka Partition Segment * Segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀".index"和“.log”分别表示为segment索引文件、数据文件。 ![](https://box.kancloud.cn/52fcc7b7d7205d4228de1c0dddc4eee1_401x265.png) * Segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。 * 索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。 ![](https://box.kancloud.cn/56dc446ef40a3abf4c0844ffaad3044e_513x304.png) 3, 497:当前log文件中的第几条信息,存放在磁盘上的那个地方 上述图中索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。 其中以索引文件中元数据3, 497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message)、以及该消息的物理偏移地址为497。 * segment data file由许多message组成,物理地址如下 | 关键字 | 解释说明 | --- | --- | | 8 byte offset | 在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message | 4 byte message size| message大小 | 4 byte CRC32| 用crc32校验message | 1 byte “magic"| 表示本次发布Kafka服务程序协议版本号 | 1 byte “attributes"| 表示为独立版本、或标识压缩类型、或编码类型。 | 4 byte key length | 表示key的长度,当key为-1时,K byte key字段不填 | K byte key | 可选 | value bytes payload | 表示实际消息数据。 ## kafka查找message 读取offset=368776的message,需要通过下面2个步骤查找 ![](https://box.kancloud.cn/1df52471aef03e2e0c5d3e2b357d5072_401x273.png) ### 查找segment file 00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0 00000000000000368769.index的消息量起始偏移量为368770 = 368769 + 1 00000000000000737337.index的起始偏移量为737338=737337 + 1 其他后续文件依次类推。 以起始偏移量命名并排序这些文件,只要根据offset **二分查找**文件列表,就可以快速定位到具体文件。当offset=368776时定位到00000000000000368769.index和对应log文件。 ### 通过segment file查找message 当offset=368776时,依次定位到00000000000000368769.index的元数据物理位置和00000000000000368769.log的物理偏移地址 然后再通过00000000000000368769.log顺序查找直到offset=368776为止