Kafka 位移提交的正确姿势

你说你 Kafka 用了很多年了,但是位移提交的这些细节你未必清楚。

不久前有个同事出去面试了,他说在 Kafka 问题上被面试官藐视了。

同事:我做了一个消息总线,简化了业务团队消息的收发,并且提供了消息管理功能。

面试官:你做的这个东西,有什么深入价值吗?

同事:目的在于封装技术的复杂度,使业务团队更加专注于业务。

面试官:这个价值并没有真实打动我,这不是很简单吗?

简单吗?那我问你:Kafka 是如何进行位移提交的?(你不一定能答得全面)

回到问题本身,Kafka 位移提交的方式有两种:有自动提交和手动提交。

一、自动提交

在默认情况下,Consumer 每 5 秒自动提交一次位移。现在,我们假设提交位移之后的 3 秒发生了 Rebalance 操作。在 Rebalance 之后,所有 Consumer 从上一次提交的位移处继续消费,但该位移已经是 3 秒前的位移数据了,故在 Rebalance 发生前 3 秒消费的所有数据都要重新再消费一次。

虽然你能够通过减少 auto.commit.interval.ms 的值来提高提交频率,但这么做只能缩小重复消费的时间窗口,不可能完全消除它。这是自动提交机制的一个缺陷。

弥补这个问题,就要用到手动提交了。

二、手动同步提交

下面这段代码展示了 commitSync() 的使用方法。

scss 复制代码
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    process(records); // 处理消息
    try {
        consumer.commitSync();
    } catch (CommitFailedException e) {
        handle(e); // 处理提交失败异常
    }
}

手动提交位移的好处就在于更加灵活,你完全能够把控位移提交的时机和频率,可以在消费完成后立马提交位移。

但是,它也有一个缺陷,就是在调用 commitSync() 时,Consumer 程序会处于阻塞状态,直到远端的 Broker 返回提交结果,这个状态才会结束。在任何系统中,因为程序而非资源限制而导致的阻塞都可能是系统的瓶颈,会影响整个应用程序的 TPS。

三、手动异步提交

由于它是异步的,Kafka 提供了回调函数(callback),供你实现提交之后的逻辑,比如记录日志或处理异常等。下面这段代码展示了调用 commitAsync() 的方法。

scss 复制代码
while (true) {
    ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
    process(records); // 处理消息
    consumer.commitAsync((offsets, exception) -> {
        if (exception != null) {
            handle(exception);
        }
    });
}

commitAsync 的问题在于,出现问题时它不会自动重试。因为它是异步操作,倘若提交失败后自动重试,那么它重试时提交的位移值可能早已经"过期"或不是最新值了。因此,异步提交的重试其实没有意义,所以 commitAsync 是不会重试的。

那同步提交、异步提交那我该怎么选呢?

小孩子才做选择题,成年人当然是全部都要啊!

四、手动同异步结合提交

下面这段代码,将两个 API 方法结合使用进行手动提交。

scss 复制代码
try {
    while (true) {
        ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
        process(records); // 处理消息
        consumer.commitAysnc(); // 使用异步提交规避阻塞
    }
} catch (Exception e) {
    handle(e); // 处理异常
} finally {
    try {
        consumer.commitSync(); // 最后一次提交使用同步阻塞式提交
    } finally {
        consumer.close();
    }
}

对于常规性、阶段性的手动提交,我们调用 commitAsync() 避免程序阻塞,而在 Consumer 要关闭前,我们调用 commitSync() 方法执行同步阻塞式的位移提交,以确保 Consumer 关闭前能够保存正确的位移数据。将两者结合后,我们既实现了异步无阻塞式的位移管理,也确保了 Consumer 位移的正确性。

所以,如果你需要自行编写代码开发一套 Kafka Consumer 应用,那么我推荐你使用上面的代码范例来实现手动的位移提交。

(看到这里,面试官陷入了深深的沉默中...)

封面图

相关推荐
可可嘻嘻大老虎3 分钟前
SpringBoot拦截器防重复提交实战
java·spring boot·后端
RainCityLucky8 分钟前
Java Swing 自定义组件库分享(十一)
java·笔记·后端
cheems95278 分钟前
[开发日记]Spring Boot + MyBatis-Plus 抽奖系统排障实录:从 JWT 被拦截到雪花 ID 失控,我是怎样一步步修通登录与人员列表的
spring boot·后端·mybatis
古城小栈9 分钟前
Rustix库:Rust 系统编程 的 基石
开发语言·后端·rust
我登哥MVP12 分钟前
Spring Boot 从“会用”到“精通”:Rest风格原理
java·spring boot·后端·spring·maven·intellij-idea·mybatis
Je1lyfish14 分钟前
CMU15-445 (2025 Fall/2026 Spring) Project#4 - Concurrency Control
开发语言·数据库·c++·笔记·后端·算法·系统架构
我登哥MVP15 分钟前
Spring Boot 从“会用”到“精通”:静态资源原理
java·spring boot·后端·spring·tomcat·maven·intellij-idea
接着奏乐接着舞16 分钟前
springcloud xxl-job
后端·spring·spring cloud
我是一颗柠檬16 分钟前
【Redis】Cluster集群Day11(2026年)
数据库·redis·后端·缓存
nvd1117 分钟前
从 Spring 到 Quarkus:为什么依赖注入正在从“运行时”退回“编译期”?
java·后端·spring