[Flink/Java] Flink Job 运行问题 FAQ

概述: Flink Job 运行问题 FAQ

历次处理Flink任务的错误情况,一般原因有:

  • 与第三方资源(数据库、OSS等)的网络不互通(搭建环境的早期阶段)、网络不稳定
  • 配置错误 (url / 用户名 / 密码; 大小写、空格等特殊字符)
  • 集群/队列的CU资源不足
  • Flink Job 的 JVM内存不足
  • Flink CDC Job中mysql binlog过期或失效
  • checkpoint保存失败
  • Flink 程序的业务逻辑、数据量太大:导致性能缓慢,导致 checkpoint 超时
  • Flink程序中的依赖组件(OSS、MYSQL、Redis、OLAP数据库等)不稳定/运行崩溃,导致 checkpoint 超时
  • ...

Q: Flink运行时报java.lang.IllegalStateException: Buffer pool is destroyed.

问题描述

  • Flink运行时报java.lang.IllegalStateException: Buffer pool is destroyed.

且这个报错在日志中与"Could not forward element to next operator"同时存在。

java.lang.RuntimeException: Buffer pool is destroyed.
    at org.apache.flink.streaming.runtime.io.RecordWriterOutput.pushToRecordWriter(RecordWriterOutput.java:110) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.io.RecordWriterOutput.collect(RecordWriterOutput.java:89) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.io.RecordWriterOutput.collect(RecordWriterOutput.java:45) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:718) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:696) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.StreamMap.processElement(StreamMap.java:41) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.pushToOperator(OperatorChain.java:579) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:554) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.tasks.OperatorChain$CopyingChainingOutput.collect(OperatorChain.java:534) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:718) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.AbstractStreamOperator$CountingOutput.collect(AbstractStreamOperator.java:696) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.StreamSourceContexts$NonTimestampContext.collect(StreamSourceContexts.java:104) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at com.ucarinc.framework.flink.connectors.flexq.FlexQSource.run(FlexQSource.java:204) ~[flink-connector-flexq-1.8.500-20191206.054312-28.jar:1.8.500-SNAPSHOT]
    at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:93) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:57) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.tasks.SourceStreamTask.run(SourceStreamTask.java:97) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:300) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.taskmanager.Task.run(Task.java:711) [flink-runtime_2.11-1.8.1.jar:1.8.1]
    at java.lang.Thread.run(Thread.java:745) [?:1.8.0_31]
Caused by: java.lang.IllegalStateException: Buffer pool is destroyed.
    at org.apache.flink.runtime.io.network.buffer.LocalBufferPool.internalRequestMemorySegment(LocalBufferPool.java:264) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.buffer.LocalBufferPool.requestMemorySegment(LocalBufferPool.java:240) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.buffer.LocalBufferPool.requestBufferBuilderBlocking(LocalBufferPool.java:218) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.api.writer.RecordWriter.requestNewBufferBuilder(RecordWriter.java:264) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.api.writer.RecordWriter.getBufferBuilder(RecordWriter.java:257) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.api.writer.RecordWriter.copyFromSerializerToTargetChannel(RecordWriter.java:177) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.api.writer.RecordWriter.emit(RecordWriter.java:162) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.runtime.io.network.api.writer.RecordWriter.emit(RecordWriter.java:128) ~[flink-runtime_2.11-1.8.1.jar:1.8.1]
    at org.apache.flink.streaming.runtime.io.RecordWriterOutput.pushToRecordWriter(RecordWriterOutput.java:107) ~[flink-streaming-java_2.11-1.8.1.jar:1.8.1]
    ... 18 more

问题分析

  • 一般为为任务network buffer不足。可以调整下任务的network buffer的大小。

解决方法

  • 方法1:清理/腾出运行的计算机内存资源,尔后重新提交运行 (亲测有效)

  • 方法2:高级参数中添加:taskmanager.memory.network.fraction 0.2 (默认值为0.1,可根据实际情况适当调整)

参考文献

Q: Flink中为算子配置 uid 和 name 分别有什么作用和区别?

问题描述

  • Flink中为算子配置 uid 和 name 分别有什么作用和区别?
