初入公司,接到一个卡夫卡的问题,将学习笔记输出在这里。
Kafka相关资料
Kafka 就是一款基于发布与订阅的消息系统。它一般被称为“分布式提交日志”或者“分布式流平台”。
Kafka 的数据单元被称为消息,消息可以有一个可选的元数据 ,也就是键。批次就是一组消息,这些消息属于同一个主题和分区。
Kafka的基本术语
Topic
Kafka将消息种子(Feed)分门别类,每一类的消息称之为一个主题(Topic).
Producer
发布消息的对象称之为主题生产者(Kafka topic producer)
Consumer
订阅消息并处理发布的消息的种子的对象称之为主题消费者(consumers)
Broker
已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker). 消费者可以订阅一个或多个主题(topic),并从Broker拉数据,从而消费这些已发布的消息。
集群成员关系
通过zookeeper维护,每个broker的id唯一,可在配置文件指定,或自动生成。具体配置见Zookeeper配置。在 broker 停机、出现网络分区或长时间垃圾回收停顿时,broker 会从 Zookeeper 上断开连接,此时 broker 在启动时创建的临时节点会自动从 Zookeeper 上移除。监听broker列表的Kafka组件会被告知该 broker已移除。在关闭 broker 时,它对应的节点也会消失,不过它的 ID 会继续存在于其他数据结构中 。例如,主题的副本列表里就可能包含这些id。在完全关闭一个 broker 之后,如果使用相同的id启动另一个全新的 broker,它会立即加入集群,并拥有与旧 broker相同的分区和主题。
控制器
就是一个broker,额外负责首领选举,其他 broker 在控制器节点上创建Zookeeper watch 对象。如果控制器关闭或与zookeeper断开,其他broker就尝试成为控制器。如果一个broker断开,控制器负责为失去首领的分区选择新的首领(一般就是分区副本的下一个),控制器使用epoch 来避免两个节点同时认为自己是当前的控制器。
Kafka数据可靠保证
- Kafka可以保证分区消息的顺序;
- 只有消息写入所有同步副本才认为消息是“已提交”的;
- 只要有一个活跃副本,已提交消息就不会丢失;
- 消费者只能读取已提交消息;
复制(ISR)
所有生产者请求和消费者请求都会经过首领副本。跟随者唯一的任务就是从首领那里复制消息,保持与首领一致的状态。首领确保跟随者数据与其保持一致,跟随者数据不一致则无法成为新首领。replica.lag.time.max.ms用来设定跟随者的正常不活跃时间。
跟随者活跃的判断条件:1. 它在过去的6s内(可配置)向Zookeeper 发送过心跳。2. 过去的10s内(可配置)从首领那里获取过消息。3. 在过去的10s内从首领那里获取过最新的消息,还必须是几乎零延迟的。如果不活跃,跟随者会与zookeeper重新建立连接,重新获取最新数据。如果ack设置为all,而跟随者不活跃,则消费者可以忽略此跟随者响应。
主题级别的配置副本数量参数是replicatlon.factor,而在broker级别则可以通过 default.replication.factor 来配置自动创建的主题。
unclean.leader.selection.enable设置为true则允许不完全的首领选举,即如果首领副本崩溃,而其他跟随者副本都没有同步,那么允许不同步的副本成为首领。
min.insync.replic设定最小同步副本数量。如果不够,则broker拒绝接收消息,返回错误,副本相当于变成只读。
发送确认
ack =0,可以保证惊人的吞吐量,但是生产者完全不知道出现了数据丢失;
ack=1,也有可能出现丢失,因为可能成功写入后首领副本出现了崩溃;
ack=all,配合min.insync.replicas可以实现最保险的办法,生产者会一直重试直到数据发送成功,但是会降低吞吐量。
生产者重试设置
首先要区分发送消息错误可否重试,如果要保证数据不丢失,那么就需要在遇到可重试错误时进行重试,要注意重试次数。另外,还要注意重试可能会导致都写入成功而重复,可以在消息里加入唯一的标识符加以辨别。
可靠系统里使用消费者
如果消费者提交了偏移量却未能处理完消息,那么就有可能造成消息丢失,这也是消费者丢失消息的主要原因。在这种情况下,如果其他消费者接手了工作,那些没有被处理完的消息就会被忽略,永远得不到处理。具体分析详见轮询
有几个设置需要注意:
- group.id 如果希望一个消费者获取到一个topic的所有消息,则需要设置其唯一的group.id
- auto.offset.reset 可配置为earliest与latest,none,需要注意的是,虽然是如果已有提交offset,从offset开始消费,但是长时间保持生产者速度大于消费者消费速度,消费者记录的offset被删除,那么还是会触发从最早还是从最新offset开始消费。
- enable.auto.commit 此项容易造成重复消费,如在提交前停止处理消息,这时消息已经被处理一部分但是offset没有提交上去。而且如果将消息交给线程处理,那么可能消息还没有处理完offset就已经提交了。
- auto.commit.interval.ms 默认5s自动提交一次offset,频率越低重复消费的可能性越低,但是会提高额外开销。
显式提交偏移量
- 总是在处理完事件后再提交偏移量;
- 提交频度是性能和重复消息数量之间的权衡;
- 确保对提交的偏移量心里有数:在轮询(poll)中提交的偏移量是读取到的最新偏移量,而不是处理到的最新偏移量;
- 再均衡:需要注意对再均衡的处理;
- 消费者可能需要重试:有两种处理方式,一种是在遇到可重试错误时,提交最后一个处理成功的偏移量,然后把还没有处理好的消息保存到缓冲区里(这样下一个轮询就不会把它们覆盖掉),调用消费者的 pause()方怯来确保其他的轮询不会返回数据(不需要担心在重试时缓冲区隘出),在保持轮询的同时尝试重新处理。如果重试成功,或者重试次数达到上限井决定放弃,那么把错误记录下来井丢弃消息,然后调用resume()方能让消费者继续从轮询里获取新数据。另一种则是在遇到可重试错误时,把错误写入一个独立的主题,然后继续。一个独立的消费者群组负责从该主题上读取错误消息,井进行重试,或者使用其中的一个消费者同时从该主题上读取错误消息并进行重试,不过在重试时需要暂停该主题。这种模式有点像其他消息系统里的 dead-letter-queue。
- 消费者可能需要维护状态;
- 长时间处理:将数据交给线程池来处理,加快速度。并在保持轮询的状态下调用pause(),以防止再均衡。
模式
消息模式 (schema) Json XML
主题和分区
消息通过主题进行分类,主题可以被分为若干个分区。
通过分区来实现数据冗余和伸缩性。一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能。
Topic的删除
实际测试的时候,发现删除topic时,kafka只是将其marked as deleted,并未真正删除,此时继续向topic发送消息仍然成功,需要在server.properties设置一个属性:delete.topic.enable=true,这样才是真正的删除了topic。一般清空topic内的数据可以删除topic,不用重新创建,直接再向topic发送消息即可。具体的操作可以查看某一topic数据清空
生产者
创建消息。一般情况下,一个消息会被发布到一个特定的主题上。生产者在默认情况下把消息均衡地分布到主题的所有分区上,而并不关心特定消息会被写到哪个分区。不过,在某些情况下,生产者会把消息直接写到指定的分区。这通常是通过消息键和分区器来实现的,分区器为键生成一个散列值,并将其映射到指定的分区上。这样可以保证包含同一个键的消息会被写到同一个分区上。生产者也可以使用自定义的分区器,根据不同的业务规则将消息映射到分区。
序列化器将消息序列化成字节数组,以网络传输。
如果消息成功写入 Kafka,就返回 一 个RecordMetaData 对象,它包含了主题和分区信息,以及记录在分区里的偏移量。如果写入失败, 则会返回 一个错误。
消息发送方式
- 发送并忘记
- 同步发送
- 异步发送
KafkaProducer一般会发生两类错误。其中一类是可重试错误 ,这类错误可以通过重发消息来解决。比如对于连接错误,可以通过再次建立连接来解决,“无主( no leader )” 错误则可以通过重新为分区选举首领来解决。 KafkaProducer可以被配置成自动重试,如果在多次重试后仍无能解决问题,应用程序会收到一个重试异常。另一类错误无出通过重试解决,比如“消息太大”异常。对于这类错误, KafkaProducer不会进行任何重试,直接抛出异常。
注意:
librdkafka不像java客户端那样,可以通过future.get()实现同步发送。所以,如果broker不能连通的话,send方法还是可以正常将消息放入队列。这会导致两个问题
1、我们的客户端是不会知道broker已经挂掉了,因而不能对这种情况作出及时处理,导致消息全部堆积在内存中,如果此时不幸,我们的客户端也挂掉了,那这部分消息就全部丢失了。
2、如果broker一直没有恢复,而我们一直向队列中写数据的话,producer中有一个选项message.timeout.ms,如果超过了设定的消息超时时间,那么会有线程清理队列中的数据,导致消息丢失,而如果将时间设置为0(永不超时)的话,将导致客户端内存撑满。
此问题可以通过设置发送回调的处理方式来应对。
生产者重要配置
- acks指定了必须要有多少个分区副本收到消息,生产者才会认为消息写入是成功的。
acks=0 , 生产者不等待服务器接收响应,吞吐量高。
acks=1 ,需要等待首领确认,可能产生数据丢失(首领切换时),吞吐量取决于同步还是异步发送。
acks=all ,只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自
服务器的成功响应。这种模式是最安全的,它可以保证不止一个服务器收到消息,就算
有服务器发生崩溃,整个集群仍然可以运行。不过,它的延迟比 acks=1时更高,因为要等待不只一个服务器节点接收消息。
- buffer.memory
内存缓冲区的大小,生产者发送数据速度超过服务器接收时,这里会被占满,send要么阻