【原理|数据传输】Flink系列之数据传输原理

有了Flink 数据传输(1)的基础,接下来看看数据在一个job中的传输细节

先通过一个job了解一下数据传输的流程,再拓展到跨TaskManager,之后讨论taskmanager之间使用netty进行数据传输的相关细节
 

一、数据传输

1. 本地的数据传输

下图展示了一个job的StreamGraph,逻辑比较简单,将数据拉平,然后sum聚合后输出到flink的控制台,其中sum和sink print组成了一个算子链,这减少了他们之间数据通讯的成本,提高了传输效率。
一个简单的

接着我们以FlatMap所在线程与下游 sum() 所在线程的通信为例,讨论下数据是如何在上下游算子之间传输的。

这里提到的线程其实就是slot

在这里插入图片描述
值得关注的是这两个线程共享同一块内存,数据交互通过wait()/notifyAll来同步。具体过程如下:

当FlatMap所在线程写入结果到ResultSubPartition并flush结果到Buffers之后,RS将会唤醒(inputChannelWithData.notifyAll())sum所在线程。唤醒后,从Buffers中获取数据,经过反序列化传递给用户代码处理。

当没有buffer可以消费时,sum所在线程将会堵塞(inputChannelWithData.wait())
当有buffer且唤醒了sum所在线程,但是sum没有能力消费时,这时因为Buffers没有被及时释放,所以FlatMap就不能继续flush数据,这就形成了反压,接着反压现象会传递到source。

 

2. 远程的数据传输

远程线程的算子间的数据传递和本地类似,不同在于通讯和Buffer的处理:

当下游算子没有Buffer可以消费时,会通过PartitionRequestClient向FlatMap所在进程发起RPC请求,
远程的PartitionRequestServerHandler接到请求之后,读取ResultPartitionManager的Buffer,并返回给下游算子对应的client。
 

3. 同一线程的数据传输

如上图的算子链:sum->Print to std,两个算子是在同一个线程中(同一个slot中)运行,数据不需要序列化和反序列化,也不需要共享的Buffers。当sum处理完数据后会调用Collector发送数据,sink这边调用processElement方法接收并处理数据。

 
 

二、Buffer的读写模型

1. MemorySegment

通过上面的分析知道,跨线程的数据传递都需要经历数据的序列化、flush Buffer、read Buffer、数据反序列化的过程。

MemorySegment抽象了Flink内存管理,代表了Flink管理的一块内存,其中有两个主要的实现类:HeapMemorySegment代表堆内内存的读写,HybridMemorySegment代表了堆外内存的读写。

 

1.1. MemorySegment角度下的数据处理

观察下数据在MemorySegment的处理过程
在这里插入图片描述

当Task1有Buffer空间时,“A”被Task1处理序列化到LocalBufferPool(缓冲池1)中,接着被发送到Task2的LocalBufferPool(缓冲池2)中,Task2读取“A”然后再反序列化,交由程序处理。

 

1.2. 本地传输

当task1,2运行在一个TaskManager节点时,buffer可以直接交给下一个task。当Task2消费了该Buffer,buffer就会被缓冲池1回收。如果这时Task2处理速度比Task1慢,那Buffer的回收速度就赶不上Task1取用Buffer的速度,导致缓冲池1无可用的Buffer,Task1就堵塞等待,这就形成了反压,也就是Task1的降速。反压会逆向传递,直到source算子。

 

1.3 远程传输

当Task1,2不在一个TaskManager运行时,Buffer会被发送到网络(TCP Channel)后,等接收端消费完Buffer后再回收。

传输过程
Task1通过Netty的水位值机制保证不往网络中写入太多数据。如果Netty输出的缓冲字节数导致网络中数据超过了高水位值,那Netty会等到其降低(接收端消费了数据)到低水位值才继续写入数据。这保证了网络中不能有太多数据。

Task2会从LocalBufferPool中申请Buffer,然后拷贝网络中的数据到buffer中。如果池中没有可用的Buffer,则会停止从TCP连接中读取数据。

反压
此时如果Task2停止消费网络中的数据,网络中的缓冲数据就会堆积,当到达Netty水位时,Task1也会停止发送缓存。
同时因为Buffer中的数据没有被消费,Task1的Buffer就不能回收到缓冲池,之后到达Task1的数据因为申请不到Buffer而堵塞了往ResultSubPartition中写数据。

 
 

2. 了解Netty水位

2.1 数据通过netty进行的网络传输

当使用Netty作为网络通信框架时,业务数据传输之前,先将数据发送到Netty的缓冲区中,然后再发送到TCP的缓冲区,最后发送到网路中。
 

2.2 水位

Netty通过高低水位控制向Netty缓冲区写入数据的多少。

向Netty缓冲区写入数据时,会判断写入的数据总量是否超过了设置的高水位值,如果超过了就设置通道(Channel)不可写状态。当Netty缓冲区中的数据写入到TCP缓冲区之后,Netty缓冲区的数据量变少,当低于低水位值的时候,就设置通过(Channel)可写状态。

// 代码位置: io.netty.channel.DefaultChannelPipeline.HeadContext#write​
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
    unsafe.write(msg, promise);
}



// 代码位置: io.netty.channel.AbstractChannel.AbstractUnsafe#write
@Override
public final void write(Object msg, ChannelPromise promise) {
    
    ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
    // 数据​写入到Netty缓冲区
    outboundBuffer.addMessage(msg, size, promise);
}



// 代码位置: io.netty.channel.ChannelOutboundBuffer#addMessage
public void addMessage(Object msg, int size, ChannelPromise promise) {
    Entry entry = Entry.newInstance(msg, size, total(msg), promise);
    if (tailEntry == null) {
        flushedEntry = null;
        tailEntry = entry;
    } else {
        Entry tail = tailEntry;
        tail.next = entry;
        tailEntry = entry;
    }
    if (unflushedEntry == null) {
        unflushedEntry = entry;
    }// 高水位判断​
    incrementPendingOutboundBytes(entry.pendingSize, false);
}// 代码位置: io.netty.channel.ChannelOutboundBuffer#incrementPendingOutboundBytes(long, boolean)
private void incrementPendingOutboundBytes(long size, boolean invokeLater) {
    if (size == 0) {
        return;
    }
​
​    long newWriteBufferSize = TOTAL_PENDING_SIZE_UPDATER.addAndGet(this, size);
    // 如果Netty缓冲区的数据总量已经超过高水位,则设置不可写状态​if (newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()) {
        setUnwritable(invokeLater);
    }
}


// 默认的高低水位值
private static final int DEFAULT_LOW_WATER_MARK = 32 * 1024;
private static final int DEFAULT_HIGH_WATER_MARK = 64 * 1024;

 
 

参考
https://www.jianshu.com/p/5748df8428f9

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值