kafka系列13——第7章1——稳定性

 🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。

✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!

🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文||一分钟看完我的几百篇博客

🔥温馨提示:划到文末发现专栏彩蛋   点击这里直接传送

🔥本篇概览:详细讲解了kafka系列13——第7章1——稳定性。🌈⭕🔥


【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】


🔥   微服务全集

🔥   kafka全集

🔥   前一篇章


🌈引出

Apache的kafka是一个分布式的消息发布订阅中间件。具有高吞吐、可扩展和容错性等特点。主要用于处理大规模的流式数据

本博客从各个方面详细讲解了kafka的机制,并实际上手使用之,好好学完定会习得大功。(bushi,上一次面试就噶在kafka上了,好好对待之。)


7章 稳定性

*tips 学完这一章你可以
深入学习 Kafka在保证高性能、高吞吐的同时通过各种机制来保证高可用性

        Kafka的消息传输保障机制非常直观。当 producer broker 发送消息时,一旦这条消息被提交 ,由于副本机制 的存在,它就不会丢失。但是如果 producer 发送数据给 broker 后,遇到的网络问题而造成通信中断,那producer 就无法判断该条消息是否已经提交 commit )。虽然 Kafka 无法确定网络故障期间到底发生了什么,但是producer 可以 retry 多次,确保消息已经正确传输到 broker 中,所以目前Kafka 实现的是 at least once[至少提交一次]


7.1 幂等性

场景

        所谓幂等性, 就是对接口的多次调用所产生的结果和调用一次是一致的。 生产者在进行重试的时候有可 能会重复写入消息,二使用Kafka的幂等性功能就可以避免这种情况。
幂等性是有条件的:
        只能保证 Producer 在单个会话内不丟不重,如果 Producer 出现意外挂掉再重启是无法保证的 (幂等性情况下,是无法获取之前的状态信息,因此是无法做到跨会话级别的不丢不重);
幂等性不能跨多个 Topic-Partition ,只能保证单个 partition 内的幂等性,当涉及多个 Topic--Partition 时,这中间的状态并没有同步。
        Producer 使用幂等性的示例非常简单,与正常情况下 Producer 使用相比变化不大,只需要把 Producer 的配置 enable.idempotence 设置为 true 即可,如下所示:
Properties props = new Properties();
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
props.put("acks", "all"); // 当 enable.idempotence 为 true,这里默认为 all
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer producer = new KafkaProducer(props);
producer.send(new ProducerRecord(topic, "test");


7.2 事务

场景

        幂等性并不能跨多个分区运作,而事务可以弥补这个缺憾,事务可以保证对多个分区写入操作的原子性。操作的原子性是指多个操作要么全部成功,要么全部失败,不存在部分成功部分失败的可能。
        为了实现事务,应用程序必须提供唯一的transactionalId ,这个参数通过客户端程序来进行设定。

前期准备

事务要求生产者开启幂等性特性,因此通过将 transactional.id 参数设置为非空从而开启事务特性的同时需要将ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG 设置为 true (默认值为 true ),如果显示设置为false ,则会抛出异常。
KafkaProducer 提供了 5 个与事务相关的方法,详细如下:
//初始化事务,前提是配置了transactionalId
publicvoidinitTransactions()
//开启事务
publicvoidbeginTransaction()
//为消费者提供事务内的位移提交操作
publicvoidsendOffsetsToTransaction(Map<TopicPartition,OffsetAndMetadata>offsets,StringconsumerGroupId)
//提交事务
publicvoidcommitTransaction()
//终止事务,类似于回滚
publicvoidabortTransaction()

案例解析

见代码库: com.heima.kafka.chapter7.ProducerTransactionSend
消息发送端
/**
* Kafka Producer事务的使用
*/
public class ProducerTransactionSend {
public static final String topic = "topic-transaction";
public static final String brokerList = "localhost:9092";
public static final String transactionId = "transactionId";
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
StringSerializer.class.getName());
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList);
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG, transactionId);
KafkaProducer<String, String> producer = new KafkaProducer<>
(properties);
producer.initTransactions();
producer.beginTransaction();
try {
//处理业务逻辑并创建ProducerRecord
ProducerRecord<String, String> record1 = new ProducerRecord<>(topic,
"msg1");
producer.send(record1);
ProducerRecord<String, String> record2 = new ProducerRecord<>(topic,
"msg2");
producer.send(record2);
ProducerRecord<String, String> record3 = new ProducerRecord<>(topic,
"msg3");
producer.send(record3);
//处理一些其它逻辑
producer.commitTransaction();
} catch (ProducerFencedException e) {
producer.abortTransaction();
        }
    }
}
模拟事务回滚案例
        
