Kafka 有哪些特点
- 高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒,每个topic可以分多个partition,通过consumer group 对partition进行consumer操作。
- 可扩展性:kafka集群支持热扩展
- 持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失
- 容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)
- 高并发:支持数千个客户端同时读写
Kafka的应用场景
- 日志收集:一个公司可以用Kafka收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、HBase、Solr等。
- 消息系统:解耦生产者和消费者、缓存消息等。
- 用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
- 运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
- 流式处理:比如spark streaming和 Flink
Kafka的集群安装
-
启动zookeeper集群
-
进入到config目录下修改server.properties
broker.id=1 listeners=PLAINTEXT://192.168.11.140:9092 zookeeper.connect = 192.168.11.140:2181, 192.168.11.139:2181, 192.168.11.138:2181
-
启动
sh kafka-server-start.sh -daemon ../config/server.properties sh kafka-server-stop.sh
-
Kafka常用命令
./kafka-server-start.sh ../config/server.properties //kafka服务启动 ./kafka-topics.sh --list --zookeeper zk服务IP:2181 //查看有哪些主题 ./kafka-topics.sh --create --zookeeper zk服务IP:2181 --replication-factor 1 --partitions 1 --topic WordCount //创建topic ./kafka-topics.sh -zookeeper zk服务IP:2181 -describe -topic WordCount //查看topic的详细信息 ./kafka-console-producer.sh --broker-list kafka服务IP:9092 --topic WordCount //生产者客户端命令 ./kafka-console-consumer.sh -zookeeper zk服务IP:2181 --from-beginning --topic WordCount //消费者客户端命令 ./kafka-topics.sh --zookeeper zk服务IP:2181 --delete --topic WordCount //删除topic (注:不能真正删除topic只是把这个topic标记为删除(marked for deletion),要彻底把topic删除必须把kafka中与当前topic相关的数据目录和zookeeper中与当前topic相关的路径一并删除。)
Kafka的架构
![](https://i-blog.csdnimg.cn/blog_migrate/186d4ce1b8ae0feececb8f1f8a6784c8.jpeg)
Kafka 架构分为以下几个部分
- Producer :消息生产者,就是向 kafka broker 发消息的客户端。
- Consumer :消息消费者,向 kafka broker 取消息的客户端。
- Topic :可以理解为一个队列,一个 Topic 又分为一个或多个分区。
- Consumer Group:是 kafka 用来实现一个 topic 消息的广播(发给所有的 consumer)和单播(发给任意一个 consumer)的手段。一个 topic 可以有多个 Consumer Group。
- Broker :一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
- Partition:
- 为了实现扩展性,一个非常大的 topic 可以分布到多个 partition上,每个 partition 是一个有序的队列。
- partition 中的每条消息都会被分配一个有序的id(offset)。
- 将消息发给 consumer,kafka 只保证单个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。
- Segment:partition物理上由多个segment组成。
- offset:
- 每个partition都由一系列有序的、不可变的消息组成,这些消息被连续的追加到partition中。
- partition中的每个消息都有一个连续的序列号叫做offset,用于partition唯一标识一条消息。
- kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。
- 例如你想找位于 2049 的位置,只要找到 2048.kafka 的文件即可。当然 the first offset 就是 00000000000.kafka。
- Message: 消息是kafka中最基本的数据单元。消息由一串字节构成,其中主要由key和value构成,key和value也都是byte数组。key的主要作用是根据一定的策略,将消息路由到指定的分区中,这样就可以保证包含同一key的消息全部写入到同一个分区中,key可以是null。
Kafka在zookeeper的存储结果
![](https://i-blog.csdnimg.cn/blog_migrate/d9ee7f2d4cbbf46ef27cfedb76e7863c.jpeg)
消息可靠性机制
消息发送端
-
Producer异步发送
(1) 设置 producer.type=async
(2) 优点是可批量发送消息(消息个数达到 batch.num.messages=200 或 时间达到 linger.ms “间隔时间")、吞吐量佳
(3) 缺点是发送不及时可能导致丢失; -
Producer同步发送
(1) 设置 producer.type=sync
(2) 三种确认方式-
acks = 0: producer不会等待broker(leader)发送ack 。因为发送消息网络超时或broker crash,既有可能丢失也可能会重发
(1.Partition的Leader还没有commit消息 2.Leader与Follower数据不同步) -
acks = 1: (默认值) 当leader接收到消息之后发送ack。
(之后 Leader 向 Followers 同步时,如果 Leader 宕机会导致消息没同步而丢失,producer 却依旧认为成功) -
acks=all/-1: producer 等待 Leader 写入本地日志、而且 Leader 向 Followers 同步完成后才会确认;最可靠。
-
-
Producer为消息选择分区
(1) 当key为空时,消息随机发送到某个分区
(2) 用key的hash值对partition个数取模,决定要把消息发送到哪个partition上
消息接收端
-
consumer存在两种消费模型
- push:优势在于消息实时性高。劣势在于没有考虑consumer消费能力和饱和情况,容易导致producer压垮consumer。
- pull:优势在可以控制消费速度和消费数量,保证consumer不会出现饱和。劣势在于当没有数据,会出现空轮询,消耗cpu。
-
kafka采用pull
-
Kafka Consumer 有两个接口
- Low-level API: 消费者自己维护 offset 等值,可以完全控制;
- High-level API: 封装了对 parition 和 offset 的管理,使用简单;可能遇到 Consumer 取出消息并更新了 offset,但未处理消息即宕机,从而相当于消息丢失;
- Low-level API: 消费者自己维护 offset 等值,可以完全控制;
-
Kafka 支持 3 种消息传递语义
- 消息最多消费一次(at most once):consumer先读取消息,再确认offset,最后处理消息(消息可能会丢失,但永远不会重新发送)
- 消息至少消费一次(at least once):consumer先读取消息,再处理消息,最后确认offset(消息永远不会丢失,但可能会重新传递) —> 首选
- 恰恰一次(exactly once): 消息只会发送一次。kafka中并没有严格的去实现(基于2阶段提交),我们认为这种策略在kafka中是没有必要的
-
消息的消费原理
- 之前Kafka存在的一个非常大的性能隐患就是利用ZK来记录各个Consumer Group的消费进度(offset)。当然JVM Client帮我们自动做了这些事情,但是Consumer需要和ZK频繁交互,而利用ZK Client API对ZK频繁写入是一个低效的操作,并且从水平扩展性上来讲也存在问题。所以ZK抖一抖,集群吞吐量就跟着一起抖,严重的时候简直抖的停不下来。
- 新版Kafka已推荐将consumer的位移信息保存在Kafka内部的topic中,即__consumer_offsets内置topic中。通过以下操作来看看__consumer_offsets内置topic是怎么存储消费进度的,__consumer_offsets内置topic默认有50个分区
-
计算consumer group对应的hash值,结果放在了15分区上
Math.abs(“my_consumer_offsets_group".hashCode()) % 50
-
获得consumer group的位移信息
bin/kafka-simple-consumer-shell.sh --topic __consumer_offsets --partition 15 -broker-list 192.168.11.140:9092,192.168.11.141:9092,192.168.11.138:9092 --formatter kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter
-
Kafka消息存储
Topic & Partition
同一个Topic 通常存储的是一类消息,每个topic内部实现又被分成多个partition,每个partition在存储层面是append log文件。
在Kafka文件存储中,同一个topic下有多个不同partition,每个partition为一个目录,partiton命名规则为topic名称+有序序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。
-
每个partion(目录)相当于一个巨型文件被平均分配到多个大小相等segment(段)数据文件中。但每个段segment file消息数量不一定相等,这种特性方便old segment file快速被删除。
-
每个partiton只需要支持顺序读写就行了,segment文件生命周期由服务端配置参数决定。这样做的好处就是能快速删除无用文件,有效提高磁盘利用率。
-
segment file组成:由2大部分组成,分别为index file和data file,此2个文件一一对应,成对出现,后缀”.index”和“.log”分别表示为segment索引文件、数据文件。
-
segment文件命名规则:partion全局的第一个segment从0开始,后续每个segment文件名为上一个segment文件最后一条消息的offset值。数值最大为64位long大小,19位数字字符长度,没有数字用0填充。
-
segment中index与data file对应关系物理结构如下:
- 上图中索引文件存储大量元数据,数据文件存储大量消息,索引文件中元数据指向对应数据文件中message的物理偏移地址。
- 其中以索引文件中元数据3,497为例,依次在数据文件中表示第3个message(在全局partiton表示第368772个message),以及该消息的物理偏移地址为497。
-
了解到segment data file由许多message组成,下面详细说明message物理结构如下:
-
查看kafka数据文件内容
在使用kafka的过程中有时候需要我们查看产生的消息信息,这些都被记录在kafka的log文件中。由于log文件的特殊格式,需要通过kafka提供的工具来查看./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files /tmp/kafka-logs/*/000**.log --print-data-log {查看消息内容}
副本(replication)策略
-
数据同步
(1) kafka在0.8版本前没有提供Partition的Replication机制,一旦Broker宕机,其上的所有Partition就都无法提供服务,而Partition又没有备份数据,数据的可用性就大大降低了。所以0.8后提供了Replication机制来保证Broker的failover。
(2) 引入Replication之后,同一个Partition可能会有多个Replica,而这时需要在这些Replication之间选出一个Leader,Producer和Consumer只与这个Leader交互,其它Replica作为Follower从Leader中复制数据。
-
副本放置策略
-
为了更好的做负载均衡,Kafka尽量将所有的Partition均匀分配到整个集群上。Kafka分配Replica的算法如下:
(1) 将所有存活的N个Brokers和待分配的Partition排序
(2) 将第i个Partition分配到第(i mod n)个Broker上,这个Partition的第一个Replica存在于这个分配的Broker上,并且会作为partition的优先副本
(3) 将第i个Partition的第j个Replica分配到第((i + j) mod n)个Broker上 -
假设集群一共有4个brokers,一个topic有4个partition,每个Partition有3个副本。下图是每个Broker上的副本分配情况。
-
-
副本同步策略
-
ISR(副本同步队列)
维护的是有资格的follower节点- 副本的所有节点都必须要和zookeeper保持连接状态
- 副本的最后一条消息的offset和leader副本的最后一条消息的offset之间的差值不能超过指定的阀值。
这个阀值是可以设置的(replica.lag.max.messages) - 副本同步时间限制(replica.lag.time.max.ms)
假如设置500毫秒,只要 follower副本每隔500毫秒或更早地向 leader 副本发送一个 fetch 请求,它们就不会被标记为死亡并且不会从 ISR 中删除。
-
LEO(Log End Offset)
LEO 是所有副本都会有的一个offset标记,它指向追加到当前副本的最后一个消息的offset。当生产者向leader副本追加消息的时候,leader副本的LEO标记就会递增;当follower副本成功从leader副本拉取消息并更新到本地的时候,follower副本的LEO就会增加。 -
HW(HighWatermark)
(1) HW标记了一个特殊的offset,当消费者处理消息的时候,只能拉去到HW之前的消息,HW之后的消息对消费者来说是不可见的。
(2) 也就是说,取partition对应ISR中最小的LEO作为HW,consumer最多只能消费到HW所在的位置。
(3) 每个replica都有HW,leader和follower各自维护更新自己的HW的状态。
(4) 对于leader新写入的消息,consumer不能立刻消费,leader会等待该消息被所有ISR中的replicas同步更新HW,此时消息才能被consumer消费。
(5) 这样就保证了如果leader副本损坏,该消息仍然可以从新选举的leader中获取。 -
同步过程
(1) Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,然后无论该Topic的Replication Factor为多少,Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader pull数据。这种方式上,Follower存储的数据顺序与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK。一旦Leader收到了ISR中的所有Replica的ACK,该消息就被认为已经commit了,Leader将增加HW并且向Producer发送ACK。(2) 为了提高性能,每个Follower在接收到数据后就立马向Leader发送ACK,而非等到数据写入Log中。因此,对于已经commit的消息,Kafka只能保证它被存于多个Replica的内存中,而不能保证它们被持久化到磁盘中,也就不能完全保证异常发生后该条消息一定能被Consumer消费。
(3) Consumer读消息也是从Leader读取,只有被commit过的消息才会暴露给Consumer。
(4) Leader会跟踪与其保持同步的Replica列表,该列表称为ISR(即in-sync Replica)。如果一个Follower宕机,或者落后太多,Leader将把它从ISR中移除。这里所描述的“落后太多”指Follower复制的消息落后于Leader后的条数超过预定值或者Follower超过一定时间未向Leader发送fetch请求。
(5) Kafka只解决fail/recover,一条消息只有被ISR里的所有Follower都从Leader复制过去才会被认为已提交。这样就避免了部分数据被写进了Leader,还没来得及被任何Follower复制就宕机了,而造成数据丢失(Consumer无法消费这些数据)。而对于Producer而言,它可以选择是否等待消息commit。这种机制确保了只要ISR有一个或以上的Follower同步了被commit的消息就不会丢失。
-
leader选举
- Leader选举本质上是一个分布式锁,有两种方式实现基于ZooKeeper的分布式锁
(1) 节点名称唯一性:多个客户端创建一个节点,只有成功创建节点的客户端才能获得锁
(2) 临时顺序节点:所有客户端在某个目录下创建自己的临时顺序节点,只有序号最小的才获得锁 - 控制器(Broker)选举
在多个broker中选举出一个leader来管理其他broker
应用上面第一种策略 - 分区副本选举
也是应用上面第一种策略
如何处理所有的Replica不工作的情况?-
在ISR中至少有一个follower时,Kafka可以确保已经commit的数据不丢失,但如果某个Partition的所有Replica都宕机了,就无法保证数据不丢失
(1) 等待ISR中的任意一个Replica“活”过来,并且选它作为Leader
(2) 选择第一个“活”过来的Replica(不一定是ISR中的)作为Leader -
这就需要在可用性和一致性当中作出一个简单的折衷。
(1) 如果一定要等待ISR中的Replica“活”过来,那不可用的时间就可能会相对较长。而且如果ISR中的所有Replica都无法“活”过来了,或者数据都丢失了,这个Partition将永远不可用。
(2) 选择第一个“活”过来的Replica作为Leader,而这个Replica如果不是ISR中的Replica,那即使它并不保证已经包含了所有已commit的消息,它也会成为Leader而作为consumer的数据源(前文有说明,所有读写都由Leader完成)。
(3) Kafka0.8.*使用了第二种方式。Kafka支持用户通过配置选择这两种方式中的一种,从而根据不同的使用场景选择高可用性还是强一致性
-
- Leader选举本质上是一个分布式锁,有两种方式实现基于ZooKeeper的分布式锁
-
kafka高吞吐量的原因
-
顺序写的方式存储数据
每条消息都被append到该Partition中,属于顺序写磁盘,因此效率非常高。对于传统的message queue而言,一般会删除已经被消费的消息,而Kafka是不会删除数据的,它会把所有的数据都保留下来,每个消费者(Consumer)对每个Topic都有一个offset用来表示读取到了第几条数据。
-
批量发送;在异步发送模式中。kafka允许进行批量发送,也就是先将消息缓存到内存中,然后一次请求批量发送出去。这样减少了磁盘频繁io以及网络IO造成的性能瓶颈
batch.size //每批次发送的数据大小 linger.ms //间隔时间
-
零拷贝
-
消息从发送到落地保存,broker维护的消息日志本身就是文件目录,每个文件都是二进制保存,生产者和消费者使用相同的格式来处理。在消费者获取消息时,服务器先从硬盘读取数据到内存,然后把内存中的数据原封不动的通过socket发送给消费者。虽然这个操作描述起来很简单,但实际上经历了很多步骤:
(1) 操作系统将数据从磁盘读入到内核空间的页缓存
(2) 应用程序将数据从内核空间读入到用户空间缓存中
(3) 应用程序将数据写回到内核空间到socket缓存中
(4) 操作系统将数据从socket缓冲区复制到网卡缓冲区,以便将数据经网络发出 -
通过“零拷贝”技术可以去掉这些没必要的数据复制操作,同时也会减少上下文切换次数
-
kafka的分区分配策略
-
同一个consumer group里面的consumer怎么去分配应该消费哪个分区里的数据?如下有两种分区策略。
-
Range 范围分区(默认的)
假如有11个分区,3个消费者,把分区按照序号排列0,1,2,3,4,5,6,7,8,9,10;消费者为C1,C2,C3,那么用分区数除以消费者数来决定每个Consumer消费几个Partition,除不尽的前面几个消费者将会多消费一个。C1:0,1,2,3
C2:4,5,6,7
C3:8,9,10 -
RoundRobin 轮询分区
-
把所有的partition和consumer列出来,然后轮询consumer和partition,尽可能的把partition均匀的分配给consumer
-
案例
假如有3个Topic T0(三个分区P0-0,P0-1,P0-2),T1(两个分区P1-0,P1-1),T2(四个分区P2-0,P2-1,P2-2,P2-3)
有三个消费者:C0(订阅了T0,T1),C1(订阅了T1,T2),C2(订阅了T0,T2)那么分区过程如下图所示
分区将会按照一定的顺序排列起来,消费者将会组成一个环状的结构,然后开始轮询。 -
过程
P0-0分配给C0,
P0-1分配给C1但是C1并没订阅T0,于是跳过C1把P0-1分配给C2,
P0-2分配给C0,
P1-0分配给C1,
P1-1分配给C0,
P2-0分配给C1,
P2-1分配给C2,
P2-2分配给C1,
P2-3分配给C2 -
结果
C0: P0-0,P0-2,P1-1
C1:P1-0,P2-0,P2-2
C2:P0-1,P2-1,P2-3
-
-
-
consumer rebalance(Kafka 的再均衡)
- kafka提供了一个角色Coordinator来执行。当Consumer Group的第一个Consumer启动的时候,他会向kafka集群中的任意一台broker发送GroupCoordinatorRequest请求,broker会返回一个负载最小的broker设置为coordinator,之后该group的所有成员都会和coordinator进行协调通信
- 整个Rebalance分为两个过程 joinGroup和sysncJoin
- joinGroup过程
在这一步中,所有的成员都会向coordinator发送JionGroup请求,请求内容包括group_id,member_id.protocol_metadata等,coordinator会从中选出一个consumer作为leader,并且把组成员信息和订阅消息,leader信息,rebanlance的版本信息发送给consumer - Synchronizing Group State阶段
组成员向coordinator发送SysnGroupRequet请求,但是只有leader会发送分区分配的方案(分区分配的方案其实是由消费者确定的),当coordinator收到leader发送的分区分配方案后,会通过SysnGroupResponse把方案同步到各个consumer中
- joinGroup过程
日志策略
-
日志保留策略
无论消费者是否已经消费了消息,kafka都会一直保存这些消息,但并不会像数据库那样长期保存。为了避免磁盘被占满,kafka会配置相应的保留策略(retention policy),以实现周期性地删除陈旧的消息- 根据消息保留的时间,当消息在kafka中保存的时间超过了指定时间,就可以被删除
- 根据topic存储的数据大小,当topic所占的日志文件大小大于一个阀值,则可以开始删除最旧的消息
-
日志压缩策略
在很多场景中,消息的key与value的值之间的对应关系是不断变化的,就像数据库中的数据会不断被修改一样,消费者只关心key对应的最新的value。我们可以开启日志压缩功能,kafka定期将相同key的消息进行合并,只保留最新的value值