DataStream<Tuple2<AlarmRecord, DimXxxCode>> xxxBackendAlarmRecordsWithDimXxxCodeDataStream = xxxBackendAlarmRecordsMysqlCdcDataStream
	.map( new DimXxxCodeMapFunction() )
	/**
	 * name
	 * 1. 用于给算子一个可读的名称,方便在 WEB UI 或日志中识别
	 * 2. name 的设置或调整,不影响作业的执行逻辑,仅是提高程序可维护性————便于监控和调试。
	 * 3. 允许重复(但不建议这么做)
	 */
	.name( "Map-BigdataRedis-DimXxxCode" )
	/**
	 * uid: 用户定义的算子唯一标识符
	 * 1. 在作业重启或版本升级时保持状态的一致性,其影响状态恢复和作业的稳定性,不建议随意变更
	 * 2. 当修改作业拓扑时,如果没有显式设置uid,flink会自动生成uid,这可能导致状态无法正确修复(因: flink 通过 uid 来关联状态)
	 * 3. 要求唯一、且稳定
	 */
	.uid( "Map-BigdataRedis-DimXxxCode" );
FlinkJobUtils.setParallelism(jobParameterTool, xxxBackendAlarmRecordsWithDimXxxCodeDataStream, DwdAlarmRecordsRiConstants.Parallel.MAP_DIM_Xxx_CODE_PARAM, null);

问题分析

  • name
    1. 用于给算子一个可读的名称,方便在 WEB UI 或日志中识别
    1. name 的设置或调整,不影响作业的执行逻辑,仅是提高程序可维护性————便于监控和调试。
    1. 允许重复(但不建议这么做)
  • uid: 用户定义的算子唯一标识符
    1. 在作业重启或版本升级时保持状态的一致性,其影响状态恢复和作业的稳定性,不建议随意变更
    1. 当修改作业拓扑时,如果没有显式设置uid,flink会自动生成uid,这可能导致状态无法正确修复(因: flink 通过 uid 来关联状态)
    1. 要求唯一、且稳定

参考文献

  • 在 Apache Flink 中,sinkToaddSink 是两种用于将数据写入外部存储或系统的方式。它们的主要区别在于实现方式和适用场景。

addSink

  • addSink
  • addSink 是 Flink 中较早的输出算子,适用于自定义实现的场景。它需要用户实现 SinkFunction 接口,并重写 invoke() 方法来定义数据写入逻辑。
  • 示例代码
DataStream<String> dataStream = ...; // 数据流
dataStream.addSink(
    new SinkFunction<String>() {
		@Override
		public void invoke(String value, Context context) {
			// 自定义写入逻辑
			System.out.println("写入数据: " + value);
		}
	}
);
  • 特点:

灵活性高,适合自定义需求。
需要用户手动处理容错和事务一致性。
在 Flink 1.12 之前是主要的 Sink 实现方式。

sinkTo

  • sinkTo 是 Flink 1.12 引入的新方法,基于新的 Sink API 架构,提供了更高的抽象和易用性。它支持多种内置的 Sink,如 Kafka、文件系统、JDBC 等。

  • 示例代码:

DataStream<String> dataStream = ...; // 数据流
FileSink<String> fileSink = FileSink
  .forRowFormat(new Path("output/path"), new SimpleStringEncoder<>("UTF-8"))
  .build();

dataStream.sinkTo(fileSink);
  • 特点:
  • 提供了更简洁的接口,减少了用户的实现负担。
  • 支持高级功能,如分桶策略、滚动策略等。
  • 更适合与 Flink 的 Checkpoint 机制结合,支持精确一次语义。

Q:基于保存点恢复并启动Flink时报错:Caused by: java.lang.IllegalArgumentException: Key group 1 is not in KeyGroupRange{startKeyGroup=64, endKeyGroup=127}. Unless you're directly using low level state access APIs, this is most likely caused by non-deterministic shuffle key (hashCode and equals implementation).

问题描述