try {
//处理业务逻辑并创建ProducerRecord
ProducerRecord<String, String> record1 = new ProducerRecord<>(topic,
"msg1");
producer.send(record1);
//模拟事务回滚案例
System.out.println(1/0);
ProducerRecord<String, String> record2 = new ProducerRecord<>(topic,
"msg2");
producer.send(record2);
ProducerRecord<String, String> record3 = new ProducerRecord<>(topic,
"msg3");
producer.send(record3);
//处理一些其它逻辑
producer.commitTransaction();
} catch (ProducerFencedException e) {
producer.abortTransaction();
}
从上面案例中, msg1 发送成功之后,出现了异常事务进行了回滚,则 msg1 消费端也收不到消息。

7.3 控制器

        在Kafka 集群中会有一个或者多个 broker ,其中有一个 broker 会被选举为控制器( Kafka Controller ), 它负责管理整个集群中所有分区和副本的状态。当某个分区的leader 副本出现故障时,由控制器负责为 该分区选举新的leader 副本。当检测到某个分区的 ISR 集合发生变化时,由控制器负责通知所有 broker 更新其元数据信息。当使用kafka-topics.sh 脚本为某个 topic 增加分区数量时,同样还是由控制器负责分 区的重新分配。
        Kafka中的控制器选举的工作依赖于 Zookeeper ,成功竞选为控制器的 broker 会在 Zookeeper 中创 建/controller 这个临时( EPHEMERAL )节点,此临时节点的内容参考如下:

ZooInspector管理

        使用zookeeper 图形化的客户端工具 (ZooInspector) 提供的 jar 来进行管理,启动如下:
1 、定位到 jar 所在目录
2 、运行 jar 文件 java -jar zookeeper-dev-ZooInspector.jar
3 、连接 Zookeeper
{"version":1,"brokerid":0,"timestamp":"1529210278988"}
        其中version在目前版本中固定为1 brokerid 表示称为控制器的 broker 的id编号,timestamp表示竞选 称为控制器时的时间戳。
        在任意时刻,集群中有且仅有一个控制器每个broker 启动的时候会去尝试去读取 /controller 节点的 brokerid的值,如果读取到 brokerid 的值不为 -1 ,则表示已经有其它 broker 节点成功竞选为控制器,所 以当前broker 就会放弃竞选;如果 Zookeeper 中不存在 /controller 这个节点,或者这个节点中的数据异 常,那么就会尝试去创建/controller 这个节点,当前 broker 去创建节点的时候,也有可能其他 broker 同 时去尝试创建这个节点,只有创建成功的那个broker 才会成为控制器,而创建失败的 broker 则表示竞选 失败。每个broker 都会在内存中保存当前控制器的 brokerid 值,这个值可以标识为 activeControllerId
        Zookeeper中还有一个与控制器有关的 /controller_epoch 节点,这个节点是持久 节 点,节点中存放的是一个整型的controller_epoch 值。 controller_epoch 用于记录控制器发生变更的次 数,即记录当前的控制器是第几代控制器,我们也可以称之为“ 控制器的纪元

        controller_epoch的初始值为 1 ,即集群中第一个控制器的纪元为 1 ,当控制器发生变更时,每选出一个 新的控制器就将该字段值加1 。每个和控制器交互的请求都会携带上 controller_epoch 这个字段,如果 请求的controller_epoch 值小于内存中的 controller_epoch 值,则认为这个请求是向已经过期的控制器 所发送的请求,那么这个请求会被认定为无效的请求。如果请求的controller_epoch 值大于内存中的 controller_epoch值,那么则说明已经有新的控制器当选了。由此可见, Kafka 通过 controller_epoch 来 保证控制器的唯一性,进而保证相关操作的一致性。
        具备控制器身份的broker 需要比其他普通的 broker 多一份职责,具体细节如下:
