kafka internals

cluster membership

kafka使用zookeeper上的ephemeral node(短暂的节点,每有一个broker会创建一个node)来管理kafka cluster现有的brokers成员列表,通过brokerId来区分,当一个broker短暂的与zookeeper失联,zookeeper会将它移除出列表,但每个topic的replicas列表中仍会保留brokerId,所以broker短暂的失联后立即用同一个brokerId启动一个新的broker,它会立即加入集群并替代原先的broker,with the same partitions and topics assigned to it。

Controller

第一个加入集群的broker会充当controller的职能,负责分区选主,集群同一时刻只有一个controller。zookeeper负责监控,当controller所在的broker失联,集群中的其他brokers会申请创建controller node,第一个创建成功的就是新的controller,controller可以通过epoch number来避免旧的controller发送过来的垃圾消息。

当controller注意到有一个broker离开cluster时,在这个broker上有leader的所有partitions就需要一个新的leader,controller会负责选举leader,并发送请求到所有包含新leader或者followers的brokers,请求包含partitions的新的leader和followers,然后leader负责serving producer和consumer,follower负责跟新leader中的数据。

当有新的broker加入集群,controller会根据brokerId来检查这个broker上是否有replicas归属于这个集群,如果有,controller会提醒所有的brokers这个change,然后新的broker中的replicas会开始复制现有的leader中的消息。(当一个broker短暂失联时)

Replication

topic partition用于数据归类分区,replication用于防止某个broker失联而导致的数据丢失。

replicas分两类:leader 和 follower

leader:每一个partition都有一个指定的leader replicas,负责处理所有的producer consumer请求。

follower:一个partition的除leader外的所有的replicas都是followers,followers不处理客户端请求,他们只负责fetch(consumer也是发送fetch请求来请求数据) 同步leader的数据,当leader挂掉了,其中一个follower会立马顶上。

leader的另一个任务是负责监管所有的follower看他们是否及时同步了,当网络故障或者broker挂了,导致的follower无法及时(replica.lag.time.max.ms)同步leader的数据,他们就会暂时被认为out of sync,从而无法参与leader选举并成为leader。

当一个topic最开始被创建时,它的第一个partition replica会更倾向于被选为leader,所以,在创建tp时,最好spread这些tp到不同的brokers来balance每个broker上的leader数量,从而防止某个broker上的leader过多导致的网络请求overloading。

request processing

如何保证请求的顺序处理?(数据按顺序写入kafka)

如果一个broker收到对指定partition的请求,然而这个partiton的leader不在这个broker上,则会返回err response : not a leader for partition。

那么client是如何知道该向哪个broker发送请求呢,通过metadata request可以请求到client所需要的topics列表,里面会记录partiton的replicas列表,以及哪个replica是leader。metadata request可以发给任何broker,因为每个broker上都有metadata cache,可以通过设置metadata.max.age.ms来配置metadata refresh intervals,或者当metadata里指示的leader不正确时,也会发送request更新metadata。

produce request

producer通过acks来配置写入成功的策略,acks可以为0(发送请求即视为写入成功),1(leader收到请求即视为写入成功),all(所有in-sync的follower也收到请求才视为写入成功)(可以配置in-sync replicas的数量,当真实数量少于该数量时,拒绝接收新的消息直到out of sync 的follower同步后数量达到该值)。

fetch request

client发送请求,得知该从哪个tp的哪个offset开始读消息。并可以限制一次发送消息的数据量。不限制可能会造成client run out of memory。

若client发送请求的offset过于old而被删除了,会返回一个err。kafka使用zero-copy方法来发送消息,它会直接从file发送到network channel,避免了中间的buffers。

client也可以设置一个lower boundary,kafka达到这个boundary的消息后才会发送,这可以降低cpu和network的使用率。可以同时设置timeout时间,达到该时间后,即使不达到boundary也发送。

leader中的所有数据不是都可读的,只有这些消息成功写入所有的in-sync的replicas后才可读,尝试读这些数据会返回empty而不是err。原因是没有全部同步的数据会被认为unsafe,当读了这个消息后,如果leader挂掉了,另一个replicas代替了它,这个消息便不存在于kafka之中了,其他的consumer便无法再读这个message了,这便导致了读它的consumer的inconsistency。