2025-08-18 15:02:59,138 WARN  org.apache.flink.runtime.taskmanager.Task                    1132 [Filter -> Map -> Filter -> Map-BigdataRedis-DimFaultCode -> Map-xxxBackendMysql-deviceRemoteConfigVersion -> Map-BigdataRedis-Dimdevice (2/2)#4]  - Filter -> Map -> Filter -> Map-BigdataRedis-DimFaultCode -> Map-xxxBackendMysql-deviceRemoteConfigVersion -> Map-BigdataRedis-Dimdevice (2/2)#4 (ca3a4e1bbeabcfb04b904447536f614a) switched from RUNNING to FAILED with failure cause:
java.lang.RuntimeException: Exception occurred while setting the current key context.
	at org.apache.flink.streaming.api.operators.StreamOperatorStateHandler.setCurrentKey(StreamOperatorStateHandler.java:480) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.api.operators.AbstractStreamOperator.setCurrentKey(AbstractStreamOperator.java:549) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.api.operators.AbstractStreamOperator.setKeyContextElement(AbstractStreamOperator.java:544) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.api.operators.AbstractStreamOperator.setKeyContextElement1(AbstractStreamOperator.java:531) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.api.operators.OneInputStreamOperator.setKeyContextElement(OneInputStreamOperator.java:36) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.tasks.OneInputStreamTask$StreamTaskNetworkOutput.emitRecord(OneInputStreamTask.java:232) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.io.AbstractStreamTaskNetworkInput.processElement(AbstractStreamTaskNetworkInput.java:134) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.io.AbstractStreamTaskNetworkInput.emitNext(AbstractStreamTaskNetworkInput.java:105) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.io.StreamOneInputProcessor.processInput(StreamOneInputProcessor.java:65) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.tasks.StreamTask.processInput(StreamTask.java:539) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.tasks.mailbox.MailboxProcessor.runMailboxLoop(MailboxProcessor.java:216) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.tasks.StreamTask.runMailboxLoop(StreamTask.java:829) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:778) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.taskmanager.Task.runWithSystemExitMonitoring(Task.java:958) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.taskmanager.Task.restoreAndInvoke(Task.java:937) [flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:751) [flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.taskmanager.Task.run(Task.java:573) [flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at java.lang.Thread.run(Thread.java:750) [?:1.8.0_422]
Caused by: java.lang.IllegalArgumentException: Key group 58 is not in KeyGroupRange{startKeyGroup=64, endKeyGroup=127}. Unless you're directly using low level state access APIs, this is most likely caused by non-deterministic shuffle key (hashCode and equals implementation).
	at org.apache.flink.runtime.state.KeyGroupRangeOffsets.newIllegalKeyGroupException(KeyGroupRangeOffsets.java:37) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.state.heap.InternalKeyContextImpl.setCurrentKeyGroupIndex(InternalKeyContextImpl.java:77) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.runtime.state.AbstractKeyedStateBackend.setCurrentKey(AbstractKeyedStateBackend.java:194) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	at org.apache.flink.streaming.api.operators.StreamOperatorStateHandler.setCurrentKey(StreamOperatorStateHandler.java:478) ~[flink-dist-1.15.0-h0.cbu.dli.330.20241021.r34.jar:1.15.0-h0.cbu.dli.330.20241021.r34]
	... 17 more
2025-08-18 15:02:59,139 WARN  org.apache.flink.runtime.taskmanager.Task                    1139 [Filter -> Map -> Filter -> Map-BigdataRedis-DimFaultCode -> Map-xxxBackendMysql-deviceRemoteConfigVersion -> Map-BigdataRedis-Dimdevice (2/2)#4]  - Call stack:
    at java.lang.Thread.getStackTrace(Thread.java:1564)
    at org.apache.flink.runtime.taskmanager.Task.transitionState(Task.java:1139)
    at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:801)
    at org.apache.flink.runtime.taskmanager.Task.run(Task.java:573)
    at java.lang.Thread.run(Thread.java:750)

2025-08-18 15:02:59,139 WARN  org.apache.flink.runtime.taskmanager.Task                    1139 [Filter -> Map -> Filter -> Map-BigdataRedis-DimFaultCode -> Map-xxxBackendMysql-deviceRemoteConfigVersion -> Map-BigdataRedis-Dimdevice (1/2)#4]  - Call stack:
    at java.lang.Thread.getStackTrace(Thread.java:1564)
    at org.apache.flink.runtime.taskmanager.Task.transitionState(Task.java:1139)
    at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:801)
    at org.apache.flink.runtime.taskmanager.Task.run(Task.java:573)
    at java.lang.Thread.run(Thread.java:750)
...

这个异常:

Caused by: java.lang.IllegalArgumentException: Key group 1 is not in KeyGroupRange{startKeyGroup=64, endKeyGroup=127}. Unless you're directly using low-level state access APIs, this is most likely caused by non-deterministic shuffle key (hashCode and equals implementation).

是 Flink 在恢复状态时(如重启、从 checkpoint/savepoint 恢复)常见的报错,核心原因是 key 的 hashCodeequals 方法不合法或不一致,导致 key 被错误地分配到不属于它的 key group。


问题本质

Flink 使用 key group 来分片 ·keyed state·。每个 key 会被映射到一个 key group(通过 KeyGroupRangeAssignment.assignKeyToParallelOperator()),而这个映射依赖于 key.hashCode()

如果:

  • hashCode() 不稳定(比如依赖可变字段或随机值);
  • equals()hashCode() 不遵循 Java 规范(即两个 equal 的对象 hashCode 不一致);
  • 或者你在恢复时使用了不同的 key 类型(比如类结构变了);

就会导致 key 被错误地分配到不属于它的 key group,从而触发这个异常。


如何排查

1. 检查 key 的类型

确认你使用的 key(比如 POJO、Tuple、String、Long 等)是否:

  • 实现了 hashCode()equals()
  • 这两个方法是稳定的(不依赖可变字段、时间戳、随机数等);
  • 满足 Java 规范:equal 的对象必须有相同的 hashCode

2. 是否有使用自定义 key

如果你用了自定义类作为 key:

public class MyKey {
    public String id;
    public long timestamp;

    @Override
    public boolean equals(Object o) { ... }

    @Override
    public int hashCode() { ... }
}

确保 hashCode()equals()基于不可变字段实现的。

3. 是否修改过 key 类型或结构

如果你从 checkpoint/savepoint 恢复,但期间修改了 key 类的结构(比如加了字段、改了 hashCode() 实现),也会导致这个错误。


解决方案

✅ 方案一:修复 key 的 hashCode()equals()

确保 key 的 hashCode()equals() 满足以下要求:

  • 不变性:多次调用 hashCode() 返回相同值;
  • 一致性:equals() == truehashCode() 必须相同;
  • 不依赖可变字段或外部状态。

示例修复:

@Override
public int hashCode() {
    return Objects.hash(id); // 只使用不可变字段
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    MyKey myKey = (MyKey) o;
    return Objects.equals(id, myKey.id);
}

✅ 方案二:清除状态,重新启动

如果你已经修复了 key 的问题,但旧 checkpoint/savepoint 中保存的状态仍有问题,可以尝试:

  • 丢弃旧 checkpoint/savepoint,重新启动作业;
  • 或者使用 --allowNonRestoredState(仅当你确定状态不重要);
  • 或者使用 state processor API 修复状态(高级用法)。

注意事项

如果你使用的是 Flink SQL,key 是由 SQL 的 GROUP BYPRIMARY KEY 自动生成的,通常不会有问题。但如果你用了 UDTFUDAF 或自定义 connector,也可能引入不合法 key。


简单总结

这个错误 99% 是因为 key 的 hashCode()equals() 不合法或不一致,导致 key group 分配错误。修复 key 的实现,或清除旧状态重新启动作业即可。

如果你能贴出你用的 key 类型或相关代码,我可以帮你具体看看哪里有问题。

参考文献

Caused by: java.lang.IllegalArgumentException: Key group 45 is not in KeyGroupRange{startKeyGroup=0, endKeyGroup=42}.
        at org.apache.flink.runtime.state.KeyGroupRangeOffsets.computeKeyGroupIndex(KeyGroupRangeOffsets.java:142)
        at org.apache.flink.runtime.state.KeyGroupRangeOffsets.setKeyGroupOffset(KeyGroupRangeOffsets.java:104)
        at org.apache.flink.contrib.streaming.state.RocksDBKeyedStateBackend$RocksDBFullSnapshotOperation.writeKVStateData(RocksDBKeyedStateBackend.java:664)
        at org.apache.flink.contrib.streaming.state.RocksDBKeyedStateBackend$RocksDBFullSnapshotOperation.writeDBSnapshot(RocksDBKeyedStateBackend.java:521)
        at org.apache.flink.contrib.streaming.state.RocksDBKeyedStateBackend$3.performOperation(RocksDBKeyedStateBackend.java:417)
        at org.apache.flink.contrib.streaming.state.RocksDBKeyedStateBackend$3.performOperation(RocksDBKeyedStateBackend.java:399)
        at org.apache.flink.runtime.io.async.AbstractAsyncIOCallable.call(AbstractAsyncIOCallable.java:72)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at org.apache.flink.util.FutureUtil.runIfNotDoneAndGet(FutureUtil.java:40)
        at org.apache.flink.streaming.runtime.tasks.StreamTask$AsyncCheckpointRunnable.run(StreamTask.java:897)
        ... 5 more
    [CIRCULAR REFERENCE:java.lang.IllegalArgumentException: Key group 45 is not in KeyGroupRange{startKeyGroup=0, endKeyGroup=42}.]


问题是我的数据对象(PO​​JO)具有可变的哈希码。具体来说,哈希代码包含枚举。例如,如果我有一个汽车流,其哈希码由汽车年份和汽车类型(枚举)组成,如下所示。

Car {
   private final CarType carType;
   private final int carYear

   public long hashCode() {
     int result = 17;
     result = 31 * result + carYear;
     result = 31 * result + carType.hasCode();  <---- This is mutable!
   }
}

枚举的hashCode本质上是Object.hashCode()(取决于内存地址)。随后,一台机器(或进程)上的hashCode将与另一台机器(或进程)上的hashCode不同。这也解释了为什么我只在分布式环境中运行而不是本地运行时才遇到此问题。

为了解决这个问题,我将hashCode()更改为不可变的。做String.hashCode()的性能很差,所以我可能需要对其进行优化。但是下面对Car的定义将解决此问题。

Car {
   private final CarType carType;
   private final int carYear

   public long hashCode() {
     int result = 17;
     result = 31 * result + carYear;
     result = 31 * result + carType.name().hasCode();  <---- This is IMMUTABLE!
   }
}
在Flink中,Idle状态是指作业处于【空闲状态】,即没有输入数据可用于处理的状态。当Flink作业没有输入数据可供处理时,它将进入Idle状态。
理解Flink中的Idle状态可以从以下几个方面来考虑:

无输入数据:Idle状态表示当前作业没有输入数据可用于处理。这可能是因为输入源暂时没有可用的数据,或者数据流已经被处理完毕。
空闲等待:在Idle状态下,Flink作业会等待输入数据的到来。它会周期性地检查输入源是否有新的数据产生,一旦有新的数据产生,作业将从Idle状态转换为Active状态开始处理数据。
优化机会:Idle状态也给了Flink一些优化的机会。在空闲状态下,Flink可以进行一些优化操作,例如重新分配资源、调整并行度、合并任务等,以提高作业的性能和效率。

需要注意的是,Idle状态并不代表作业已经完成或者失败,它只是作业在等待输入数据的状态。一旦有新的输入数据到来,作业将离开Idle状态并开始处理数据。
  • Kafka分区数:是指Kafka主题中的分区数量,一个主题可以有多个分区。每个分区都是独立的消息队列,可以并发地接收和处理消息。在Kafka中,分区数决定了消息的并行性,即一个主题的分区数越多,可以同时处理的消息量就越大。

  • Flink并行度:是指一个任务或算子可以并行执行的并发任务数。在Flink中,每个任务或算子都可以独立地处理数据流,而并行度就是同时处理的任务或算子的数量。并行度的设置可以影响Flink任务的并发性以及整体的吞吐量。

  • Kafka分区数与 Flink KafkaSource 并行度之间存在一定的关系。

  • Kafka分区数和Flink KafkaSource 并行度之间的关系是:

  • 1)通常情况下,我们希望【Flink的并行度】与【Kafka的分区数】保持一致

这样每个Flink 任务或KafkaSource算子可以处理一个Kafka分区的数据。
这样可以最大程度地发挥消息的并行性和Flink任务的并发性,提高整体的处理能力和吞吐量。

  • 2)但也可以根据具体的业务需求来调整Flink的并行度。(但必须满足: Kafka Partitions ≥ Flink Job Kafka Source Paralism
  • 如果 Flink KafkaSource 的并行度 < Kafka的分区数,则:Kafka 多个分区的数据将被一个 Flink 任务或算子处理。当数据量大的时候,可能会导致Flink任务数据处理能力的下降、kafka消费组显著的消息积压。
  • 如果 Flink KafkaSource 的并行度 > Kafka的分区数,则:会存在一些任务无法处理数据的情况,Flink KafkaSource 任务因部分节点无法分配到数据而夯住无法提交 checkpoint ,Flink作业必将运行故障(诚然,Flink KafkaSource 也不会自动进入空闲状态)。

此时,您需要降低并行度或向水印策略添加空闲超时。

  • 如果在这段时间内流的某个分区中没有记录流动,则该分区被视为“空闲”,并且不会阻止Flink下游运算符中水印(watermark)的进度。

因此,在设置Flink任务的并行度时,可以考虑将 Flink KafkaSource 的并行度(Paralism)设置为与 Kafka 分区数(Partitions)为【相等】或【倍数关系】( N*Partitions = Partitions),以达到最佳的处理性能。

例如:Flink作业中KafkaSource的下游Task来不及消费/处理上游 KafkaSource Task 的数据,项目实践中可能将 KafkaSource 调小。
例如:也可将Kafka的分区数增大,但增大后,Flink Job需要重新启动或者在作业中开启Kafka的分区自动发现特性。

  • 扩展: flink sink 到 kafka 时,如果Flink并行度 > kafka分区数时,则:会轮询把数据插入到kafka分区中,数据不会丢失。
  • 扩展: flink sink 到 kafka 时,如果Flink并行度 < kafka分区数时,则:也会轮询把数据插入到kafka分区中,数据不会丢失。

因为如果指定key的情况下,则:producer 会按照 hash 规则,把数据hash到相应分区中,
也就是说————flink sink时,并行度之于kafka影响不大,不会存在数据丢失或者分区没有写入的情况。

  • 参考文献

1、基础常识

  • 一个 Flink Job 由多个任务(Task) 组成(转换/算子、数据源和数据接收器)
  • 一个 Task 包括多个并行执行的实例,且每一个实例都处理 Task 输入数据的一个子集。
  • 一个 Task 的并行实例数被称为该 task 的 并行度 (parallelism)。
  • 代码设置的并行度(C_P) > 大数据(云)平台设置的并行度(BDP_P)
  • Task(算子) 级粒度的并行度 > Job级粒度的并行度

2、默认并行度

  1. parallelism.default

在任何地方未指定并行度时使用的默认并行度(默认值:1)。

  1. taskmanager.numberOfTaskSlots

任务管理器提供的插槽数(默认值:1)。 每个插槽可以接受一个任务或管道。
在 TaskManager 中拥有多个插槽有助于在并行任务或管道之间分摊某些恒定开销(JVM、应用程序库或网络连接)。
有关详细信息,请参阅任务槽和资源概念部分。
运行更多较小的 TaskManager,每个 TaskManager 有一个插槽是一个很好的起点,可以实现任务之间的最佳隔离。
将相同的资源专用于具有更多插槽的较少较大的 TaskManager 有助于提高资源利用率,但代价是任务之间的隔离较弱(更多任务共享同一个 JVM)。

  • 参考文献

3、最佳实践(以某个Task获取并行度的过程为例)

目的:既需要能灵活地设置 某个 Task 级的并行度,又能默认设置 Job(全局)级的并行度

支持为每个source/sink/operator设置task级并行度

import cn.johnnyzen.bdp.xxxx.yyyy.zzzz.conf.Constants;
import org.apache.flink.api.java.utils.ParameterTool;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.GlobalConfiguration;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSink;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FlinkJobUtils {
    private final static Logger logger = LoggerFactory.getLogger(FlinkJobUtils.class);

    /**
     * 全局配置
     * @description
     *  1. 对应 ${FLINK_CONF_DIR}/flink-conf.yaml 配置文件
     *  2. 如 : parallelism.default / taskmanager.numberOfTaskSlots / jobmanager.heap.size / taskmanager.heap.size 等配置项
     */
    private static Configuration globalConfiguration;

    static {
        //globalConfiguration | 读取 {org.apache.flink.configuration.ConfigConstants.ENV_FLINK_CONF_DIR:FLINK_CONF_DIR}/{org.apache.flink.configuration.GlobalConfiguration.FLINK_CONF_FILENAME:"flink-conf.yaml"} 下的配置 | DLI等大数据平台的界面/全局配置也在此处
        globalConfiguration = GlobalConfiguration.loadConfiguration();
        logger.info("Success to loading flink global configuration!");
    }

    public static Integer getDataStreamParallelism(DataStream dataStream){
        return dataStream.getParallelism();
    }
    
    /**
     * 设置 数据流[流式源/流式算子(任务)]并行度 / setting data stream's parallelism
     * @note
     *  1. The parallelism of non parallel operator must be 1.(非并行运算符的并行度必须为1, 如: DataStreamSource 等)
     * @param jobParameterTool | NotEmpty |
     * @param dataStreamOrEnv | NotEmpty | DataStream(Source/Operator/Sink) or StreamExecutionEnvironment
     * @param dataStreamParallelConfiguration | NotEmpty |
     *  eg : {@link  cn.johnnyzen.bdp.xxxx.yyyy.zzzz.conf.Constants#FLINK_SOURCE_KAFKA_PARALLEL_PARAM } / ...
     * @param defaultParallelism | Nullable | 默认并行度 (允许为空,非旁支流不建议设置默认值,即不建议非旁支流在此参数处有值)
     * @reference-doc
     *  [1] [Deployment > Configurations > Parallelism - Apache Flink Docs](https://nightlies.apache.org/flink/flink-docs-release-1.12/deployment/config.html)
     *      # 1) parallelism.default
     *      在任何地方未指定并行度时使用的默认并行度(默认值:1)。
     *      # 2) taskmanager.numberOfTaskSlots
     *      任务管理器提供的插槽数(默认值:1)。 每个插槽可以接受一个任务或管道。
     *      在 TaskManager 中拥有多个插槽有助于在并行任务或管道之间分摊某些恒定开销(JVM、应用程序库或网络连接)。
     *      有关详细信息,请参阅任务槽和资源概念部分。
     *      运行更多较小的 TaskManager,每个 TaskManager 有一个插槽是一个很好的起点,可以实现任务之间的最佳隔离。
     *      将相同的资源专用于具有更多插槽的较少较大的 TaskManager 有助于提高资源利用率,但代价是任务之间的隔离较弱(更多任务共享同一个 JVM)。
     */
    public static void setParallelism(ParameterTool jobParameterTool, Object dataStreamOrEnv, String dataStreamParallelConfiguration, Integer defaultParallelism){
        String parallelism = null;//"1" / ...
        //step1 获取 Task级粒度的并行度配置。若获取失败,则跳过 by 配置文件
        parallelism = jobParameterTool.get(dataStreamParallelConfiguration, null);
        logger.info("{} : {}", dataStreamParallelConfiguration, parallelism);

        //step2 获取 Job 全局级粒度的默认并行度配置。若获取失败,则跳过 by 配置文件
        if(ObjectUtils.isEmpty(parallelism)){
            //step2.1 获取 NACOS 配置中心 或 本地的作业配置文件 中的 全局默认并行度
            //注1 : jobParameterTool : 其配置来源于 NACOS 配置中心 或 本地的作业配置文件, 未合并其他出的配置,如 flink-conf.yaml
            //注2 : 当然可自行优化该对象的配置数据获取逻辑
            parallelism = jobParameterTool.get(Constants.DEFAULT_PARALLEL_PARAM, null);
            logger.info("{} : {} | from `{}` by `jobParameterTool`", dataStreamParallelConfiguration, parallelism, Constants.DEFAULT_PARALLEL_PARAM);

            //step2.2 获取 flink-conf.yaml 的作业配置文件 中的 全局默认并行度
            if(ObjectUtils.isEmpty(parallelism)){
                //注1 : globalConfiguration (来源于 : flink-conf.yaml )
                //注2 : 如果是DLI等大数据平台上(基于YARN/K8S部署运行)时,基本上到了这一步,就不太可能没有值了,因为原则上不可能不提交 flink-conf.yaml (99.999%)
                //注3 : 如果是在本地电脑部署运行时,此处取决于前面步骤是否有配置;否则, 此处仍为 null (但不影响运行)
                parallelism = globalConfiguration.getString(Constants.DEFAULT_PARALLEL_PARAM, ObjectUtils.isEmpty(defaultParallelism)?null : String.valueOf(defaultParallelism) );
                logger.info("{} : {} | from `{}` by `globalConfiguration`", dataStreamParallelConfiguration, parallelism, Constants.DEFAULT_PARALLEL_PARAM);
            }
        }

        //step3 若获取配置成功,则设置,反之跳过
        if(!ObjectUtils.isEmpty(parallelism)){//parallel is not empty
            Integer parallelismInt = Integer.valueOf(parallelism);
            String type = null;
            if(dataStreamOrEnv instanceof SingleOutputStreamOperator){
                SingleOutputStreamOperator dataStreamOperator = (SingleOutputStreamOperator) dataStreamOrEnv;
                dataStreamOperator.setParallelism(parallelismInt);
                type = SingleOutputStreamOperator.class.getCanonicalName();
            } else if (dataStreamOrEnv instanceof DataStreamSource) {
                DataStreamSource dataStreamSource = (DataStreamSource) dataStreamOrEnv;
                dataStreamSource.setParallelism(parallelismInt);
                type = DataStreamSource.class.getCanonicalName();
            } else if(dataStreamOrEnv instanceof DataStreamSink){
                DataStreamSink dataStreamSink = (DataStreamSink) dataStreamOrEnv;
                dataStreamSink.setParallelism(parallelismInt);
                type = DataStreamSink.class.getCanonicalName();
            } else if (dataStreamOrEnv instanceof StreamExecutionEnvironment) {
                StreamExecutionEnvironment streamExecutionEnvironment = (StreamExecutionEnvironment) dataStreamOrEnv;
                streamExecutionEnvironment.setParallelism(parallelismInt);
                type = StreamExecutionEnvironment.class.getCanonicalName();
            } else {
                throw new RuntimeException(String.format("Not support the class type when setting parallelism by parallelism! %s : %d", dataStreamParallelConfiguration, parallelism));
            }
            logger.info("Success to set parallelism({})! type: {}", parallelismInt, type);
        } else {//parallel is empty
            logger.warn("The `{}`'s parallelism is empty now , and will be set a default value = 1 by flink framework!", dataStreamParallelConfiguration);
        }
    }
}
  • Constants
public class Constants {
    /**
     * 作业(全局)默认并行度
     * (Definition by Apache Flink Framework | support versions : [ flink 1.0, flink 1.18 or future version] )
     * @description parallelism.default : 在任何地方未指定并行度时使用的默认并行度(默认值:1)
     * @sample 作业启动时,日志中可见:
     *  "202X-XX-XX 17:06:02,029 INFO  org.apache.flink.configuration.GlobalConfiguration           [] - Loading configuration property: parallelism.default, 2"
     * @reference-doc
     *  [1] [Deployment > Configurations - Apache Flink Docs](https://nightlies.apache.org/flink/flink-docs-release-1.12/deployment/config.html)
     **/
    public final static String DEFAULT_JOB_PARALLEL_PARAM = "parallelism.default";
}

X 参考文献

参考文献

推荐文献

posted @ 2025-07-30 14:23  千千寰宇  阅读(49)  评论(0)    收藏  举报