1 、监听 partition 相关的变化。
2 、监听 topic 相关的变化。
3 、监听 broker 相关的变化

7.4 可靠性保证

1. 可靠性保证:

        确保系统在各种不同的环境 下能够发生 一致 的行为。

2. Kafka的保证

        保证分区内消息的顺序
        如果使用同一个生产者 同一个分区 写入消息,而且消息 B 在消息 A 之后写入 。那么Kafka 可以保证消息 B 的偏移量比消息 A 的偏移量大,而且消费者会先读取消息 A 再 读取消息B ,只有当消息被写入分区的所有同步副本时(文件系统缓存),它才被认为是已提交 。生产者可以选择接收不同类型的确认,控制参数 acks 只要还有一个副本是活跃的,那么已提交的消息就不会丢失。
消费者只能读取已经提交的消息。

失效副本

        怎么样判定一个分区是否有副本是处于同步失效状态的呢?从Kafka 0.9.x 版本开始通过唯一的一个参数 replica.lag.time.max.ms(默认大小为 10,000 )来控制, ISR 中的一个 follower 副本滞后 leader 副本 的时间超过参数replica.lag.time.max.ms 指定的值时即判定为副本失效,需要将此 follower 副本剔出除 ISR之外。具体实现原理很简单,当 follower 副本将 leader 副本的 LEO Log End Offset 每个分区最后 一条消息的位置)之前的日志全部同步时,则认为该follower 副本已经追赶上 leader 副本,此时更新该 副本的lastCaughtUpTimeMs 标识。
        Kafka 的副本管理器( ReplicaManager )启动时会启动一个副本过 期检测的定时任务,而这个定时任务会定时检查当前时间与副本的lastCaughtUpTimeMs 差值是否大于 参数replica.lag.time.max.ms 指定的值。千万不要错误的认为 follower 副本只要拉取 leader 副本的数据 就会更新lastCaughtUpTimeMs ,试想当 leader 副本的消息流入速度大于 follower 副本的拉取速度时, follower副本一直不断的拉取 leader 副本的消息也不能与 leader 副本同步,如果还将此 follower 副本置 于ISR 中,那么当 leader 副本失效,而选取此 follower 副本为新的 leader 副本,那么就会有严重的消息丢 失。

副本复制

        Kafka 中的每个主题分区都被复制了 n 次,其中的 n 是主题的复制因子 replication factor )。这允许 Kafka 在集群服务器发生故障时自动切换到这些副本,以便在出现故障时消息仍然可用。 Kafka 的复制 是以分区为粒度的,分区的预写日志被复制到 n 个服务器。 在 n 个副本中,一个副本作为 leader ,其 他副本成为 followers
        
        顾名思义,producer 只能往 leader 分区上写数据(读也只能从 leader 分区上 进行),followers 只按顺序从 leader 上复制日志。 一个副本可以不同步Leader 有如下几个原因
        慢副本:
在一定周期时间内follower不能追赶 leader
最 常见的原因之一是I / O瓶颈导致follower 追加复制消息速度慢于从 leader 拉取速度。
       
       卡住副本:
在一定 周期时间内follower停止从leader 拉取请求。 follower replica 卡住了是由于 GC 暂停或 follower 失效或死 亡。

        新启动副本:
        当用户给主题增加副本因子时,新的follower 不在同步副本列表中,直到他们完全赶上了
