《深入理解kafka》读书笔记
一,主题的管理
主题是消息的归类,分区是消息的第二次归类,每个分区可以有一个至多个副本,每个副本对应一个日志文件,每个日志文件对应一至多日志分段,每个日志分段细分为:索引文件,日志存储文件,快照文件。
1.创建主题:当向broker发送未创建的topic的时候,如果broker设置了auto.create.topics.enable=ture。则broker会自动创建一个对应的分区=1,副本因子=1的topic。不推荐自动创建,因为这样会增加维护的难度,更加推荐的方式是:通过kafka-tipic.sh脚本来创建主题。
bin/kafka-topics.sh --zookeeper localhost:2181/kafka --create --topic topic-create --partitions 4 --replication-factor 2
创建一个分区数为4,副本因子为2的主题。主题名为“topic-create"
在默认的目录结构下,/tmp/kafka-logs/可以看见四个”topic-create"文件夹,这就是对应的四个分区,(注意,分区和主题,都是逻辑概念,在物理上不存在的,所以这个文件夹,不能明确的当成分区!)在集群里面,这里应该是分布了一共八个,因为2*4=8,副本,一个副本对应一个日志,所以一共八个,命名形式是partition是从0开始的标号。
这里就可以理解,分区和主题是逻辑概念了,它是提供给用户理解的,而实际是实现就依靠副本日志,而且同一个分区的副本日志是不能放在同一个broker的,分开存放才能体现日志冗余,提供可靠性。
我们最老实的方法,就是在各个broker的日志目录中,查看副本日志的分布情况,但是我们可以直接从zk的/brokers/topics/节点下查看这个对应的节点信息,里面存放了各个分区副本的存放情况,
2.分区副本的分配:哪个broker创建哪些分区的副本?尽量要平衡,这里首先有两个大类的分配,指定方案(这里当然就不描述了)与默认方案,默认方案就是创建脚本sh中的内置方案,该方案里面又分是否使用机架信息。(在创建broker的时候,未配置broker.rack参数,就是未使用机架信息)
未指定机架信息:这个策略实现在kafka.admin.AdminUtils.scala中的assignReplicaseToBrokersRackUnaware()方法中,大概思路就是给这个分区规定的副本,分配到存在的broker中,返回这个分区存在的broker的ID列表,然后再分配下一个分区。这里我们只需要关注,一个分区的副本如何分配到各个broker中即可,随机产生两个数,下一个数递增的步长和起始点位置,带入公式计算出第一个broker的下标,然后使用replicaIndex计算下一个点的坐标,(传入的参数和前一个点有关系),就这样分配出了所有的broker。
机架信息(就是对broker进行了分组,比如三个机架,9个broker,那么一个机架就3个broker),它的分配大概差不多,只不过多了几个参考的参数。
3.查看主题:我们使用了kafka-topics.sh脚本的create命令,还有四个:list,describe,alter,delete,前两个用来查看topic,alter用来对主题进行修改,而目前kafka只支持增加分区数,不支持减少分区数,算法可以实现,但是过于复杂,考虑因素太多,增加了代码的量和阅读性,所以不值得实现。alter已经过时了推荐使用下面的配置管理脚本来实现。
4.配置管理:kafka-configs.sh专门用来对配置进行操作的。动态修改,使用entity-type参数来指定操作配置的类型,使用entity-name参数来指定操作配置的名称。
二,初识KafkaAdminClient
使用api来管理主题,实现集中管理,监控,运维,警告一体化平台。
基本使用:内部方法实现了创建主题,删除主题,列出所有可用的主题,查看主题的信息,查询配置的信息,修改配置信息,增加分区等功能方法。
三,分区的管理
1.优先副本的选举:kafka分区使用多副本的机制来提升可靠性,副本从使用主从模式leader-follower,独写分离,kafka规定,同一个分区的副本是不能存在于同一个broker上的,但leader锁在broker宕机了,那么这个分区就失效了,需要从剩下的follower中选择一个新的Leader提供服务,那么重新选择一个leader就会出现负载不均衡的问题,因为本来broker已经有一个Leader了,它现在又获得一个leader,那么就持有两个leader业务,如何解决呢?推出了优先副本选举概念
所谓优先副本,就是每个broker中记录了节点中的分区副本id,第一个副本id就推荐为leader,报个broker都有一个如此的优先副本就可以实现负载均衡(这里的均衡还和业务的处理时间,pc的性能等等因素有关系,不是绝对的负载均衡),默认情况下,kafka会扫描broker中的副本分布,计算是否平衡,如果超过了默认10%的不平衡性,那么就会重新进行分配,重新分配的过程会阻塞,性能会下降,所有推荐不使用自动检测,而是手动进行监控然后手动平衡。
2.分区重分配:产生的场景,当一个broker有计划的下线,或者新增加broker集群扩容,为了集群的负载均衡,需要将分区,重新分配到各个broker上面,kafka-reassign-partitions.sh脚本执行重新分配的工作。有脚本指定方案,然后在broker添加新的副本,并且从leader副本复制数据到达新的副本,这个时候完全依靠网络传输数据,是很消耗时间的。最后删除旧的副本。
3.复制限流:通过对复制数据传输的限流,来避免影响集群正常工作。
一,文件目录布局
一个主题分为多个分区,分区又多个副本实现,副本由日志实现,避免日志太大,分成了日志分段,日志分段由日志文件和2个索引文件组成。我们的一个消息发送到分区,其实在对应到一个日志分段中存放。而且消息的加入是顺序写入的,也就是最后一个日志分段(logSegment)才能写入数据,最后一个称为“activeSegment"活跃分段,随着发展,最后一个满了,又会产生新的日志分段,为了消息检索,每个日志分段都有两个索引问题:.index偏移量索引,.timeindex时间戳索引。偏移量是一个64位的长整型数,logSegment的名字就是本分段的第一个消息的偏移量来命名的,使用20位来命名,那么显然第一个logSegment的名字就是20个0,一个消息就+1,通过比较两个日志分段还可以确定一个日志存放了多少个消息。
二,日志格式的演变
对于一个消息的存放格式,也就是此处的日志格式,相当重要,存在冗余字段,那么就增大网络的开销,磁盘的开销,如果设计的太小,会影响很多功能的实现,比如切分策略,消息审计等等。kafka经历了三次版本,这里面有什么门道呢?
1.vo格式:首先日志头部(log_overhead:offset+message size ;)+record一起来描述一条消息,头部12b,然后消息集存在一个或者多个消息,它是存储,传输,压缩的基本单元:Message set。这里有三个概念,分清楚。一个消息:crc32(效验值),magic(消息版本号),attributes(消息的属性,压缩类型等),key length,key,value length,value;那么最小的消息(空内容),长度也是14B,小于这个长度,消息就收损伤了。
2.v1版本:在vo版本的基础上,增加了timestamp字段,表示消息的时间戳,占8b,magic升级为1,最小消息长度变成了22字节。
3.消息压缩:压缩率越小越好,但是消息本身就很小,压缩效果不大,所以把消息放在一起进行压缩,效果好一点。默认是producer保留生产者使用的压缩方式,还有另外三种压缩方式,将消息集压缩后,作为一个消息的value进行存放,而这个外层消息的key是空的,内层每个消息都是有offset的,生产者发出的消息offset都是0开始的,到了broker分区再重新赋值,
4.变长字段:数值越小,占用的长度越小,redis里面也使用了,同一个数据的字节里面,每个字节的首位称为msb位,除最后一个都设置为1,最后一个设置为0,那么当读到0的时候,说明是一个数据存放的最后一个字节(分隔符),
5.v2版本:在该版本消息集称为Record Batch,大量采用变长字段,
三,日志索引
偏移量索引和Mysql中的主键索引差不多,都是从偏移量找到物理地址这样,时间戳是根据日期换算出来的一个长整数表示一个时刻。由于偏移量和时间戳都是单调递增的,所以可以使用二分查找快速获得。
1.偏移量索引:结构:偏移量4字节:物理地址4字节;
2.时间戳索引:时间戳占8字节,地址占4字节。
四,日志清理
1.日志删除:删除对象是日志分段,周期线的检测不符合条件的日志分段,默认是五分钟,
2.日志压缩
五,磁盘存储
kafka使用末尾追加的方法写入消息,那么在磁盘中是顺序写的操作,避免磁指针到处乱跑,所以吞吐量是很高的,
1.页缓存:页缓存是操作系统实现的一种主要的磁盘缓存,因为磁盘速度太慢,而内存速度快得多。当进程访问一个数据页的时候,os首先去缓存中查看是否具有这个页缓存,如果没有,从磁盘中加载到缓存中,再返回给进程,如果要修改这个页的数据,还是会先从缓存中查找这个页,没有页是加载到缓存中,对缓存中的数据页进行修改,这个时候页就变成了脏页(缓存中的和磁盘中的数据不一致),为了保持一致性,自然要把数据重新写入磁盘,但是os没有马上写入,而是等待脏页到达一定量再一次性写入,那么这个过程种,数据不会发生错误吗(不会,因为首先是从缓存中读取数据的,缓存中的数据还是一手的)。
但是为了提高速度,进程自己又缓存了一份所需要的数据,所以同一个数据被缓存了两次(页缓存+进程缓存),比如jvm中,对象的内存实际占用的空间要大得多。而直接使用页缓存,效率更高,这样磁盘的数据直接到达内存,不会产生更多的开销,减少了维护进程缓存于页缓存一致性的代码,我们的数据直接写入磁盘,其实操作系统是写道内存缓存中的,效率更高,而且把任务交给了操作系统去处理,减少了kafka的任务。
2.磁盘I/O流程
3.零拷贝:数据直接从用户到磁盘或者网卡设备中,没有经过内核缓存,或者直接从磁盘到网卡,减少了内核态用户态的切换,实现依赖于sendfile()方法实现,