Kafka基本概念
Kafka 是一个 “数据流” 平台,能够帮助开发者更好地专注于核心业务。Kafka 支持发布和订阅数据(消息),能够将其保存,并进行处理。
- 关键 “术语”
- 消息(message):Kafka 的数据单元,称为 “消息”。消息由键(key)、值(value)组成,键作为元数据,是可选的,值即为消息的内容。消息写入到 Kafka 时,按照主题和分区分组,以批次(batch)的形式写入。需要根据时间延迟和吞吐量,权衡批次的 “大小”:批次越大,单位时间内处理的消息越多,单个消息的传输时间越长。
- 主题(topic) & 分区(partition): Kafka 的消息通过 “主题” 进行分类。主题能够被划分为若干 “分区”。消息以追加的方式写入分区,并以 FIFO 的方式读取,保证了消息在单个分区内的顺序。
Kafka 支持分区的 “复制机制”,以提供消息冗余,分区的 “实例” 称为 “副本”(replica),其中之一作为 “首领副本”(leader replica)。 - broker:Kafka 服务器通常被称为 broker,broker 负责 “消息保留策略”:当消息超过保留时间,或者消息的总量达到大小限制,旧的消息将过期并且被删除。
多个 broker 能够组成 Kafka 集群,其中之一作为 “集群管理器”。单个分区的副本必须位于不同的 broker。
- kafka的优势
- 生产者与消费者完全独立,特别的,Kafka 支持多个消费者独立地读取相同的数据 “流”
- 持久性,消息由磁盘存储,并支持自定义的保留规则
- 伸缩性 & 高性能,能够以集群的方式水平扩展,并具备故障容错能力
数据丢失
重复消费和数据丢失问题主要分三种情况:
1、生产者弄丢消息
生产者发送消息成功后不等 Kafka 同步完成的确认继续发送下一条消息,在发的过程中如果 Leader Kafka 宕机了,但 Producer 并不知情,发出去的信息 Broker 就收不到导致数据丢失。解决方案是将 request.required.acks 设置为 -1,表示 Leader 和 Follower 都收到消息才算成功。
request.required.acks=0 表示发送消息即完成发送,不等待确认(可靠性低,延迟小,最容易丢失消息)
request.required.acks=1 表示当 Leader 收到消息返回确认后才算成功
2、消费者弄丢消息:
消费者有两种情况,一种是消费的时候自动提交偏移量导致数据丢失:拿到消息的同时将偏移量加一,如果业务处理失败,下一次消费的时候偏移量已经加一了,上一个偏移量的数据丢失了。
另一种是手动提交偏移量导致重复消费:等业务处理成功后再手动提交偏移量,有可能出现业务成功,偏移量提交失败,那下一次消费又是同一条消息。怎么解决呢?
这是一个 or 的问题,偏移量要么手动提交要么自动提交,对应的问题是要么数据丢失要么重复消费。如果消息要求实时性高,丢个一两条没关系的话可以选择自动提交偏移量。如果消息一条都不能丢的话可以将业务设计成幂等(redis分布式锁实现幂等),不管消费多少次都一样。
3、Kafka 弄丢了数据
这块比较常见的一个场景,就是 Kafka 某个 broker 宕机,然后重新选举 partition 的 leader。大家想想,要是此时其他的 follower 刚好还有些数据没有同步,结果此时 leader 挂了,然后选举某个 follower 成 leader 之后,不就少了一些数据?这就丢了一些数据啊。
生产环境也遇到过,我们也是,之前 Kafka 的 leader 机器宕机了,将 follower 切换为 leader 之后,就会发现说这个数据就丢了。
所以此时一般是要求起码设置如下 4 个参数:
给 topic 设置 replication.factor 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
在 Kafka 服务端设置 min.insync.replicas 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
在 producer 端设置 acks=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了。
在 producer 端设置 retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了。
我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
kafka事物
Kafka 事务性的使用方法也非常简单,用户只需要在 Producer 的配置中配置 transactional.id,通过 initTransactions() 初始化事务状态信息,再通过 beginTransaction() 标识一个事务的开始,然后通过 commitTransaction() 或 abortTransaction() 对事务进行 commit 或 abort
Properties props = new Properties();
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("client.id", "ProducerTranscationnalExample");
props.put("bootstrap.servers", "localhost:9092");
props.put("transactional.id", "test-transactional");
props.put("acks", "all");
KafkaProducer producer = new KafkaProducer(props);
producer.initTransactions();
try {
String msg = "matt test";
producer.beginTransaction();
producer.send(new ProducerRecord(topic, "0", msg.toString()));
producer.send(new ProducerRecord(topic, "1", msg.toString()));
producer.send(new ProducerRecord(topic, "2", msg.toString()));
producer.commitTransaction();
} catch (ProducerFencedException e1) {
e1.printStackTrace();
producer.close();
} catch (KafkaException e2) {
e2.printStackTrace();
producer.abortTransaction();
}
producer.close();