leader 日志。
        如何确定副本是滞后的 ?replica.lag.max.messages=4
        在服务端现在只有一个参数需要配置replica.lag.time.max.ms 。这个参数解释 replicas 响应 partition leader的最长等待时间。检测卡住或失败副本的探测 —— 如果一个 replica 失败导致发送拉取请求时间间 隔超过replica.lag.time.max.ms Kafka 会认为此 replica 已经死亡会从同步副本列表从移除。检测慢副本机制发生了变化—— 如果一个 replica 开始落后 leader 超过 replica.lag.time.max.ms Kafka 会认为太缓慢并且会从同步副本列表中移除。除非replica 请求 leader 时间间隔大于 replica.lag.time.max.ms ,因 此即使leader 使流量激增和大批量写消息。 Kafka 也不会从同步副本列表从移除该副本。

7.5 一致性保证

        在leader 宕机后,只能从 ISR 列表中选取新的 leader ,无论 ISR 中哪个副本被选为新的 leader ,它都 知道HW 之前的数据,可以保证在切换了 leader 后,消费者可以继续看到 HW 之前已经提交的数 据。
        HW的截断机制选出了新的 leader ,而新的 leader 并不能保证已经完全同步了之前 leader 的所有 数据,只能保证HW 之前的数据是同步过的,此时所有的 follower 都要将数据截断到 HW的位置, 再和新的leader 同步数据,来保证数据一致 当宕机的 leader 恢复,发现新的 leader 中的数据和 自己持有的数据不一致,此时宕机的leader 会将自己的数据截断到宕机之前的hw 位置,然后同步 新leader 的数据。宕机的 leader 活过来也像 follower 一样同步数据,来保证数据的一致性。
  • HW
    • (High Watermark)
    • 高水位,标识了一个特定的 offset ,消费者只能拉取到这个 offset 之前的消息

Leader Epoch引用

数据丢失场景
数据出现不一致场景

Kafka 0.11.0.0. 版本解决方案
        造成上述两个问题的根本原因在于HW 值被用于衡量副本备份的成功与否以及在出现failture时作为日志截断的依据,但HW值的更新是异步延迟的,特别是需要额外的FETCH请求处理流程才能更新,故这中间发生的任何崩溃都可能导致HW 值的过期。鉴于这些原因, Kafka 0.11 引入了 leader epoch 来取代 HW值。Leader 端多开辟一段内存区域专门保存 leader epoch 信息,这样即使出现上面的两个场景也能很好地规避这些问题。
        所谓leader epoch 实际上是一对值: epoch offset epoch 表示 leader 的版本号,从 0 开始,当 leader变更过 1 次时 epoch 就会 +1 ,而 offset 则对应于该 epoch 版本的 leader 写入第一条消息的位移。因此假设有两对值:
(0, 0)
(1, 120)
则表示第一个 leader 从位移 0 开始写入消息;共写了 120 [0, 119] ;而第二个 leader 版本号是 1 ,从位移120处开始写入消息。
leader broker 中会保存这样的一个缓存,并定期地写入到一个 checkpoint 文件中。

避免数据丢失:

 

避免数据不一致

7.6 消息重复的场景及解决方案

7.6.1 生产者端重复

生产发送的消息没有收到正确的 broke 响应,导致 producer 重试。
producer 发出一条消息, broke 落盘以后因为网络等种种原因发送端得到一个发送失败的响应或者网络 中断,然后producer 收到一个可恢复的 Exception 重试消息导致消息重复。
解决方案:
1、启动kafka的幂等性
        要启动kafka 的幂等性,无需修改代码,默认为关闭,需要修改配置文件:enable.idempotence=true 同时要求 ack=all retries>1
2ack=0,不重试。

7.6.2 消费者端重复

1、根本原因
        数据消费完没有及时提交offset broker
        

解决方案

1、取消自动自动提交
        每次消费完或者程序退出时手动提交。这可能也没法保证一条重复。
2、下游做幂等
        一般的解决方案是让下游做幂等或者尽量每消费一条消息都记录offset ,对于少数严格的场景可能需要 把offset 或唯一 ID, 例如订单 ID 和下游状态更新放在同一个数据库里面做事务来保证精确的一次更新或者 在下游数据表里面同时记录消费offset ,然后更新下游数据的时候用消费位点做乐观锁拒绝掉旧位点的 数据更新。