kafka internals_第1张图片

 

partition allocation

6brokers,10partitions with replicastion factor of 3.那么就有30个replicas需要分配。那么每个broker分5个replicas。

保证每个partition的replicas不在同一个broker上,比如partition0的leader在broker2上,那么他的两个followers分别在broekr3和broker4上。

如果brokers可以感知机架信息,就可以分配每个partition的replicas到不同的机架上,避免机架downtime导致的partition失效。

我们从一个随机的broker开始(broker4),partition leader0会被分在broker4上,partition1 leader会被分在broker5上,partition2 leader会被分在broker0上。。。。。然后,对于每个partition,我们会放置replicas在一个leader增长的offset上,partition0在broker4上,那么它的两个follower就会在broker5和broker6上。。。。

如果有机架感知,那么我们broker的顺序就不再是012345了,而是031425,如图,这样的话,如果partiton0的leader在broker4上,那么它的第一个replica会在broker2上,就在不同的机架上了。

当选完broker后,就是选directory了,规则是哪个directory里面的partition数量最少放哪个,这意味着如果有新的disk加入,所有的新的partition都会放在哪个新的disk中直到balance。

kafka internals_第2张图片

 

file management

retention非常重要,kafka不会一直保留数据,但也不会consumer一读完就删,可以配置每个topic的retention period,可根据message的大小或时间。我们会把partition分到segments中,默认的一个segment包含1GB数据或者一周的数据,无论哪个更小。当这个limit达到后,我们就会关闭这个file并开始一个新的。

我们正在写入的segment叫做active segment,它绝不会被删,所以如果你的retention是保留1天的data,但每个segment包含5天的data,你就会真的保留5天的data,因为我们无法删它,如果你打算保留一周的数据并每天都roll一个新的segment,你就会发现它每天都会roll一个新的segment同时删掉旧的,也就是它时刻保持着7个segment。

file format

消息包含:key,value,offset,message size,checksum code(to detect corruption),magic byte(version of the message format),compression codec(snappy,gzip,lz4),timestamp,

kafka允许通过offset来指定读消息的offset,为了更快的定位offset,kafka通过给每个partition都配一个index文件,index map会定位segment file和position。删掉它也是安全的,kafka会自动重新生成。

compaction

kafka还支持数据保留的另一种方式,即保留topic中每个key的最近的value值(如存放购物地址,存放状态state)。只对有key的消息有效,若topic有null keys,compaction会失败。

kafka internals_第3张图片

每个log分为两部分

clean:上次compact过后,保留key对应的最新的value。

dirty:上次compact后写入的数据,还未经历compact。

通过log.cleaner.enable来开启compaction功能。每个broker会启动一个compaction manager thread 和 一定数量的compaction threads来负责执行compaction任务。这些threads会选择所有partition中dirty message写入最高频的partition并clean这个partition。thread会用一个offset map来进行工作。一个map entry有16byte的key和8byte的value。

配置kafka时,可以配置thread可以使用的memory和thread数量。1GB和5个threads意味着每个有200MB的offset map。kafka并不要求partition的整个dirty section都fit into这个map的size。但至少要有一个full segment可以fit。否则会报错并需要分配更多的memory或使用更少的threads。若只有少量的segment fit。kafka会compact fit into the map的oldest segments。剩下的会remain dirty然后等待下一次的compaction。

cleaner thread建好offset map后,就会开始读clean segment starting with the oldest。处理过程如下:

kafka internals_第4张图片

deleted events

当需要删一个key时(比如一个用户注销了,不需要追踪这个用户的所有信息了)。produce一个key 和null value。cleaner看到这个message后,会先做normal 的compaction,保留一个带着null value的message。可以配置保留时长。然后consumer就可以根据null value得知它被注销了,然后就可以去数据库里删掉所有相关的数据了。

 

 

你可能感兴趣的:(kafka internals)