Flink 的内存管理采用了显式的内存管理并用序列化方式存储对象,同时支持 on-heap 和 off-heap。在这点上 Flink 与 Spark 的MEMORY_ONLY_SER存储级别十分相似,不同点在于 Spark 仍以 on-heap 对象存储为主,而 Flink 则只支持序列化的对象存储。
Flink 内存管理概览
Flink 内存主要指 TaskManager 运行时提供的内存资源。TaskManager 主要由几个内部组件构成:
负责和 JobManager 等进程通信的 actor 系统,负责在内存不足时将数据溢写到磁盘和读回的 IOManager,还有负责内存管理的 MemoryManager。其中 actor 系统和 MemoryManager 会要求大量的内存。相应地,Flink 将 TaskManager 的运行时 JVM heap 分为 Network Buffers、MemoryManager 和 Free 三个区域(在 streaming 模式下只存在 Network Buffers 和 Free 两个区域,因为算子不需要缓存一次读入的大量数据)。
参考Flink官方文档: Detailed Memory Model:
ci.apache.org/projects/fl…
下表列出了上面描述的所有内存组件,并引用了影响各个组件大小的Flink配置选项:
成分 配置选项 描述
框架堆内存 taskmanager.memory.framework.heap.size 专用于Flink框架的JVM堆内存(高级选项)
任务堆内存 taskmanager.memory.task.heap.size 专用于Flink应用程序的JVM堆内存可运行算子和用户代码
托管内存 taskmanager.memory.managed.size
taskmanager.memory.managed.fraction 由Flink管理的本机内存,保留用于排序,哈希表,中间结果的缓存和RocksDB状态后端
框架堆外内存 taskmanager.memory.framework.off-heap.size 专用于Flink框架的堆外直接(或本机)内存(高级选项)
任务堆外内存 taskmanager.memory.task.off-heap.size 专用于Flink应用程序以运行操作员的堆外直接(或本机)内存
网络内存 taskmanager.memory.network.min
taskmanager.memory.network.max
taskmanager.memory.network.fraction 直接存储器保留用于任务之间的数据记录交换(例如缓冲用于传输通过网络),
它是一种在总Flink存储器中占用的网络内存阈值
JVM元空间 taskmanager.memory.jvm-metaspace.size Flink JVM进程的元空间大小
JVM开销 taskmanager.memory.jvm-overhead.min
taskmanager.memory.jvm-overhead.max
taskmanager.memory.jvm-overhead.fraction 架空其他JVM保留本机内存:如线程堆栈,代码缓存,垃圾收集等的空间,这是一个上限分级成分的的总进程内存
Flink网络内存传输
这篇文章官方的ververica已经删掉了,所以只好引用这条链接
1.根据算子InputGate申请的buffers缓冲列表,判断当前buffer的数据是否可用
2.锁住当前算子需要将申请的内存批量添加进内存的buffers资源
3.根据协议报解析,跳过协议报首部。添加本次从Buffer中消费的BufferConsumer单位
4.更新本次消费buffer的状态信息
5.增加收集每个buffer中的checkpoint barrier
6.根据协议包的格式,每个申请的Buffer协议包首部会有一个固定的序列号。按照生成的序列号存进优先队列。根据优先级队列的消息编号通知下游消息消费
7.通知下游算子可以消费的消息
ResultSubPartition.java
/**
* 添加给定的缓冲区。
*
* <p>该请求可以同步执行,也可以异步执行,具体取决于
* 执行。
*
* <p> <strong>重要</ strong>:在添加新的{@link BufferConsumer}之前,必须先添加
*处于完成状态。由于性能原因,此操作仅在
*数据读取。在先前的缓冲区使用者仍处于打开状态时,可以添加优先级事件,
*在这种情况下,开放缓冲区使用者将被覆盖。
*
* @param buffer消费要添加的缓冲区(将所有权转让给该分区对象)
* @parampartialRecordLength为了以一个完整的开头开始要跳过的字节的长度
*从基础{@cite MemorySegment}的位置索引0开始记录。
* @return如果操作成功并且将bufferConsumer排入消费队列,则返回true。
* @throws IOException在添加缓冲区时发生错误的情况下抛出
*/
public abstract boolean add(BufferConsumer bufferConsumer, int partialRecordLength)
throws IOException;
PipelinedSubpartition.java
private boolean add(BufferConsumer bufferConsumer, int partialRecordLength, boolean finish) {
checkNotNull(bufferConsumer);
// 通知下游算子当前buffer的数据是否可用
final boolean notifyDataAvailable;
int prioritySequenceNumber = -1;
// 锁住当前buffers单例
synchronized (buffers) {
if (isFinished || isReleased) {
bufferConsumer.close();
return false;
}
// Add the bufferConsumer and update the stats
//根据协议报解析,跳过协议报首部
// 添加本次从Buffer中消费的BufferConsumer单位
if (addBuffer(bufferConsumer, partialRecordLength)) {
prioritySequenceNumber = sequenceNumber;
}
//更新本次消费buffer的状态信息
updateStatistics(bufferConsumer);
//增加收集每个buffer中的checkpoint barrier
increaseBuffersInBacklog(bufferConsumer);
notifyDataAvailable = finish || shouldNotifyDataAvailable();
isFinished |= finish;
}
if (prioritySequenceNumber != -1) {
//根据优先级队列的消息编号通知下游消息消费
notifyPriorityEvent(prioritySequenceNumber);
}
if (notifyDataAvailable) {
//通知下游算子可以消费的消息
notifyDataAvailable();
}
return true;
}
BufferConsumer的定义为接受每个算子中传输过来的网络数据,根据协议报解析成Flink内部需要的格式
/**
* 不是用于生成{@link Buffer}的线程安全类。
*
* <p>它读取{@link BufferBuilder}写入的数据。
*尽管它不是线程安全的,并且只能由一个线程使用,但是此线程可以与线程不同。
*使用/写入{@link BufferBuilder}的线程。模式在这里很简单:一个线程将数据写入
* {@link BufferBuilder},使用{@link BufferConsumer}可以读取不同的线程。
*/
@NotThreadSafe
public class BufferConsumer implements Closeable {
}
/** 流水线式的仅内存中的子分区,可以使用一次。
*
* <p>每当{@link ResultSubpartition#add(BufferConsumer,boolean)}添加完成的{@link BufferConsumer}或第二秒
* {@link BufferConsumer}(在这种情况下,我们将假定第一个完成),我们将
* {@link PipelinedSubpartitionView#notifyDataAvailable()通知}通过创建的读取视图
* {@link ResultSubpartition#createReadView(BufferAvailabilityListener)}的新数据可用性。除非致电
* {@link #flush()}总是只在第一个完成的缓冲区打开时通知,并且
*然后,读取器必须通过{@link #pollBuffer()}清空缓冲区,直到显示其返回值
*没有更多的缓冲区可用。这导致缓冲区队列为空或具有一个
*剩余的{@link BufferConsumer}尚未完成,通知将最终从这些地方再次开始。
*
* <p>显式调用{@link #flush()}将强制执行此操作
* {@link PipelinedSubpartitionView#notifyDataAvailable()通知}
* {@link BufferConsumer}存在于队列中。
public class PipelinedSubpartition extends ResultSubpartition
implements CheckpointedResultSubpartition, ChannelStateHolder {
private static final Logger LOG = LoggerFactory.getLogger(PipelinedSubpartition.class);
// ------------------------------------------------------------------------
/** 该子分区的所有缓冲区。对缓冲区的访问在此对象上同步。. */
private final ArrayDeque<BufferConsumer> buffers = new ArrayDeque<>();
/** 当前在此子分区中的非事件缓冲区的数量 */
@GuardedBy("buffers")
private int buffersInBacklog;
/** 读取视图消耗此子分区. */
private PipelinedSubpartitionView readView;
/** 指示子分区是否已完成的标志. */
private boolean isFinished;
@GuardedBy("buffers")
private boolean flushRequested;
/** 指示子分区是否已释放的标志. */
private volatile boolean isReleased;
/** 缓冲区总数(数据缓冲区和事件缓冲区). */
private long totalNumberOfBuffers;
/** 字节总数(数据缓冲区和事件缓冲区). */
private long totalNumberOfBytes;
/** 缓冲区的集合,这些缓冲区由检查点barrier跨越,并且需要持久保存快照. */
private final List<Buffer> inflightBufferSnapshot = new ArrayList<>();
}
/**
* 返回带有单独索引的保留副本。这允许从同一{@link MemorySegment}读取两次。
*
* <p>警告:新返回的{@link BufferConsumer}将从其原始缓冲区复制其阅读器索引。
* 换句话说,在复制之前已经消耗的数据对于返回的副本将不可见。
*
* @return 具有单独索引的self的保留副本
*/
public BufferConsumer copy() {
return new BufferConsumer(buffer.retainBuffer(), writerPosition.positionMarker, currentReaderPosition);
}
Continue..QwQ