7.7 __consumer_offsets

        _consumer_offsets是一个内部 topic ,对用户而言是透明的,除了它的数据文件以及偶尔在日志中出现 这两点之外,用户一般是感觉不到这个topic 不过我们的确知道它保存的是 Kafka新版本consumer 的位移信息。

7.7.1 何时创建

        一般情况下,当集群中一有消费者消费消息时会自动创建主题__consumer_offsets ,分区数可以通过 offsets.topic.num.partitions参数设定,默认值为 50 ,如下:

7.7.2 解析分区
见代码库: com.heima.kafka.chapter7.ConsumerOffsetsAnalysis
获取所有分区


总结

        本章主要讲解了Kafka 相关稳定性的操作,包括幂等性、事务的处理,同时对可靠性保证与一致性保证 做了讲解,讲解了消息重复以及解决方案。



💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖

热门专栏推荐

🌈🌈计算机科学入门系列                     关注走一波💕💕

🌈🌈CSAPP深入理解计算机原理        关注走一波💕💕

🌈🌈微服务项目之黑马头条                 关注走一波💕💕

🌈🌈redis深度项目之黑马点评            关注走一波💕💕

🌈🌈JAVA面试八股文系列专栏           关注走一波💕💕

🌈🌈JAVA基础试题集精讲                  关注走一波💕💕   

🌈🌈代码随想录精讲200题                  关注走一波💕💕


总栏

🌈🌈JAVA基础要夯牢                         关注走一波💕💕  

🌈🌈​​​​​​JAVA后端技术栈                          关注走一波💕💕  

🌈🌈JAVA面试八股文​​​​​​                          关注走一波💕💕  

🌈🌈JAVA项目(含源码深度剖析)    关注走一波💕💕  

🌈🌈计算机四件套                               关注走一波💕💕  

🌈🌈数据结构与算法                           ​关注走一波💕💕  

🌈🌈必知必会工具集                           关注走一波💕💕

🌈🌈书籍网课笔记汇总                       关注走一波💕💕         



📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spark streaming 是基于 spark 引擎的实时数据处理框架,可以通过集成 kafka 来进行数据流的处理。然而,在使用 spark streaming 进行 kafka 数据流处理时,可能会遇到一些坑。 首先,要注意 spark streaming 和 kafka 版本的兼容性。不同版本的 spark streaming 和 kafka 可能存在一些不兼容的问题,所以在选择版本时要特别留意。建议使用相同版本的 spark streaming 和 kafka,以避免兼容性问题。 其次,要注意 spark streaming 的并行度设置。默认情况下,spark streaming 的并行度是根据 kafka 分区数来决定的,可以通过设置 spark streaming 的参数来调整并行度。如果并行度设置得过高,可能会导致任务处理过慢,甚至出现 OOM 的情况;而设置得过低,则可能无法充分利用集群资源。因此,需要根据实际情况进行合理的并行度设置。 另外,要注意 spark streaming 和 kafka 的性能调优。可以通过调整 spark streaming 缓冲区的大小、批处理时间间隔、kafka 的参数等来提高性能。同时,还可以使用 spark streaming 的 checkpoint 机制来保证数据的一致性和容错性。但是,使用 checkpoint 机制可能会对性能产生一定的影响,所以需要权衡利弊。 最后,要注意处理 kafka 的消息丢失和重复消费的问题。由于网络或其他原因,可能会导致 kafka 的消息丢失;而 spark streaming 在处理数据时可能会出现重试导致消息重复消费的情况。可以通过配置合适的参数来解决这些问题,例如设置 KafkaUtils.createDirectStream 方法的参数 enable.auto.commit,并设置适当的自动提交间隔。 总之,在使用 spark streaming 进行 kafka 数据流处理时,需要留意版本兼容性、并行度设置、性能调优和消息丢失重复消费等问题,以免踩坑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值