朋友的公司做的是西南某边境省份网红新能源车的数据处理,由于新能源车的火爆,从年初从现在,数据量已经翻番。但与此同时,服务器却没有多少增加。无奈之下,只能暂时将kafka的数据存储时间由之前的1天改为6小时,5小时。其实有点危险,如果在凌晨发生什么异常生产事故导致实时任务退出,如果运维没有及时通知,导致第二天上班时已经超过5小时,那么这部份数据可能就丢了。但这也是无奈之举。

本来运行一段时间倒也好好的。今天早上,突然在微信上问我一个问题。

kafka offset存放在哪里_spark

一时间有点懵,我很确信这个问题我肯定遇到过,且解决过。但我一时之间还真没马上想起来解决方案。

我立马翻出以前写的 Structured Streaming代码,马上意识到,这不就是auto.offset.reset的问题吗?在异常信息里已经提示得很明确了。
为什么我会第一时间没反应过来呢?因为不管是spark streaming,spark Structured Streaming,flink实时任务,只要涉及到kafka作为数据源,配置auto.offset.reset已经是一个标配。无脑上这个配置。反而一时间想不起来了。

朋友公司的代码可以追溯到在大数据领域比较遥远的2016年,那是spark1.0的时代,采用spark streaming,而现在spark官方早都已经不维护spark streaming模块了。当时的基础代码一直被复制沿用,改的也只是业务代码。所以auto.offset.reset这个配置就一直被忽略了。正常情况下,也不会抛出问题,但就怕特殊情况。

这里做一个记录。
简单说auto.offset.reset配置在没有获取到group offset的时候(比如第一次读取,或者数据被删除,就像朋友发的情况,因为kafka数据过期被删除或者异常手动被删等等),有一个替代策略。
根据不同的大数据引擎它一般有3个配置值 。

LATEST, 从最新的开始读
EARLIEST,从最晚的开始读
NONE 默认

也有group offset直接设置具体值的。
根据kafka的
官方文档的表述。

What to do when there is no initial offset in Kafka or if the current offset does not exist any more on the server (e.g. because that data has been deleted):

-   earliest: automatically reset the offset to the earliest offset
-   latest: automatically reset the offset to the latest offset
-   none: throw exception to the consumer if no previous offset is found for the consumer's group
-   anything else: throw exception to the consumer.

| Type:         | string                   |
| ------------- | ------------------------ |
| Default:      | latest                   |
| Valid Values: | [latest, earliest, none] |
| Importance:   | medium

一点疑惑。
在spark官方jira看到一些相当的异常描述,比如这哥们,说它设置了相关的配置,但是还是出现了此种异常。

kafka offset存放在哪里_kafka_02


朋友也有此方面的疑惑,他认为或许auto.offset.reset配置只针对初始化,但在kafka分区或者分区数据被删除的情况下,此配置并不生效。

我记得在去年做过此方面的测试。一个测试kafka topic,数据失效时间设置为2天,先消费一波,等过3天,再去生产数据并消费,并没有出现异常。

今天正好再次做一次试验,手头刚好有flink代码环境。就以flink 1.14 + kafka 2.x为例。

示例代码

"CREATE TABLE test (\n"
        + "  `key` STRING,\n"
        + "  `time` TIMESTAMP(3),\n"
        + "  `price` float,\n"
        + "  WATERMARK FOR `time` AS `time` - INTERVAL '10' SECOND"
        + ") WITH (\n"
        + "  'connector' = 'kafka',\n"
        + "  'topic' = 'test2',\n"
        + "  'properties.bootstrap.servers' = '192.168.124.122:9092',\n"
        + "  'properties.group.id' = 'windowGroup',\n"
        + "  'scan.startup.mode' = 'latest-offset',\n"
        + "  'format' = 'json',\n"
        + "  'json.fail-on-missing-field' = 'false',\n"
        + "  'json.ignore-parse-errors' = 'true' \n"
        + ")"

flink在table api的kakfa connector中,auto.offset.reset的配置变为scan.startup.mode,看这表述,确实感觉有点误会,像是只是启动初始化的时候生效。

先把kafka分区删除一个,代码走一波。

kafka offset存放在哪里_kafka offset存放在哪里_03

后台并没有异常错误及退出。可以看到,最先我删除了index和log,然后发送了数据,kafka生成了新的inde和log,并且flink程序正常消费。

然后我把代码中scan.startup.mode配置删了。然后再把kafka中一个分区的index和log删掉。启动。然后报错了。
跟spark的异常信息相似。
Undefined offset with no reset policy for partitions: [test2-0]

可见auto.offset.reset/scan.startup.mode跟官网和我之前预期是相符的。没有问题。

简单看一下flink 1.14相关源码,关键位置在

SubscriptionState.resetMissingPositions

kafka offset存放在哪里_kafka_04

可是以看到,如果获取不到Partation相关信息且没有设置scan.startup.mode,那么OffsetResetStrategy就是默认的NONE,这种情况下就会加入partitionsWithNoOffsets,然后当partitionsWithNoOffsets不为空的时候,就会抛出异常。

苍茫之天涯,乃吾辈之所爱也;浩瀚之程序,亦吾之所爱也,然则何时而爱耶?必曰:先天下之忧而忧,后天下之爱而爱也!