Kafka 是一种高吞吐量、分布式、基于发布/订阅的消息系统,最初由 LinkedIn 公司开发,使用Scala 语言编写,目前是 Apache 的开源项目。
一、Kafka的相关概念
1、Topic
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic,类似于数据库的表名。
物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个Broker上,但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处。
2、Partition
Topic中的数据分割为一个或多个Partition。每个Topic至少有一个Partition。每个Partition中的数据使用多个segment文件存储。Partition中的数据是有序的,不同Partition间的数据丢失了数据的顺序。如果Topic有多个Partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将Partition数目设为1。
3、Broker
Kafka 集群包含一个或多个服务器,服务器节点称为Broker,Broker存储Topic的数据。
- 如果某Topic有N个Partition,集群有N个Broker,那么每个Broker存储该Topic的一个Partition。
- 如果某Topic有N个Partition,集群有(N+M)个Broker,那么其中有N个Broker存储该Topic的一个Partition,剩下的M个broker不存储该Topic的Partition数据。
- 如果某Topic有N个Partition,集群中Broker数目少于N个,那么一个Broker存储该Topic的一个或多个Partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致Kafka集群数据不均衡。
4、Producer
生产者即数据的发布者,该角色将消息发布到Kafka的Topic中。Broker接收到生产者发送的消息后,Broker将该消息追加到当前用于追加数据的Segment文件中。生产者发送的消息,存储到一个Partition中,生产者也可以指定数据存储的Partition。
5、Consumer
消费者可以从Broker中读取数据。一个消费者可以消费多个Topic中的数据。
6、ConsumerGroup
每个Consumer属于一个特定的ConsumerGroup。可以为每个Consumer指定GroupName,若不指定GroupName则属于默认的Group。
ConsumerGroup是Kafka用来实现一个Topic消息的广播(发给所有的Consumer)和单播(发给任意一个Consumer)的手段。
一个Topic可以有多个ConsumerGroup,Topic的消息会复制到Consumer。如果需要实现广播,只要每个Consumer有一个独立的ConsumerGroup就可以了;要实现单播只要所有的Consumer在同一个ConsumerGroup。
用ConsumerGroup还可以将Consumer进行自由的分组而不需要多次发送消息到不同的Topic。
7、Leader
每个Partition有多个副本,其中有且仅有一个作为Leader,Leader是当前负责数据的读写的Partition。
8、Follower
Follower跟随Leader,所有写请求都通过Leader路由,数据变更会广播给所有Follower,Follower与Leader保持数据同步。如果Leader失效,则从Follower中选举出一个新的Leader。当Follower与Leader挂掉、卡住或者同步太慢,Leader会把这个Follower从"in sync replicas"(ISR)列表中删除,重新创建一个Follower。
9、Offset
Kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000。
二、Kafka的ACK机制
ack有3个可选值,分别是1,0,-1。
1、ack=0
表示生产者在生产过程中的消息丢失。简单来说就是,Producer发送一次就不再发送了,不管是否发送成功。
2、ack=1
标识Broker在故障后的消息丢失。简单来说就是,Producer只要收到一个分区副本成功写入的通知就认为推送消息成功了。这里有一个地方需要注意,这个副本必须是Leader副本。只有Leader副本成功写入了,producer才会认为消息发送成功。
注意:ack的默认值就是1。这个默认值其实就是吞吐量与可靠性的一个折中方案。生产上我们可以根据实际情况进行调整,比如如果你要追求高吞吐量,那么就要放弃可靠性。
3、ack=-1
表示生产侧和存储侧不会丢失数据。简单来说就是,Producer只有收到分区内所有副本的成功写入的通知才认为推送消息成功了。
三、Kafka的Offset机制
Kafka消费者有三种消费语义:
- at-most-once:最多一次,可能丢数据。
- at-least-once:最少一次,可能重复消费数据。
- exact-once message:精确一次。
四、Kafka是Pull模式,还是Push模式
Kafka最初考虑的问题是,Customer应该从Brokers拉取消息还是Brokers将消息推送到Consumer,也就是Pull还Push。
消息系统都致力于让Consumer以最大的速率最快速的消费消息,一些消息系统比如Scribe和Apache Flume采用了Push模式,将消息推送到下游的Consumer。这样做有好处也有坏处:由Broker决定消息推送的速率,但当Broker推送的速率远大于Consumer消费的速率时,Consumer恐怕就要崩溃了。
最终Kafka还是选取了传统的Pull模式,Producer将消息推送到Broker,Consumer从Broker拉取消息。
Push模式必须在不知道下游Consumer消费能力和消费策略的情况下决定是立即推送每条消息还是缓存之后批量推送。
如果为了避免Consumer崩溃而采用较低的推送速率,将可能导致一次只推送较少的消息而造成浪费。Pull模式下,Consumer就可以根据自己的消费能力去决定这些策略,自主决定是否批量的从Broker拉取数据。
Pull有个缺点是,如果Broker没有可供消费的消息,将导致Consumer不断在循环中轮询,直到新消息到达。为了避免这点,Kafka有个参数可以让Consumer阻塞知道新消息到达(当然也可以阻塞知道消息的数量达到某个特定的量这样就可以批量发)。
五、Kafka中ZooKeeper的作用
Kafka中使用了ZookKeper的分布式锁、分布式配置及统一命名的分布式协调解决方案。
- 在Kafka的Broker集群中的Controller的选择,是通过zk的临时节点争抢获得的。
- BrokerID等如果自增的话也是通过zk的节点version实现的全局唯一。
- Kafka中Broker中的状态数据也是存储在zk中。
补充:新旧版本变化中,就把曾经的offset从zk中迁移出了zk。
六、Kafka中高性能如何保障
首先,性能的最大瓶颈依然是IO,这个是不能逾越的鸿沟。
虽然,Broker在持久化数据的时候已经最大努力的使用了磁盘的顺序读写;更进一步的性能优化是零拷贝的使用,也就是从磁盘日志到消费者客户端的数据传递,因为Kafka是MQ,对于msg不具备加工处理,所以得以实现。
然后就是大多数分布式系统一样,总要做tradeoff,在速度与可用性/可靠性中挣扎。ACK的0,1,-1级别就是在性能和可靠中权衡。