1. kafka分区策略
kafka的分区策略决定了producer生产者产生的一条消息最后会写入到topic的哪一个分区中
- 1、指定具体的分区号
//1、给定具体的分区号,数据就会写入到指定的分区中
producer.send(new ProducerRecord<String, String>("test", 0,Integer.toString(i), "hello-kafka-"+i));
- 2、不给定具体的分区号,给定key的值(key不断变化)
//2、不给定具体的分区号,给定一个key值, 这里使用key的 hashcode%分区数=分区号
producer.send(new ProducerRecord<String, String>("test", Integer.toString(i), "hello-kafka-"+i));
- 3、不给定具体的分区号,也不给对应的key
//3、不给定具体的分区号,也不给定对应的key ,这个它会进行轮训的方式把数据写入到不同分区中
producer.send(new ProducerRecord<String, String>("test", "hello-kafka-"+i));
-
4、自定义分区
- 定义一个类实现接口Partitioner
package com.kaikeba.partitioner; import org.apache.kafka.clients.producer.Partitioner; import org.apache.kafka.common.Cluster; import java.util.Map; //todo:需求:自定义kafka的分区函数 public class MyPartitioner implements Partitioner{ /** * 通过这个方法来实现消息要去哪一个分区中 * @param topic * @param key * @param bytes * @param value * @param bytes1 * @param cluster * @return */ public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) { //获取topic分区数 int partitions = cluster.partitionsForTopic(topic).size(); //key.hashCode()可能会出现负数 -1 -2 0 1 2 //Math.abs 取绝对值 return Math.abs(key.hashCode()% partitions); } public void close() { } public void configure(Map<String, ?> map) { } }
- 配置自定义分区类
//在Properties对象中添加自定义分区类 props.put("partitioner.class","com.kaikeba.partitioner.MyPartitioner");
2. kafka的文件存储机制
2.1 概述
同一个topic下有多个不同的partition,每个partition为一个目录,partition命名的规则是topic的名称加上一个序号,序号从0开始。
每一个partition目录下的文件被平均切割成大小相等(默认一个文件是1G,可以手动去设置)的数据文件,每一个数据文件都被称为一个段(segment file),但每个段消息数量不一定相等,这种特性能够使得老的segment可以被快速清除。默认保留7天的数据。
每次满1G后,在写入到一个新的文件中。
另外每个partition只需要支持顺序读写就可以。如上图所示:
首先00000000000000000000.log是最早产生的文件,该文件达到1G后又产生了新的00000000000002025849.log文件,新的数据会写入到这个新的文件里面。
这个文件到达1G后,数据又会写入到下一个文件中。也就是说它只会往文件的末尾追加数据,这就是顺序写的过程,生产者只会对每一个partition做数据的追加(写操作)。
2.2 数据消费问题讨论
问题:如何保证消息消费的有序性呢?比如说生产者生产了0到100个商品,那么消费者在消费的时候按照0到100这个从小到大的顺序消费?
*** 那么kafka如何保证这种有序性呢?***
难度就在于,生产者生产出0到100这100条数据之后,通过一定的分组策略存储到broker的partition中的时候,
比如0到10这10条消息被存到了这个partition中,10到20这10条消息被存到了那个partition中,这样的话,消息在分组存到partition中的时候就已经被分组策略搞得无序了。
那么能否做到消费者在消费消息的时候全局有序呢?
遇到这个问题,我们可以回答,在大多数情况下是做不到全局有序的。但在某些情况下是可以做到的。比如我的partition只有一个,这种情况下是可以全局有序的。
那么可能有人又要问了,只有一个partition的话,哪里来的分布式呢?哪里来的负载均衡呢?
所以说,全局有序是一个伪命题!全局有序根本没有办法在kafka要实现的大数据的场景来做到。但是我们只能保证当前这个partition内部消息消费的有序性。
结论:一个partition中的数据是有序的吗?回答:间隔有序,不连续。
针对一个topic里面的数据,只能做到partition内部有序,不能做到全局有序。特别是加入消费者的场景后,如何保证消费者的消费的消息的全局有序性,
这是一个伪命题,只有在一种情况下才能保证消费的消息的全局有序性,那就是只有一个partition。
2.3 Segment文件
- Segment file是什么
生产者生产的消息按照一定的分区策略被发送到topic中partition中,partition在磁盘上就是一个目录,该目录名是topic的名称加上一个序号,在这个partition目录下,有两类文件,一类是以log为后缀的文件,一类是以index为后缀的文件,每一个log文件和一个index文件相对应,这一对文件就是一个segment file,也就是一个段。
其中的log文件就是数据文件,里面存放的就是消息,而index文件是索引文件,索引文件记录了元数据信息。log文件达到1个G后滚动重新生成新的log文件
- Segment文件特点
segment文件命名的规则:partition全局的第一个segment从0(20个0)开始,后续的每一个segment文件名是上一个segment文件中最后一条消息的offset值。
那么这样命令有什么好处呢?
假如我们有一个消费者已经消费到了368776(offset值为368776),那么现在我们要继续消费的话,怎么做呢?
看下图,分2个步骤;
第1步是从所有文件log文件的的文件名中找到对应的log文件,第368776条数据位于上图中的“00000000000000368769.log”这个文件中,
这一步涉及到一个常用的算法叫做“二分查找法”(假如我现在给你一个offset值让你去找,你首先是将所有的log的文件名进行排序,然后通过二分查找法进行查找,
很快就能定位到某一个文件,紧接着拿着这个offset值到其索引文件中找这条数据究竟存在哪里);
第2步是到index文件中去找第368776条数据所在的位置。
索引文件(index文件)中存储这大量的元数据,而数据文件(log文件)中存储这大量的消息。
索引文件(index文件)中的元数据指向对应的数据文件(log文件)中消息的物理偏移地址。
2.4 kafka如何快速查询数据
上图的左半部分是索引文件,里面存储的是一对一对的key-value,其中key是消息在数据文件(对应的log文件)中的编号,比如“1,3,6,8……”,
分别表示在log文件中的第1条消息、第3条消息、第6条消息、第8条消息……,那么为什么在index文件中这些编号不是连续的呢?
这是因为index文件中并没有为数据文件中的每条消息都建立索引,而是采用了稀疏存储的方式,每隔一定字节的数据建立一条索引。
这样避免了索引文件占用过多的空间,从而可以将索引文件保留在内存中。
但缺点是没有建立索引的Message也不能一次定位到其在数据文件的位置,从而需要做一次顺序扫描,但是这次顺序扫描的范围就很小了。
其中以索引文件中元数据8,1686为例,其中8代表在右边log数据文件中从上到下第8个消息(在全局partiton表示第368777个消息),其中1686表示该消息的物理偏移地址(位置)为1686。
要是读取offset=368777的消息,从00000000000000368769.log文件中的1325的位置进行读取,那么怎么知道何时读完本条消息,否则就读到下一条消息的内容了?
参数说明:
关键字 | 解释说明 |
---|---|
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 | 表示实际消息数据。 |
这个就需要涉及到消息的物理结构了,消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes)等等字段,可以确定一条消息的大小,即读取到哪里截止。
2.5 kafka高效文件存储设计特点
- Kafka把topic中一个parition大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完文件,减少磁盘占用。
- 通过索引信息可以快速定位message
- 通过index元数据全部映射到memory,可以避免segment file的IO磁盘操作。
- 通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。
3. 为什么Kafka速度那么快
Kafka是大数据领域无处不在的消息中间件,目前广泛使用在企业内部的实时数据管道,并帮助企业构建自己的流计算应用程序。
Kafka虽然是基于磁盘做的数据存储,但却具有高性能、高吞吐、低延时的特点,其吞吐量动辄几万、几十上百万,这其中的原由值得我们一探究竟。
3.1 顺序读写
- 磁盘顺序读写性能要高于内存的随机读写
众所周知Kafka是将消息记录持久化到本地磁盘中的,一般人会认为磁盘读写性能差,可能会对Kafka性能如何保证提出质疑。实际上不管是内存还是磁盘,快或慢关键在于寻址的方式,磁盘分为顺序读写与随机读写,内存也一样分为顺序读写与随机读写。基于磁盘的随机读写确实很慢,但磁盘的顺序读写性能却很高,一般而言要高出磁盘随机读写三个数量级,一些情况下磁盘顺序读写性能甚至要高于内存随机读写。
磁盘的顺序读写是磁盘使用模式中最有规律的,并且操作系统也对这种模式做了大量优化,Kafka就是使用了磁盘顺序读写来提升的性能。Kafka的message是不断追加到本地磁盘文件末尾的,而不是随机的写入,这使得Kafka写入吞吐量得到了显著提升
3.2 Page Cache
为了优化读写性能,Kafka利用了操作系统本身的Page Cache,就是利用操作系统自身的内存而不是JVM空间内存。这样做的好处有:
(1)避免Object消耗:如果是使用Java堆,Java对象的内存消耗比较大,通常是所存储数据的两倍甚至更多。
(2)避免GC问题:随着JVM中数据不断增多,垃圾回收将会变得复杂与缓慢,使用系统缓存就不会存在GC问题。
3.3 零拷贝(sendfile)
-
零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数。通常是说在IO读写过程中。
Kafka利用linux操作系统的 "零拷贝(zero-copy)" 机制在消费端做的优化。
-
首先来了解下数据从文件发送到socket网络连接中的常规传输路径
比如:读取文件,再用socket发送出去 传统方式实现: 先读取、再发送,实际经过1~4四次copy。 buffer = File.read Socket.send(buffer)
- 第一步:操作系统从磁盘读取数据到内核空间(kernel space)的Page Cache缓冲区
- 第二步:应用程序读取内核缓冲区的数据copy到用户空间(user space)的缓冲区
- 第三步:应用程序将用户空间缓冲区的数据copy回内核空间到socket缓冲区
- 第四步:操作系统将数据从socket缓冲区copy到网卡,由网卡进行网络传输
-
传统方式,读取磁盘文件并进行网络发送,经过的四次数据copy是非常繁琐的。实际IO读写,需要进行IO中断,需要CPU响应中断(带来上下文切换),尽管后来引入DMA来接管CPU的中断请求,但四次copy是存在“不必要的拷贝”的。
重新思考传统IO方式,会注意到实际上并不需要第二个和第三个数据副本。应用程序除了缓存数据并将其传输回套接字缓冲区之外什么都不做。相反,数据可以直接从读缓冲区传输到套接字缓冲区。
显然,第二次和第三次数据copy 其实在这种场景下没有什么帮助反而带来开销,这也正是零拷贝出现的意义。
这种场景:是指读取磁盘文件后,不需要做其他处理,直接用网络发送出去。试想,如果读取磁盘的数据需要用程序进一步处理的话,必须要经过第二次和第三次数据copy,让应用程序在内存缓冲区处理。
此时我们会发现用户态“空空如也”。数据没有来到用户态,而是直接在核心态就进行了传输,但这样依然还是有多次复制。首先数据被读取到read buffer中,然后发到socket buffer,最后才发到网卡。虽然减少了用户态和核心态的切换,但依然存在多次数据复制。
如果可以进一步减少数据复制的次数,甚至没有数据复制是不是就会做到最快呢?
-
DMA
- DMA,全称叫Direct Memory Access,一种可让某些硬件子系统去直接访问系统主内存,而不用依赖CPU的计算机系统的功能。听着是不是很厉害,跳过CPU,直接访问主内存。传统的内存访问都需要通过CPU的调度来完成。如下图:
- DMA,则可以绕过CPU,硬件自己去直接访问系统主内存。如下图
- 回到本文中的文件传输,有了DMA后,就可以实现绝对的零拷贝了,因为网卡是直接去访问系统主内存的。如下图:
-
总结
Kafka采用顺序读写、Page Cache、零拷贝以及分区分段等这些设计,再加上在索引方面做的优化,另外Kafka数据读写也是批量的而不是单条的,使得Kafka具有了高性能、高吞吐、低延时的特点。这样Kafka提供大容量的磁盘存储也变成了一种优点 Java的NIO提供了FileChannle,它的transferTo、transferFrom方法就是Zero Copy。
4. kafka整合flume
-
1、安装flume
-
2、添加flume的配置
- vi flume-kafka.conf
#为我们的source channel sink起名 a1.sources = r1 a1.channels = c1 a1.sinks = k1 #指定我们的source数据收集策略 a1.sources.r1.type = spooldir a1.sources.r1.spoolDir = /kkb/install/flumeData/files a1.sources.r1.inputCharset = utf-8 #指定我们的source收集到的数据发送到哪个管道 a1.sources.r1.channels = c1 #指定我们的channel为memory,即表示所有的数据都装进memory当中 a1.channels.c1.type = memory a1.channels.c1.capacity = 1000 a1.channels.c1.transactionCapacity = 100 #指定我们的sink为kafka sink,并指定我们的sink从哪个channel当中读取数据 a1.sinks.k1.channel = c1 a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink a1.sinks.k1.kafka.topic = kaikeba a1.sinks.k1.kafka.bootstrap.servers = node01:9092,node02:9092,node03:9092 a1.sinks.k1.kafka.flumeBatchSize = 20 a1.sinks.k1.kafka.producer.acks = 1
-
3、创建topic
kafka-topics.sh --create --topic kaikeba --partitions 3 --replication-factor 2 --zookeeper node01:2181,node02:2181,node03:2181
-
4、启动flume
bin/flume-ng agent -n a1 -c conf -f conf/flume-kafka.conf -Dflume.root.logger=info,console
-
5、启动kafka控制台消费者,验证数据写入成功
kafka-console-consumer.sh --topic kaikeba --bootstrap-server node01:9092,node02:9092,node03:9092 --from-beginning
5. kafka监控工具安装和使用
5.1. Kafka Manager
kafkaManager它是由雅虎开源的可以监控整个kafka集群相关信息的一个工具。
(1)可以管理几个不同的集群
(2)监控集群的状态(topics, brokers, 副本分布, 分区分布)
(3)创建topic、修改topic相关配置
-
1、上传安装包
kafka-manager-1.3.0.4.zip
-
2、解压安装包
- unzip kafka-manager-1.3.0.4.zip -d /kkb/install
-
3、修改配置文件
-
进入到conf
-
vim application.conf
#修改kafka-manager.zkhosts的值,指定kafka集群地址 kafka-manager.zkhosts="node01:2181,node02:2181,node03:2181"
-
-
-
4、启动kafka-manager
- 启动zk集群,kafka集群,再使用root用户启动kafka-manager服务。
- bin/kafka-manager 默认的端口是9000,可通过 -Dhttp.port,指定端口
- -Dconfig.file=conf/application.conf指定配置文件
nohup bin/kafka-manager -Dconfig.file=conf/application.conf -Dhttp.port=8080 &
-
5、访问地址
- kafka-manager所在的主机名:8080
5.2. KafkaOffsetMonitor
该监控是基于一个jar包的形式运行,部署较为方便。只有监控功能,使用起来也较为安全
(1)消费者组列表
(2)查看topic的历史消费信息.
(3)每个topic的所有parition列表(topic,pid,offset,logSize,lag,owner)
(4)对consumer消费情况进行监控,并能列出每个consumer offset,滞后数据。
-
1、下载安装包
KafkaOffsetMonitor-assembly-0.2.0.jar
-
2、在服务器上新建一个目录kafka_moitor,把jar包上传到该目录中
-
3、在kafka_moitor目录下新建一个脚本
- vim start_kafka_web.sh
#!/bin/sh java -cp KafkaOffsetMonitor-assembly-0.2.0.jar com.quantifind.kafka.offsetapp.OffsetGetterWeb --zk node01:2181,node02:2181,node03:2181 --port 8089 --refresh 10.seconds --retain 1.days
-
4、启动脚本
nohup sh start_kafka_web.sh &
-
5、访问地址
在浏览器中即可使用ip:8089访问kafka的监控页面。
5.3. Kafka Eagle
-
1、下载Kafka Eagle安装包
- http://download.smartloli.org/
- kafka-eagle-bin-1.2.3.tar.gz
- http://download.smartloli.org/
-
2、解压
- tar -zxvf kafka-eagle-bin-1.2.3.tar.gz -C /kkb/install
- 解压之后进入到kafka-eagle-bin-1.2.3目录中
- 得到kafka-eagle-web-1.2.3-bin.tar.gz
- 然后解压 tar -zxvf kafka-eagle-web-1.2.3-bin.tar.gz
- 重命名 mv kafka-eagle-web-1.2.3 kafka-eagle-web
-
3、修改配置文件
-
进入到conf目录
-
修改system-config.properties
# 填上你的kafka集群信息 kafka.eagle.zk.cluster.alias=cluster1 cluster1.zk.list=node01:2181,node02:2181,node03:2181 # kafka eagle页面访问端口 kafka.eagle.webui.port=8048 # kafka sasl authenticate kafka.eagle.sasl.enable=false kafka.eagle.sasl.protocol=SASL_PLAINTEXT kafka.eagle.sasl.mechanism=PLAIN kafka.eagle.sasl.client=/kkb/install/kafka-eagle-bin-1.2.3/kafka-eagle-web/conf/kafka_client_jaas.conf # 添加刚刚导入的ke数据库配置,我这里使用的是mysql kafka.eagle.driver=com.mysql.jdbc.Driver kafka.eagle.url=jdbc:mysql://node03:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull kafka.eagle.username=root kafka.eagle.password=123456
-
-
-
4、配置环境变量
-
vi /etc/profile
export KE_HOME=/kkb/install/kafka-eagle-bin-1.2.3/kafka-eagle-web export PATH=$PATH:$KE_HOME/bin
-
-
5、启动kafka-eagle
- 进入到$KE_HOME/bin目录
- 执行脚本sh ke.sh start
- 进入到$KE_HOME/bin目录
-
6、访问地址
-
启动成功后在浏览器中输入
http://node01:8048/ke
就可以访问kafka eagle 了。- 用户名:admin
- password:123456
- 登录首页
-
- 仪表盘信息
- kafka集群信息
-
zookeeper集群
- topic信息
- consumer消费者信息
* zk客户端命令