文章目录
1.InputGate接收BufferOrEvent,等待CheckpointBarrier对齐
上游Task实例发送的数据元素和事件,会发送到下游Task节点的InputGate中。将InputGate包装成CheckpointedInputGate,可以接收、处理上游传输的Checkpoint事件。当所有InputChannel都接收到上游节点发送来的所有的CheckpointBarrier后,当前Task实例就会触发当前节点的Checkpoint操作。这是为了让隶属于同一批Checkpoint的数据能够全部进入到当前节点中处理,有效保证了数据一致性。即使出现异常,也能根据上一次的Checkpoint结果恢复当前Task实例的状态数据。
BufferOrEvent数据会传递到CheckpointedInputGate中,CheckpointedInputGate主要负责处理CheckpointBarrier事件,而StreamElement会传递给StreamOperator处理。在所有InputChannel的CheckpointBarrier事件全部到达之前,CheckpointBarrierHandler会block住指定index的InputChannel,并通过BufferStorage将所有的BufferOrEvent缓存起来。一旦所有的CheckpointBarrier到齐,就会处理当前Task实例的Buffer数据,从而保证数据准确性。
/**
* 从上游节点获取BufferOrent,对CheckpointBarrier事件进行相应处理,
* 其他类型的事件、数据会全部传递给算子处理
*/
@Override
public Optional<BufferOrEvent> pollNext() throws Exception {
while (true) {
Optional<BufferOrEvent> next;
if (bufferStorage.isEmpty()) {
next = inputGate.pollNext();
}
else {
next = bufferStorage.pollNext();
if (!next.isPresent()) {
return pollNext();
}
}
if (!next.isPresent()) {
return handleEmptyBuffer();
}
// 取出BufferOrEvent类型数据
BufferOrEvent bufferOrEvent = next.get();
// CheckpointBarrierHandler会block指定index的Channel
// CheckpointBarrierHandler作为处理Barrier的主角,有2个实现子类,分别对应“严格一次”和“至少一次”语义
if (barrierHandler.isBlocked(offsetChannelIndex(bufferOrEvent.getChannelIndex()))) {
// 如果当前InputChannel被barrierHandler锁定,就先将BufferOrEvent缓冲起来,也就是先Block住。
// CheckpointBarrierHandler会等所有InputChannel的CheckpointBarrier事件消息全部到达节点后,才会处理该Task实例的Buffer数据,保证数据计算结果的准确性
bufferStorage.add(bufferOrEvent);
// 如果缓冲区满了,就会clear + barrier reset
if (bufferStorage.isFull()) {
barrierHandler.checkpointSizeLimitExceeded(bufferStorage.getMaxBufferedBytes());
bufferStorage.rollOver();
}
}
/**
* 如果BufferOrEvent的数据类型为Buffer(也就是业务数据),直接留给算子处理
*/
else if (bufferOrEvent.isBuffer()) {
return next;
}
/**
* 如果BufferOrEvent的数据类型为CheckpointBarrier事件,就对接入的Barrier进行处理
*/
else if (bufferOrEvent.getEvent().getClass() == CheckpointBarrier.class) {
// 从BufferOrEvent中取出CheckpointBarrier事件
CheckpointBarrier checkpointBarrier = (CheckpointBarrier) bufferOrEvent.getEvent();
if (!endOfInputGate) {
// 根据CheckpointBarrier是否对齐,决定是否是否触发当前节点的Checkpoint操作
// 处理方式有2种:CheckpointBarrierAligner保证严格一次,CheckpointBarrierTracker至少一次
if (barrierHandler.processBarrier(checkpointBarrier, offsetChannelIndex(bufferOrEvent.getChannelIndex()), bufferStorage.getPendingBytes())) {
bufferStorage.rollOver();
}
}
}
/**
* 如果是CancelCheckpointMarker事件,那就取消本次Checkpoint操作
*/
else if (bufferOrEvent.getEvent().getClass() == CancelCheckpointMarker.class) {
// 取消本次Checkpoint操作
if (barrierHandler.processCancellationBarrier((CancelCheckpointMarker) bufferOrEvent.getEvent())) {
bufferStorage.rollOver();
}
}
else {
if (bufferOrEvent.getEvent().getClass() == EndOfPartitionEvent.class) {
// EndOfPartitionEvent事件表示上游Partition中的数据已经消费完了。最后要clear掉缓冲区里的Buffer
if (barrierHandler.processEndOfPartition()) {
bufferStorage.rollOver();
}
}
return next;
}
}
}
CheckpointBarrierHandler能够保证CheckpointBarrier事件对齐,其中CheckpointBarrierHandler有2个实现子类,CheckpointBarrierAligner保证严格一次,CheckpointBarrierTracker至少一次。CheckpointBarrierAligner会对所有InputChannel中的CheckpointBarrier事件严格控制对齐,视情况决定Task实例中的InputChannel的block和打开的时机。
2.以Barrier对齐的方式处理CheckpointBarrier事件
InputChannel开始处于非阻塞状态,收到一个Buffer类型的数据,会正常计算处理。如果收到1个CheckpointBarrier,processBarrier时CheckpointBarrierAligner会将当前InputChannel Block住,后续再来的Buffer类型数据会被缓存起来。
之后另一个InputChannel也收到了一个Checkpoint,CheckpointBarrierAligner同样也会将这个InputChannel Block住。然后判断“接收到的Barrier数量 + Block的InputChannel数量 = 所有InputChannel的数量”,说明当前InputGate内的所有InputChannel的Barrier全都对齐了,那就解除所有InputChannel的Block状态(每个InputChanne又能继续在非阻塞状态下处理BufferOrEvent了),并触发StreamTask开始执行一次Checkpoint操作。
/**
* 以Barrier对齐的方式处理CheckpointBarrier事件,保证严格一次的语义
*/
@Override
public boolean processBarrier(CheckpointBarrier receivedBarrier, int channelIndex, long bufferedBytes) throws Exception {
// 获取CheckpointBarrier事件的barrierId
final long barrierId = receivedBarrier.getId();
// 如果InputChannel==1,直接触发Checkpoint。因为就它自己,不需要对齐处理
if (totalNumberOfInputChannels == 1) {
// 有新的CheckpointBarrier事件发出了
if (barrierId > currentCheckpointId) {
// 更新当前的CheckpointId
currentCheckpointId = barrierId;
// 触发该Task实例的Checkpoint操作
notifyCheckpoint(receivedBarrier, bufferedBytes, latestAlignmentDurationNanos);
}
return false;
}
/**
* 能往下走,说明InputChannel的数量 > 1,这就必须得等待Barrier对齐
*/
boolean checkpointAborted = false;
// numBarriersReceived大于0,说明已经开始接收CheckpointBarrier事件
if (numBarriersReceived > 0) {
/**
* 当前需要处理的正是这个Barrier事件,那就执行“Barrier对齐操作”
*/
if (barrierId == currentCheckpointId) {
// 执行Barrier对齐操作:block住这个Barrier所在的InputChannel
onBarrier(channelIndex);
}
/**
* 得到的barrierId大于当前的CheckpointId,说明在完成当前Checkpoint之前又有新的Barrier事件来了。
* 这种情况,会忽略当前Checkpoint,并开启新的Checkpoint
*/
else if (barrierId > currentCheckpointId) {
LOG.warn("{}: Received checkpoint barrier for checkpoint {} before completing current checkpoint {}. " +
"Skipping current checkpoint.",
taskName,
barrierId,
currentCheckpointId);
// 通知Task,手头儿上的这个Checkpoint没做完
notifyAbort(currentCheckpointId,
new CheckpointException(
"Barrier id: " + barrierId,
CheckpointFailureReason.CHECKPOINT_DECLINED_SUBSUMED));
// 释放Block并重置Barrier:紧急叫停手头儿上的这个Checkpoint
releaseBlocksAndResetBarriers();
checkpointAborted = true;
// 开始做新的Checkpoint
beginNewAlignment(barrierId, channelIndex);
}
/**
* 以上条件都不满足,说明当前的Checkpoint操作已被取消,或Barrier信息属于以前的Checkpoint,就此打住,啥也不干
*/
else {
return false;
}
}
else if (barrierId > currentCheckpointId) {
// 这是一个新Checkpoint的First Barrier
beginNewAlignment(barrierId, channelIndex);
}
else {
return false;
}
/**
* 当Barrier接收的数量+Channel关闭的数量=整个InputChannel的数量时,说明所有的Barrier已经对齐,立即触发Checkpoint操作
*/
if (numBarriersReceived + numClosedChannels == totalNumberOfInputChannels) {
if (LOG.isDebugEnabled()) {
LOG.debug("{}: Received all barriers, triggering checkpoint {} at {}.",
taskName,
receivedBarrier.getId(),
receivedBarrier.getTimestamp());
}
// 解除所有InputChannel的Block状态
releaseBlocksAndResetBarriers();
// 触发该Task实例的Checkpoint操作
notifyCheckpoint(receivedBarrier, bufferedBytes, latestAlignmentDurationNanos);
return true;
}
return checkpointAborted;
}
3.StreamTask执行Checkpoint操作
StreamTask提供了“异步执行Checkpoint”的具体实现逻辑
- 让OperatorChain中的所有StreamOperator都执行“pre-barrier”
- 将CheckpointBarrier广播给下游节点的所有InputChannel
- 让OperatorChain的所有StreamOperator,执行状态数据的快照操作。执行成功后给InputGate返回True
/**
* 开始执行Task实例的Checkpoint操作
* 该方法的触发方式有2种:
* 1.CheckpointCoordinator组件周期性的触发Source节点的Checkpoint操作
* 2.下游算子通过CheckpointBarrier对齐触发本节点算子的Checkpoint操作
* 不管哪种触发,最终都得调用该方法完成状态数据的持久化。
* 注意:会先将Barrier广播给下游算子,然后本Task才会做自己的Checkpoint。如此循环...
*/
private boolean performCheckpoint(
CheckpointMetaData checkpointMetaData,
CheckpointOptions checkpointOptions,
CheckpointMetrics checkpointMetrics,
boolean advanceToEndOfTime) throws Exception {
LOG.debug("Starting checkpoint ({}) {} on task {}",
checkpointMetaData.getCheckpointId(), checkpointOptions.getCheckpointType(), getName());
final long checkpointId = checkpointMetaData.getCheckpointId();
// 判断当前Task是否正常运行
if (isRunning) {
// 核心:使用线程池以异步非阻塞的方式执行Checkpoint操作(不会block数据的正常处理)
actionExecutor.runThrowing(() -> {
if (checkpointOptions.getCheckpointType().isSynchronous()) {
setSynchronousSavepointId(checkpointId);
if (advanceToEndOfTime) {
advanceToEndOfEventTime();
}
}
/**
* step 1:让OperatorChain中的所有Operator执行“pre-barrier”
*/
operatorChain.prepareSnapshotPreBarrier(checkpointId);
/**
* stpe 2:先将CheckpointBarrier事件广播到下游的节点中
* 广播本质:在Task的输出ResultPartition中,会给下游所有的Channel都发送一个CheckpointBarrier事件
*/
operatorChain.broadcastCheckpointBarrier(
checkpointId,
checkpointMetaData.getTimestamp(),
checkpointOptions);
/**
* step 3(核心):对StreamTask中的OperatorChain内的所有StreamOperator,执行状态快照操作。执行成功后给InputGate返回True
*/
checkpointState(checkpointMetaData, checkpointOptions, checkpointMetrics);
});
return true;
} else {
/**
* 如果isRunning为false,表示Task不在运行状态。这种状态下,需要将“CancelCheckpointMarker消息”发送给OperatorChain中所有的StreamOperator,
* 并向下游算子进行广播。当且仅当OperatorChain中的算子还没执行完Checkpoint操作时,下游算子收到“CancelCheckpointMarker消息”后,才会立即取消Checkpoint操作。
*/
actionExecutor.runThrowing(() -> {
final CancelCheckpointMarker message = new CancelCheckpointMarker(checkpointMetaData.getCheckpointId());
// 将“CancelCheckpointMarker消息”广播给下游的其他算子
recordWriter.broadcastEvent(message);
});
return false;
}
}
StreamTask中会遍历OperatorChain的所有StreamOperator,将每个StreamOperator的“状态快照操作”按照“OperatorId:OperatorSnapshotFutures对象”的映射关系,保存到Map中。由线程池中的AsyncCheckpointRunnable来执行这些“状态快照操作”
/**
* 真正地执行Checkpoint操作:在线程池中执行Runnable任务
* 核心逻辑:OperatorSnapshotFutures表示为“状态快照持久化操作”,将其保存到Map集合中
*/
public void executeCheckpointing() throws Exception {
startSyncPartNano = System.nanoTime();
try {
// 遍历OperatorChain的所有StreamOperator
for (StreamOperator<?> op : allOperators) {
/**
* 定义每个StreamOperator的状态快照操作,并将其注册给OperatorSnapshotFutures对象等待执行,
* 按照“OperatorId:OperatorSnapshotFutures对象”的映射关系,保存到Map中。
* 下面会由线程池中的Runnable任务来异步执行这些“操作”
*/
checkpointStreamOperator(op);
}
省略部分代码...
// we are transferring ownership over snapshotInProgressList for cleanup to the thread, active on submit
// 创建Runnable任务:内含“装有每个StreamOperator的执行状态快照操作”所对应的Map集合
AsyncCheckpointRunnable asyncCheckpointRunnable = new AsyncCheckpointRunnable(
owner,
operatorSnapshotsInProgress, // 存放了所有Operator的“状态快照”的结果--OperatorSnapshotFutures对象
checkpointMetaData,
checkpointMetrics,
startAsyncPartNano);
owner.cancelables.registerCloseable(asyncCheckpointRunnable);
// 使用StreamTask内的ExecutorService(异步快照工作线程池),执行这个Runnable任务(容纳了每个StreamOperator的“快照持久化”的操作)
// 目的:不影响数据流的正常处理
owner.asyncOperationsThreadPool.execute(asyncCheckpointRunnable);
省略部分代码...
}
StreamOperator将“状态快照操作”封装到OperatorSnapshotFutures的逻辑,就是将算子状态、键控状态的StateBackend的snapshot操作,包装到Future中。
/**
* StreamTask在执行Checkpoint对状态数据进行snapshot时,如果对Checkpoint过程有特殊逻辑要求,可以在StreamOperator的子类中通过覆写CheckpointedFunction接口定义的钩子方法实现。
* 然后KeyedStateBackend、OperatorStateBackend的“快照持久化操作”会被set到OperatorSnapshotFutures中等待执行。
* 然后会按照“OperatorId:OperatorSnapshotFutures”的映射关系,存到Map集合中。Map集合会被添加到Runnable任务的执行逻辑中。
* 后面会有一个专门的异步快照工作线程池--ExecutorService,执行这个Runnable任务。
*/
@Override
public final OperatorSnapshotFutures snapshotState(long checkpointId, long timestamp, CheckpointOptions checkpointOptions,
CheckpointStreamFactory factory) throws Exception {
// KeyGroupRange是状态后端在对键控状态处理时,划定key的索引范围的。
// 如果keyedStateBackend不为null,就直接获取KeyGroupRange。否则,就new一个KeyGroupRange出来
KeyGroupRange keyGroupRange = null != keyedStateBackend ?
keyedStateBackend.getKeyGroupRange() : KeyGroupRange.EMPTY_KEY_GROUP_RANGE;
// 创建OperatorSnapshotFutures对象
OperatorSnapshotFutures snapshotInProgress = new OperatorSnapshotFutures();
// StateSnapshotContextSynchronousImpl用来存储“执行快照”的过程中需要用到的上下文信息
StateSnapshotContextSynchronousImpl snapshotContext = new StateSnapshotContextSynchronousImpl(
checkpointId,
timestamp,
factory,
keyGroupRange,
getContainingTask().getCancelables());
try {
// CheckpointedFunction定义了可以覆写的“钩子”方法,在StreamOperator的子类中(通过自定义Function)可以为其提供具体的实现逻辑,
// 以满足Checkpoint时的特殊逻辑要求,例如:删除状态中的某些数据、添加一些特殊数据等。
snapshotState(snapshotContext);
// 包装OperatorSnapshotFutures对象:指定专门用于处理原生状态数据的快照操作
snapshotInProgress.setKeyedStateRawFuture(snapshotContext.getKeyedStateStreamFuture());
snapshotInProgress.setOperatorStateRawFuture(snapshotContext.getOperatorStateStreamFuture());
// 把OperatorStateBackend的“快照持久化”操作包装到OperatorSnapshotFutures中,等待执行
if (null != operatorStateBackend) {
snapshotInProgress.setOperatorStateManagedFuture(
operatorStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));
}
// 把KeyedStateBackend的“快照持久化”操作包装到OperatorSnapshotFutures中,等待执行
if (null != keyedStateBackend) {
snapshotInProgress.setKeyedStateManagedFuture(
keyedStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));
}
} catch (Exception snapshotException) {
try {
snapshotInProgress.cancel();
} catch (Exception e) {
snapshotException.addSuppressed(e);
}
String snapshotFailMessage = "Could not complete snapshot " + checkpointId + " for operator " +
getOperatorName() + ".";
if (!getContainingTask().isCanceled()) {
LOG.info(snapshotFailMessage, snapshotException);
}
try {
snapshotContext.closeExceptionally();
} catch (IOException e) {
snapshotException.addSuppressed(e);
}
// 一旦上面做snapshot的过程中出现异常,就会往上层抛。TaskManager接到异常后,会取消所有Task任务并启动Job重启策略
throw new CheckpointException(snapshotFailMessage, CheckpointFailureReason.CHECKPOINT_DECLINED, snapshotException);
}
// 此时,经过包装的OperatorSnapshotFutures对象,拥有一堆的RunnableFuture(等待执行的异步任务)
return snapshotInProgress;
}
每个StreamOperator的snapshot操作均已包装成Future,通过线程池中的AsyncCheckpointRunnable执行。
1281

被折叠的 条评论
为什么被折叠?



