logs.dor
默认:/tmp/kafka-logs
为了实现横向扩展,把不同的数存放在不同的Broker上,同时降低单台服务器的访问压力,我们把一个topic中的数据分割中多个partition。
一个partition中的消息是有序的,但是全局不一定。
为了提高分区的可靠性,kafka设计了副本机制。
创建副本命令:
./kafka-topics.sh --create --bootstrap-server 172.0.0.1:9092 --replication-factor 4 --partitions 1 --topic overrep
部分数量需要小于等于Broker的数量,否则就会报错。由于这个机制,所以不论哪个broker宕机都可以完成切换。
这些所有的副本分为两种角色,Leader、Follower。leader负责对外的读写,follower唯一任务就是从leader异步拉取数据。由于所有的读写都发生leader上,所以就没有数据一致性问题。
./kafka-topics.sh --create --bootstrap-server 192.168.8.146:9092 --replication-factor 3 --partitions 3 --topic a3part3rep
查看副本分布情况:
./kafka-topics.sh --topic a3part3rep --describe --bootstrap-server 127.0.0.1:9092
分布策略由AdminUtils.scala的assignReplicasToBrokers函数决定,规则如下:
如果有5个broker,5个分区,假设第一个分区的第一个副本放在第三个broker,那么第二个分区的第一个副本放在第四个broker,第三个分区的第一个副本放在第五个broker,依次类推。
这样的好处是,所有分区leader都可以平均分布在不同的broker,避免多个分区的leader分布在同一个broker上,降低了单个分区的读写压力。
为了防止Log不断追加导致文件过大,导致检索信息效率变低,一个Partiton又被划分多个Segment来组织数据
在磁盘上,每个Segment由一个log文件和2个index文件组成
由于一个Segment的文件里面可能存放很多消息,如果根据Offset获取消息,必须要有一种快速检索消息的机制。这个就是索引。在kafka中涉及了两种索引。
偏移量索引文件记录的是Offset和消息物理地址(在Log文件中的位置)的映射关系。时间戳索引文件记录的是时间戳和Offset的关系。
内容是二进制的文件,不能以纯文件形式查看。bin目录下有dumplog工具。
查看最后10条件Offset索引
./kafka-dump-log.sh --files
/tmp/kafka-logs/mytopic-0/00000000000000000000.index|head -n 10
根据结果查看,索引并不是连续的,这个是由于kafka使用的是稀疏索引
根据消息大小来控制的稀疏度,默认是4KB:
log.index.interval.bytes=4096
只要写入的消息超过了4KB,偏移量索引文件.index和时间戳文件索引.timeindex就会增加一天索引记录(索引项)。
时间戳索引文件存在的意义:
时间戳有两种,一种就是producer创建时间,一个是写入broker的时间。可由参数控制:
log.message.timestamp.type=CreateTime
默认是创建时间。可以调整为日志写入时间:log.message.timestamp.type=LogAppendTime
查看最早10条时间戳索引
./kafka-dump-log.sh --files /tmp/kafka-logs/mytopic-0/00000000000000000000.timeindex|head -n 10
检索Offset=10001 过程:
不使用B+Tree?
kafka是写多,查少,在写入B+Tree,大量的插入就会非常消耗性能
消息清理开关默认是开启的
log.cleaner.enable=true
kafka里面提供了两种方式,一种是直接删除delete,一种是压缩compact。默认是删除。
log.cleanup.policy=delete
日志删除是通过定时任务实现的,默认5分钟执行依次,查找需要删除的数据
log.retention.check.interval.ms=300000
log.retention.hours=168
log.retention.bytes
log.retention.bytes 指的是所有日志文件的总大小,可以对单个segment文件大小限制。
log.segment.bytes
默认是1G
压缩的不是对文件常规压缩,而是重新排序,结果数据。如:一条消息key=k1,然后对这条数据做了多次的修改,但是日志文件中会记录多条,压缩就是把中间过程去掉,直接保留结果数据。
根据开发以及实际习惯,极少出现修改修改message的情况,所以这个功能其实没啥用。
kafka早期使用zk直接选举partition的Leader,使用了zk的三个机制:
这样实现比较简单,但存在一定的弊端,zk是cp模型,在分区和副本数量较多时,所有的副本都直接进行选举的话,一旦出现某个节点的增减,就会造成较大的watch事件被触发,zk出现负载过重,不堪重负。
现在的实现方式:
不是所有的replica都参与选举,而是由其中一个broker来统一控制,这个Broker的角色叫做Controller。
如果Redis Sentinel的架构,机型故障转移的时候,必须要首先冲所有哨兵中选举一个负责做故障转移的节点一样。kafka也要先从所有Broker中选出唯一的一个Controller。
所有的broker会重试在Zookeeper中创建临时节点/controller,只有一个能创建成功(根据broker的创建时间)。
如果Controller挂掉了或者网络问题,zk上的临时节点就会消失。其他的Broker通过watch监听到Controller下线的消息后,开始选举Controller。
一个节点成为Controller之后,它就赋予了更多的责任和能力:
https://kafka.apache.org/documentation/#replication
https://kafka.apache.org/documentation/#design_replicatedlog
Controller确定以后,就可以开始做分区选主的事情。不是所有的Replica都有资格选举资格。
一个分区所有的副本,都叫做Assigned-Replicas(AR)。
这些所有的副本中,跟Leader数据保持一定程度同步的,叫做In-Sync Replicas(ISR)。
副本中同步较为滞后的副本叫做Out-Sync-Replicas(OSR)。
AR = ISR+OSR,正常情况,OSR为空。
默认情况下只有ISR集合中的副本才能有资格被选举Leader。如果ISR为空,可以这只ISR之外的副本参与选举:
unclean.leader.election.enable=false
把这个参数改成true即可,不建议使用,会造成数据丢失。
Paxos选举算法:
主要思想就是:先到先得、少数服从多数。
kafka使用的不是这些方法,而是用了自己实现的算法。由于ZAB协议可能出现多个分裂现象(节点不能互通的时候,出现多个Leader)、惊群效应(大量Watch事件被触发)。
kafka的选举算法和微软的ParificA算法。
这种算法中,默认让ISR中的一个Replica变成Leader。比如ISR是1 2 4,则优先让1成为Leader。然后后再依次考虑其后。
Leader确定之后,客户端的读写只能操作Leader节点。Follower需要向Leader同步数据。
不同的Replica的Offset是不一样的,那具体怎么同步的呢。
LEO(Log End Offset):下一条等待写入消息的Offset(最新的Offset + 1),图中的 9 8 6,使用命令:
./kafka-consumer-groups.sh --bootstrap-server 192.168.8.146:9092 --describe --group test-group
HW(High Watermark):ISR中最小的LEO。Leader会管理所有ISR中最小的LEO作为HW,目前是6。
Consumer最多只能消费到HW之前的位置(Offset 5)。也就是其他副本没有同步的消息,是不能被消费的。
如此设计原因是如果消息被成功消费了,Consumer Group的Offset会偏大。如果Leader崩溃,中间会缺失消息。
kafka设计了独特的ISR复制,可以再保障数据一致性情况下又可提高吞吐量。