持久化
• Kafka存储布局简单:Topic的每个Partition对应一个逻辑日志
• 每次生产者发布消息到一个分区,代理就将消息追加到最后一个段文件中
• 与传统的消息系统不同,Kafka系统中存储的消息没有明确的消息Id。
• 消息通过日志中的逻辑偏移量来公开。
传输效率
• 生产者提交一批消息作为一个请求。消费者虽然利用api遍历消息是一个一个的,但背后也是一次请求获取一批数据
,从而减少网络请求数量。
• Kafka层采用无缓存设计,而是依赖于底层的文件系统页缓存
。这有助于避免双重缓存,及即消息只缓存了一份在页缓存中。同时这在kafka重启后保持缓存warm也有额外的优势。因kafka根本不缓存消息在进程中,故gc开销也就很小
• zero-copy
:kafka为了减少字节拷贝,采用了大多数系统都会提供的 sendfile系统调用
无状态的Broker
• Kafka代理是无状态的:意味着消费者必须维护已消费的状态信息
。这些信息由消费者自己维护,代理完全不管。这种设计非常微妙,它本身包含了创新
– 从代理删除消息变得很棘手,因为代理并不知道消费者是否已经使用了该消息。 Kafka创新
性地解决了这个问题,它将一个简单的基于时间的SLA应用于保留策略
。
当消息在代理中超过一定时间后,将会被自动删除。
– 这种创新设计有很大的好处,消费者可以故意倒回到老的偏移量再次消费数据。
这违反了队列的常见约定,但被证明是许多消费者的基本特征。
消息的交付保证
• Kafka默认采用at least once的消息投递策略。即在消费者端的处理顺序是获得 消息->处理消息->保存位置。这可能导致一旦客户端挂掉,新的客户端接管时处理前面客户端已处理过的消息。
• 三种保证策略:
– At most once (消息最多发送一次)消息可能会丢,但绝不会重复传输
– At least one (消息最少发送一次)消息绝不会丢,但可能会重复传输
– Exactly once (确实就发送一次传输一次)每条消息肯定会被传输一次且仅传输一次
副本管理
• kafka将日志复制到指定多个服务器上。
• 复本的单元是partition。在正常情况下,每个分区有一个leader和0到多个follower。
• leader处理对应分区上所有的读写请求。分区可以多于broker数
,leader也是分布式的。
• follower的日志和leader的日志是相同的, follower被动的复制 leader。如果 leader挂了,其中一个follower会自动变成新的leader.
• 和其他分布式系统一样,节点“活着” 定义在于我们能否处理一些失败情况。
kafka需要两个条件保证是“活着”
– 节点在zookeeper注册的session还在且可维护
(基于zookeeper心跳机制
)
– 如果是 slave 则能够紧随 leader 的更新不至于落得太远。
• kafka采用in sync来代替“活着”
– 如果follower挂掉或卡住或落得很远,则leader会移除同步列表中的in sync。至于落了多远
才叫远由replica.lag.max.messages配置,而表示复本“卡住”由replica.lag.time.max.ms
配置
• 所谓一条消息是“提交”的,意味着所有in sync的复本也持久化到了他们的log中。这意味着消费者无需担心leader挂掉导致数据丢失。另一方面,生产者可以选择是否等待消息“提交”。
• kafka动态的维护了一组in-sync(ISR)的复本,表示已追上了leader,只有处于该状态的成员组才是能被选择为leader。这些ISR组会在发生变化时被持久化到zookeeper中。通过ISR模型和f+1复本,可以让kafka的topic支持最多f个节点挂掉而不会导致提交的数据丢失
• 由于kafka中一个topic中的不同分区只能被消费组中的一个消费者消费
,就避免了多个消费者消费相同的分区时会导致额外的开销
(如要协调哪个消费者消费哪个消息,还有锁及状态的开销)。 kafka中消费进程只需要在代理和同组消费者有变化时时进行一次协调(这种协调不是经常性的,故可以忽略开销)。
zookeeper 的使用
• kafka使用zookeeper做以下事情
:
– 探测 broker和consumer 的添加或移除
– 当1发生时触发每个消费者进程的重新负载
。
– 维护消费关系
和追踪消费者在分区消费的消息的offset
• Broker Node Registry
• /brokers/ids/[0...N]
--> host:port (ephemeral node)
– broker启动时在/brokers/ids下创建一个znode,把broker id写进去。
– 因为broker把自己注册到zookeeper中使用的是瞬时节点,所以这个注册是动态的,如果
broker宕机或者没有响应该节点就会被删除。
• Broker Topic Registry
• /brokers/topics/[topic]/[0...N]
--> nPartions (ephemeral node)
– 每个broker把自己存储和维护的partion信息注册到该路径下。
• Consumers and Consumer Groups
– consumers也把它们自己注册到zookeeper上,用以保持消费负载平衡和offset记录
。
– group id 相同的多个consumer构成一个消费组,共同消费一个topic,同一个组的 consumer会尽量均匀的消费,其中的一个consumer只会消费一个partion的数据
。
• Consumer Id Registry
• /consumers/[group_id]/ids/[consumer_id] --> {“topic1”: #streams, …,“topicN”: #streams} (ephemeral node)
– 每个consumer在/consumers/[group_id]/ids下创建一个瞬时的唯一的consumer_id,用来
描述当前该group下有哪些consumer是alive的,如果消费进程挂掉对应的consumer_id就
会从该节点删除。
• Consumer Offset Tracking
• /consumers/[group_id]/offsets/[topic]/[partition_id] --> offset_counter_value ((persistent node)
– consumer把每个partition的消费offset记录保存在该节点下。
• Partition Owner registry
• /consumers/[group_id]/owners/[topic]/[broker_id-partition_id] --> consumer_node_id (ephemeral node)
– 该节点维护着partion与consumer之间的对应关系。
问题:
为什么需要分partition?
Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1
Kafka可以将Topic主题划分为多个分区(Partition),会根据分区规则选择把消息存储到哪个分区中,只要如果分区规则设置的合理,那么所有的消息将会被均匀的分布到不同broker不同的分区中
,这样就实现了负载均衡和水平扩展
。另外,多个订阅者可以从一个或者多个分区中同时消费数据,以支撑海量数据处理能力
Kafka的设计也是源自生活,好比是为公路运输,不同的起始点和目的地需要修不同高速公路(主题),高速公路上可以提供多条车道(分区),流量大的公路多修几条车道保证畅通,流量小的公路少修几条车道避免浪费
。
收费站好比消费者,车多的时候多开几个一起收费避免堵在路上,车少的时候开几个让汽车并道就好了
由于消息是以追加到分区中的,多个分区顺序写磁盘的总效率要比随机写内存还要高
(引用Apache Kafka – A High Throughput Distributed Messaging System的观点),是Kafka高吞吐率的重要保证之一。
为了保证数据的可靠性,Kafka会给每个分区找一个节点当带头大哥(Leader),以及若干个节点当随从(Follower)。消息写入分区时,带头大哥除了自己复制一份外还会复制到多个随从。如果随从挂了,Kafka会再找一个随从从带头大哥那里同步历史消息;如果带头大哥挂了,随从中会选举出新一任的带头大哥,继续笑傲江湖。
comsumer group 有什么作用?
如果要一个group用几个consumer来同时读取的话,需要多线程来读取,一个线程相当于一个consumer实例。当consumer的数量大于分区的数量的时候,有的consumer线程会读取不到数据。
假设一个topic test 被groupA消费了,现在启动另外一个新的groupB来消费test,默认test-groupB的offset不是0,而是没有新建立,除非当test有数据的时候,groupB会收到该数据,该条数据也是第一条数据,groupB的offset也是刚初始化的ofsert, 除非用显式的用–from-beginnging 来获取从0开始数据
Group的作用就在于让多个不同的组可以独立消费同一个topic。保证每个组都可以消费到全部完整的消息。
比如A和B,从属于这家公司的两个team,业务需求都不一样,但是都需要消费同一个topic,所以需要独立的消费,就是说同一条message给了A组也得给B组。但是在同一个组里,为了并行消费,可以设置多个机器(或者就是多个进程),每个机器的消费是不独立的,因为A1和A2是一个组里面的同一个业务需求。
怎么去实现?
鸣谢-参考资料:
https://www.zhihu.com/question/28925721/answer/139861200