首页 文章 精选 留言 我的

精选列表

搜索[flink],共2082篇文章
优秀的个人博客,低调大师

Flink - NetworkEnvironment

NetworkEnvironment 是一个TaskManager对应一个,而不是一个task对应一个 其中最关键的是networkBufferPool, operator产生的中间结果,ResultPartition,或是input数据,InputGate 都是需要memory来暂存的,这就需要networkBufferPool来管理这部分内存 /** * Network I/O components of each {@link TaskManager} instance. The network environment contains * the data structures that keep track of all intermediate results and all data exchanges. * * When initialized, the NetworkEnvironment will allocate the network buffer pool. * All other components (netty, intermediate result managers, ...) are only created once the * environment is "associated" with a TaskManager and JobManager. This happens as soon as the * TaskManager actor gets created and registers itself at the JobManager. */ public class NetworkEnvironment { private final NetworkEnvironmentConfiguration configuration; private final NetworkBufferPool networkBufferPool; private ConnectionManager connectionManager; private ResultPartitionManager partitionManager; private ResultPartitionConsumableNotifier partitionConsumableNotifier; /** * ExecutionEnvironment which is used to execute remote calls with the * {@link JobManagerResultPartitionConsumableNotifier} */ private final ExecutionContext executionContext; /** * Initializes all network I/O components. */ public NetworkEnvironment( ExecutionContext executionContext, FiniteDuration jobManagerTimeout, NetworkEnvironmentConfiguration config) throws IOException { // create the network buffers - this is the operation most likely to fail upon // mis-configuration, so we do this first try { networkBufferPool = new NetworkBufferPool(config.numNetworkBuffers(), config.networkBufferSize(), config.memoryType()); } catch (Throwable t) { throw new IOException("Cannot allocate network buffer pool: " + t.getMessage(), t); } } } NetworkBufferPool 先看看networkBufferPool, 首先,它管理了一堆的BufferPool,而不是buffer,因为一个task manager只有一个networkBufferPool,所以对于每个task,需要分配一个buffer pool 再者,它的内存管理和memory manager一样的模式,从heap或off-heap申请相应数量的segments放入availableMemorySegments中 可以看到底下黄色部分,就是分配给networkBufferPool的heap /** * The NetworkBufferPool is a fixed size pool of {@link MemorySegment} instances * for the network stack. * * The NetworkBufferPool creates {@link LocalBufferPool}s from which the individual tasks draw * the buffers for the network data transfer. When new local buffer pools are created, the * NetworkBufferPool dynamically redistributes the buffers between the pools. */ public class NetworkBufferPool implements BufferPoolFactory { private final int totalNumberOfMemorySegments; //该Pool所管理的所有MemorySegment的数量 private final int memorySegmentSize; //memorySegment的大小,size private final Queue<MemorySegment> availableMemorySegments; //可用的MemorySegment队列 private final Set<LocalBufferPool> managedBufferPools = new HashSet<LocalBufferPool>(); //管理一组LocalBufferPool,每个task需要分配一个 public final Set<LocalBufferPool> allBufferPools = new HashSet<LocalBufferPool>(); private int numTotalRequiredBuffers; /** * Allocates all {@link MemorySegment} instances managed by this pool. */ public NetworkBufferPool(int numberOfSegmentsToAllocate, int segmentSize, MemoryType memoryType) { this.totalNumberOfMemorySegments = numberOfSegmentsToAllocate; this.memorySegmentSize = segmentSize; final long sizeInLong = (long) segmentSize; try { this.availableMemorySegments = new ArrayBlockingQueue<MemorySegment>(numberOfSegmentsToAllocate); //availableMemorySegments按totalNumberOfMemorySegments分配 } catch (OutOfMemoryError err) { } try { if (memoryType == MemoryType.HEAP) { //可以选择是从heap或off-heap分配 for (int i = 0; i < numberOfSegmentsToAllocate; i++) { byte[] memory = new byte[segmentSize]; availableMemorySegments.add(MemorySegmentFactory.wrapPooledHeapMemory(memory, null)); } } else if (memoryType == MemoryType.OFF_HEAP) { for (int i = 0; i < numberOfSegmentsToAllocate; i++) { ByteBuffer memory = ByteBuffer.allocateDirect(segmentSize); availableMemorySegments.add(MemorySegmentFactory.wrapPooledOffHeapMemory(memory, null)); } } else { throw new IllegalArgumentException("Unknown memory type " + memoryType); } } } public MemorySegment requestMemorySegment() { return availableMemorySegments.poll(); //request就是从availableMemorySegments里面取一个 } // This is not safe with regard to destroy calls, but it does not hurt, because destroy happens // only once at clean up time (task manager shutdown). public void recycle(MemorySegment segment) { availableMemorySegments.add(segment); //而回收就是放回availableMemorySegments } @Override public BufferPool createBufferPool(int numRequiredBuffers, boolean isFixedSize) throws IOException { // It is necessary to use a separate lock from the one used for buffer // requests to ensure deadlock freedom for failure cases. synchronized (factoryLock) { // Ensure that the number of required buffers can be satisfied. // With dynamic memory management this should become obsolete. if (numTotalRequiredBuffers + numRequiredBuffers > totalNumberOfMemorySegments) { //确定已经required的加上这次require的没有超过总量 throw new IOException(String.format("Insufficient number of network buffers: " + "required %d, but only %d available. The total number of network " + "buffers is currently set to %d. You can increase this " + "number by setting the configuration key '%s'.", numRequiredBuffers, totalNumberOfMemorySegments - numTotalRequiredBuffers, totalNumberOfMemorySegments, ConfigConstants.TASK_MANAGER_NETWORK_NUM_BUFFERS_KEY)); } this.numTotalRequiredBuffers += numRequiredBuffers; //增加numTotalRequiredBuffers // We are good to go, create a new buffer pool and redistribute // non-fixed size buffers. LocalBufferPool localBufferPool = new LocalBufferPool(this, numRequiredBuffers); //创建LocalBufferPool,这时并不会把segement给他,request是lazy的 // The fixed size pools get their share of buffers and don't change // it during their lifetime. if (!isFixedSize) { //如果不是Fixed,可以动态把多的segment分配出去 managedBufferPools.add(localBufferPool); } allBufferPools.add(localBufferPool); //管理localBufferPool redistributeBuffers(); return localBufferPool; } } // Must be called from synchronized block //目的就是把多余的segement也分配出去,利用起来 private void redistributeBuffers() throws IOException { int numManagedBufferPools = managedBufferPools.size(); if (numManagedBufferPools == 0) { return; // necessary to avoid div by zero when no managed pools } // All buffers, which are not among the required ones int numAvailableMemorySegment = totalNumberOfMemorySegments - numTotalRequiredBuffers; //多的Segments // Available excess (not required) buffers per pool int numExcessBuffersPerPool = numAvailableMemorySegment / numManagedBufferPools; //多的平均到每个bufferpool // Distribute leftover buffers in round robin fashion int numLeftoverBuffers = numAvailableMemorySegment % numManagedBufferPools; //余数 int bufferPoolIndex = 0; for (LocalBufferPool bufferPool : managedBufferPools) { int leftoverBuffers = bufferPoolIndex++ < numLeftoverBuffers ? 1 : 0; //余数可能是1或0 bufferPool.setNumBuffers(bufferPool.getNumberOfRequiredMemorySegments() + numExcessBuffersPerPool + leftoverBuffers); //在getNumberOfRequiredMemorySegments的基础上加上多余的 } } 可看到,当一个task需要申请buffer pool时,要先createBufferPool 即,在从availableMemorySegments中取出相应数量的segement,封装成LocalBufferPool,返回 这里有个managedBufferPools,表示bufferpool的size是可以动态变化的, redistributeBuffers会平均将现有可用的segments分配到所有当前的managedBufferPools上去 LocalBufferPool class LocalBufferPool implements BufferPool { private final NetworkBufferPool networkBufferPool; //总的bufferPool // The minimum number of required segments for this pool private final int numberOfRequiredMemorySegments; //要求申请的MemorySegments的个数,最小个数 // The current size of this pool private int currentPoolSize; //实际的MemorySegments的个数,如果不是fixed,可能会多 // The currently available memory segments. These are segments, which have been requested from // the network buffer pool and are currently not handed out as Buffer instances. private final Queue<MemorySegment> availableMemorySegments = new ArrayDeque<MemorySegment>(); //缓存MemorySegment的队列 // Buffer availability listeners, which need to be notified when a Buffer becomes available. // Listeners can only be registered at a time/state where no Buffer instance was available. private final Queue<EventListener<Buffer>> registeredListeners = new ArrayDeque<EventListener<Buffer>>(); // Number of all memory segments, which have been requested from the network buffer pool and are // somehow referenced through this pool (e.g. wrapped in Buffer instances or as available segments). private int numberOfRequestedMemorySegments; //已经分配的MemorySegments的个数 private boolean isDestroyed; private BufferPoolOwner owner; //owner复杂去释放networkBufferPool的buffer LocalBufferPool(NetworkBufferPool networkBufferPool, int numberOfRequiredMemorySegments) { this.networkBufferPool = networkBufferPool; this.numberOfRequiredMemorySegments = numberOfRequiredMemorySegments; //初始化的时候,numberOfRequiredMemorySegments,currentPoolSize相等 this.currentPoolSize = numberOfRequiredMemorySegments; } @Override public int getMemorySegmentSize() { return networkBufferPool.getMemorySegmentSize(); //MemorySegment本身的size } @Override public int getNumBuffers() { synchronized (availableMemorySegments) { return currentPoolSize; //当前local pool的size } } private Buffer requestBuffer(boolean isBlocking) throws InterruptedException, IOException { synchronized (availableMemorySegments) { returnExcessMemorySegments(); //把多申请的MemorySegment还回去,如果动态的情况下,是可能的 boolean askToRecycle = owner != null; while (availableMemorySegments.isEmpty()) { //如果availableMemorySegments中没有现成的 if (numberOfRequestedMemorySegments < currentPoolSize) { //只有在numberOfRequestedMemorySegments小于currentPoolSize,才能继续申请 final MemorySegment segment = networkBufferPool.requestMemorySegment(); //从networkBufferPool中申请一块 if (segment != null) { numberOfRequestedMemorySegments++; availableMemorySegments.add(segment); continue; //如果申请到继续 } } if (askToRecycle) { //如果申请不到,说明networkBufferPool也没有buffer了 owner.releaseMemory(1); //试图让owner去让networkBufferPool释放一块 } if (isBlocking) { availableMemorySegments.wait(2000); } else { return null; } } return new Buffer(availableMemorySegments.poll(), this); } } @Override public void recycle(MemorySegment segment) { synchronized (availableMemorySegments) { if (isDestroyed || numberOfRequestedMemorySegments > currentPoolSize) { returnMemorySegment(segment); //直接还回networkBufferPool } else { EventListener<Buffer> listener = registeredListeners.poll(); if (listener == null) { //如果没有listen,直接把segment放回availableMemorySegments availableMemorySegments.add(segment); availableMemorySegments.notify(); //触发通知availableMemorySegments有新的segment } else { try { listener.onEvent(new Buffer(segment, this)); //如果有listener,触发onEvent让listener去处理这个segment } catch (Throwable ignored) { availableMemorySegments.add(segment); availableMemorySegments.notify(); } } } } } @Override public void setNumBuffers(int numBuffers) throws IOException { synchronized (availableMemorySegments) { checkArgument(numBuffers >= numberOfRequiredMemorySegments, "Buffer pool needs at least " + numberOfRequiredMemorySegments + " buffers, but tried to set to " + numBuffers + "."); currentPoolSize = numBuffers; returnExcessMemorySegments(); // If there is a registered owner and we have still requested more buffers than our // size, trigger a recycle via the owner. if (owner != null && numberOfRequestedMemorySegments > currentPoolSize) { owner.releaseMemory(numberOfRequestedMemorySegments - numBuffers); } } } } associateWithTaskManagerAndJobManager NetworkEnvironment首先需要做的是associate,然后才能用 NetworkEnvironment 中有很多组件,是需要在绑定TaskManagerAndJobManager时,才需要去初始化的 /** * This associates the network environment with a TaskManager and JobManager. * This will actually start the network components. * * @param jobManagerGateway Gateway to the JobManager. * @param taskManagerGateway Gateway to the TaskManager. * * @throws IOException Thrown if the network subsystem (Netty) cannot be properly started. */ public void associateWithTaskManagerAndJobManager( ActorGateway jobManagerGateway, ActorGateway taskManagerGateway) throws IOException { synchronized (lock) { if (this.partitionConsumableNotifier == null && this.partitionManager == null && this.taskEventDispatcher == null && this.connectionManager == null) { // good, not currently associated. start the individual components LOG.debug("Starting result partition manager and network connection manager"); this.partitionManager = new ResultPartitionManager(); this.taskEventDispatcher = new TaskEventDispatcher(); this.partitionConsumableNotifier = new JobManagerResultPartitionConsumableNotifier( executionContext, jobManagerGateway, taskManagerGateway, jobManagerTimeout); this.partitionStateChecker = new JobManagerPartitionStateChecker( jobManagerGateway, taskManagerGateway); // ----- Network connections ----- final Option<NettyConfig> nettyConfig = configuration.nettyConfig(); connectionManager = nettyConfig.isDefined() ? new NettyConnectionManager(nettyConfig.get()) : new LocalConnectionManager(); try { LOG.debug("Starting network connection manager"); connectionManager.start(partitionManager, taskEventDispatcher, networkBufferPool); } catch (Throwable t) { throw new IOException("Failed to instantiate network connection manager: " + t.getMessage(), t); } } else { throw new IllegalStateException( "Network Environment is already associated with a JobManager/TaskManager"); } } } 主要是初始化一系列组件,TaskEventDispatcher,ConnectionManager, ResultPartitionManager JobManagerResultPartitionConsumableNotifier, JobManagerPartitionStateChecker 对于ConnectionManager,这里如果定义了netty,会创建NettyConnectionManager 这里面,主要是初始化Netty client和Netty server 否则是创建LocalConnectionManager 而对于ResultPartitionManager, 主要就是用于track所有的result partitions, 核心结构为, Table<ExecutionAttemptID, IntermediateResultPartitionID, ResultPartition>registeredPartitions=HashBasedTable.create(); 这个会记录所有的ResultPartition /** * The result partition manager keeps track of all currently produced/consumed partitions of a * task manager. */ public class ResultPartitionManager implements ResultPartitionProvider { private static final Logger LOG = LoggerFactory.getLogger(ResultPartitionManager.class); public final Table<ExecutionAttemptID, IntermediateResultPartitionID, ResultPartition> registeredPartitions = HashBasedTable.create(); private boolean isShutdown; public void registerResultPartition(ResultPartition partition) throws IOException { synchronized (registeredPartitions) { checkState(!isShutdown, "Result partition manager already shut down."); ResultPartitionID partitionId = partition.getPartitionId(); ResultPartition previous = registeredPartitions.put(partitionId.getProducerId(), partitionId.getPartitionId(), partition); } } } JobManagerResultPartitionConsumableNotifier,比较关键,通知JobMananger,ResultPartition已经ready,可以开始consume private static class JobManagerResultPartitionConsumableNotifier implements ResultPartitionConsumableNotifier { /** * {@link ExecutionContext} which is used for the failure handler of {@link ScheduleOrUpdateConsumers} * messages. */ private final ExecutionContext executionContext; private final ActorGateway jobManager; private final ActorGateway taskManager; private final FiniteDuration jobManagerMessageTimeout; @Override public void notifyPartitionConsumable(JobID jobId, final ResultPartitionID partitionId) { final ScheduleOrUpdateConsumers msg = new ScheduleOrUpdateConsumers(jobId, partitionId); //通知jobmanager,去deployconsumer Future<Object> futureResponse = jobManager.ask(msg, jobManagerMessageTimeout); //等JobManager的回复 futureResponse.onFailure(new OnFailure() { //失败,即无法deploy consumer @Override public void onFailure(Throwable failure) { LOG.error("Could not schedule or update consumers at the JobManager.", failure); // Fail task at the TaskManager FailTask failMsg = new FailTask( partitionId.getProducerId(), new RuntimeException("Could not notify JobManager to schedule or update consumers", failure)); taskManager.tell(failMsg); } }, executionContext); } } RegisterTask 在NetworkEnvironment中比较重要的操作,是注册task,需要为task的resultpartition和inputgate分配bufferpool public void registerTask(Task task) throws IOException { final ResultPartition[] producedPartitions = task.getProducedPartitions(); final ResultPartitionWriter[] writers = task.getAllWriters(); ResultPartitionConsumableNotifier jobManagerNotifier; synchronized (lock) { for (int i = 0; i < producedPartitions.length; i++) { final ResultPartition partition = producedPartitions[i]; final ResultPartitionWriter writer = writers[i]; // Buffer pool for the partition BufferPool bufferPool = null; try { bufferPool = networkBufferPool.createBufferPool(partition.getNumberOfSubpartitions(), false); //创建LocalPool,注意Reqired的segment数目是Subpartitions的数目,即一个subP一个segment partition.registerBufferPool(bufferPool); //把localPool注册到ResultPartition partitionManager.registerResultPartition(partition); //注册到partitionManager } // Register writer with task event dispatcher taskEventDispatcher.registerWriterForIncomingTaskEvents(writer.getPartitionId(), writer); } // Setup the buffer pool for each buffer reader final SingleInputGate[] inputGates = task.getAllInputGates(); for (SingleInputGate gate : inputGates) { BufferPool bufferPool = null; try { bufferPool = networkBufferPool.createBufferPool(gate.getNumberOfInputChannels(), false); gate.setBufferPool(bufferPool); } // Copy the reference to prevent races with concurrent shut downs jobManagerNotifier = partitionConsumableNotifier; } for (ResultPartition partition : producedPartitions) { // Eagerly notify consumers if required. if (partition.getEagerlyDeployConsumers()) { //如果是eager的方式,通知jobmanager,可以deploy consumer了 jobManagerNotifier.notifyPartitionConsumable( partition.getJobId(), partition.getPartitionId()); } } }

优秀的个人博客,低调大师

Flink -- Barrier

CheckpointBarrierHandler 这个接口用于react从input channel过来的checkpoint barrier,这里可以通过不同的实现来,决定是简单的track barriers,还是要去真正的block inputs /** * The CheckpointBarrierHandler reacts to checkpoint barrier arriving from the input channels. * Different implementations may either simply track barriers, or block certain inputs on * barriers. */ public interface CheckpointBarrierHandler { /** * Returns the next {@link BufferOrEvent} that the operator may consume. * This call blocks until the next BufferOrEvent is available, ir until the stream * has been determined to be finished. * * @return The next BufferOrEvent, or {@code null}, if the stream is finished. * @throws java.io.IOException Thrown, if the network or local disk I/O fails. * @throws java.lang.InterruptedException Thrown, if the thread is interrupted while blocking during * waiting for the next BufferOrEvent to become available. */ BufferOrEvent getNextNonBlocked() throws IOException, InterruptedException; /** * Registers the given event handler to be notified on successful checkpoints. * * @param checkpointHandler The handler to register. */ void registerCheckpointEventHandler(EventListener<CheckpointBarrier> checkpointHandler); /** * Cleans up all internally held resources. * * @throws IOException Thrown, if the cleanup of I/O resources failed. */ void cleanup() throws IOException; /** * Checks if the barrier handler has buffered any data internally. * @return True, if no data is buffered internally, false otherwise. */ boolean isEmpty(); } BarrierBuffer /** * The barrier buffer is {@link CheckpointBarrierHandler} that blocks inputs with barriers until * all inputs have received the barrier for a given checkpoint. * * <p>To avoid back-pressuring the input streams (which may cause distributed deadlocks), the * BarrierBuffer continues receiving buffers from the blocked channels and stores them internally until * the blocks are released.</p> */ public class BarrierBuffer implements CheckpointBarrierHandler { /** The gate that the buffer draws its input from */ private final InputGate inputGate; //输入 /** Flags that indicate whether a channel is currently blocked/buffered */ private final boolean[] blockedChannels; //被blocked的channels /** The total number of channels that this buffer handles data from */ private final int totalNumberOfInputChannels; /** To utility to write blocked data to a file channel */ private final BufferSpiller bufferSpiller; //为了不造成反压,对于被block的channl,不会真正的block,而是只是把数据放到buffer中 /** The pending blocked buffer/event sequences. Must be consumed before requesting * further data from the input gate. */ private final ArrayDeque<BufferSpiller.SpilledBufferOrEventSequence> queuedBuffered; //更多的没来得及处理的unblock buffer数据 /** The sequence of buffers/events that has been unblocked and must now be consumed * before requesting further data from the input gate */ private BufferSpiller.SpilledBufferOrEventSequence currentBuffered; //由bufferSpiller进行rollover产生的,已经unblock的buffer数据 /** Handler that receives the checkpoint notifications */ private EventListener<CheckpointBarrier> checkpointHandler; //创建checkpoint的逻辑 /** The ID of the checkpoint for which we expect barriers */ private long currentCheckpointId = -1L; /** The number of received barriers (= number of blocked/buffered channels) */ private int numBarriersReceived; /** The number of already closed channels */ private int numClosedChannels; /** Flag to indicate whether we have drawn all available input */ private boolean endOfStream; } 最关键的函数, getNextNonBlocked @Override public BufferOrEvent getNextNonBlocked() throws IOException, InterruptedException { while (true) { // process buffered BufferOrEvents before grabbing new ones BufferOrEvent next; if (currentBuffered == null) { //如果currentBuffered为空,说明没有unblock的buffer数据,直接从inputGate读取 next = inputGate.getNextBufferOrEvent(); } else { next = currentBuffered.getNext(); //从currentBuffered读 if (next == null) { //如果读到的为空,说明currentBuffered已经为空 completeBufferedSequence(); //清空当前的currentBuffered,看看queuedBuffered中还有没有需要处理的buffer return getNextNonBlocked(); } } if (next != null) { if (isBlocked(next.getChannelIndex())) { //如果这个channel仍然是被block的 // if the channel is blocked we, we just store the BufferOrEvent bufferSpiller.add(next); //那么我们只是把这个BufferOrEvent放到bufferSpiller里面 } else if (next.isBuffer()) { //如果没有被block,就处理该条数据,如果是buffer即真实数据,那么直接返回该数据 return next; } else if (next.getEvent().getClass() == CheckpointBarrier.class) { //如果是CheckpointBarrier if (!endOfStream) { // process barriers only if there is a chance of the checkpoint completing processBarrier((CheckpointBarrier) next.getEvent(), next.getChannelIndex()); //那么调用processBarrier,后面具体分析 } } else { if (next.getEvent().getClass() == EndOfPartitionEvent.class) { numClosedChannels++; // no chance to complete this checkpoint releaseBlocks(); //因为某个channel close了,那就永远也无法从这个channel获取barrier了,所以releaseBlocks } return next; } } else if (!endOfStream) { // end of stream. we feed the data that is still buffered endOfStream = true; releaseBlocks();//流结束了,所以也需要releaseBlocks return getNextNonBlocked(); } else { return null; } } } 其中两个函数比较重要processBarrier和releaseBlocks processBarrier private void processBarrier(CheckpointBarrier receivedBarrier, int channelIndex) throws IOException { final long barrierId = receivedBarrier.getId(); //取出全局barrier id if (numBarriersReceived > 0) { //如果之前收到过barrier // subsequent barrier of a checkpoint. if (barrierId == currentCheckpointId) { //看下刚收到的和之前的barrierid是否一样 // regular case onBarrier(channelIndex); //如果一样调用onBarrier } else if (barrierId > currentCheckpointId) { //如果大于currentCheckpointId,说明这个id已经过期了,因为在一个channel上,barrier id应该是按序发送的 // we did not complete the current checkpoint LOG.warn("Received checkpoint barrier for checkpoint {} before completing current checkpoint {}. " + "Skipping current checkpoint.", barrierId, currentCheckpointId); releaseBlocks(); //既然这个barrier已经过期,所以releaseBlocks() currentCheckpointId = barrierId; //设置新的barrierId onBarrier(channelIndex); } else { //忽略已过期的barrier // ignore trailing barrier from aborted checkpoint return; } } else if (barrierId > currentCheckpointId) { //新的barrier // first barrier of a new checkpoint currentCheckpointId = barrierId; onBarrier(channelIndex); } else { // trailing barrier from previous (skipped) checkpoint return; } // check if we have all barriers if (numBarriersReceived + numClosedChannels == totalNumberOfInputChannels) { //如果我们已经集齐所有的barrier if (LOG.isDebugEnabled()) { LOG.debug("Received all barrier, triggering checkpoint {} at {}", receivedBarrier.getId(), receivedBarrier.getTimestamp()); } if (checkpointHandler != null) { checkpointHandler.onEvent(receivedBarrier); //触发生成checkpoint } releaseBlocks(); 调用releaseBlocks } } 这里的onEvent,在StreamTask中定义, protected final EventListener<CheckpointBarrier> getCheckpointBarrierListener() { return new EventListener<CheckpointBarrier>() { @Override public void onEvent(CheckpointBarrier barrier) { try { triggerCheckpoint(barrier.getId(), barrier.getTimestamp()); //做checkpoint } catch (Exception e) { throw new RuntimeException("Error triggering a checkpoint as the result of receiving checkpoint barrier", e); } } }; } onBarrier,只是置标志位和计数,比较简单 private void onBarrier(int channelIndex) throws IOException { if (!blockedChannels[channelIndex]) { blockedChannels[channelIndex] = true; numBarriersReceived++; if (LOG.isDebugEnabled()) { LOG.debug("Received barrier from channel " + channelIndex); } } else { throw new IOException("Stream corrupt: Repeated barrier for same checkpoint and input stream"); } } releaseBlocks /** * Releases the blocks on all channels. Makes sure the just written data * is the next to be consumed. */ private void releaseBlocks() throws IOException { for (int i = 0; i < blockedChannels.length; i++) { blockedChannels[i] = false; } numBarriersReceived = 0; if (currentBuffered == null) { //理论上,在调用releaseBlocks前,所有channel都是处于blocked状态,所以currentBuffered应该为空 // common case: no more buffered data currentBuffered = bufferSpiller.rollOver(); //把block期间buffer的数据文件,设为currentBuffered if (currentBuffered != null) { currentBuffered.open(); } } else { //不为空,是uncommon的case // uncommon case: buffered data pending // push back the pending data, if we have any // since we did not fully drain the previous sequence, we need to allocate a new buffer for this one BufferSpiller.SpilledBufferOrEventSequence bufferedNow = bufferSpiller.rollOverWithNewBuffer(); if (bufferedNow != null) { bufferedNow.open(); queuedBuffered.addFirst(currentBuffered); //currentBuffered不为空,所以先把当前的放到queuedBuffered里面 currentBuffered = bufferedNow; } } } 看下BufferSpiller /** * Creates a new buffer spiller, spilling to one of the I/O manager's temp directories. * * @param ioManager The I/O manager for access to teh temp directories. * @param pageSize The page size used to re-create spilled buffers. * @throws IOException Thrown if the temp files for spilling cannot be initialized. */ public BufferSpiller(IOManager ioManager, int pageSize) throws IOException { this.pageSize = pageSize; this.readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE); this.readBuffer.order(ByteOrder.LITTLE_ENDIAN); this.headBuffer = ByteBuffer.allocateDirect(16); this.headBuffer.order(ByteOrder.LITTLE_ENDIAN); this.sources = new ByteBuffer[] { this.headBuffer, null }; //sources是由headBuffer和contents组成的 File[] tempDirs = ioManager.getSpillingDirectories(); this.tempDir = tempDirs[DIRECTORY_INDEX.getAndIncrement() % tempDirs.length]; byte[] rndBytes = new byte[32]; new Random().nextBytes(rndBytes); this.spillFilePrefix = StringUtils.byteToHexString(rndBytes) + '.'; // prepare for first contents createSpillingChannel(); } private void createSpillingChannel() throws IOException { //打开用于写buffer的文件 currentSpillFile = new File(tempDir, spillFilePrefix + (fileCounter++) +".buffer"); currentChannel = new RandomAccessFile(currentSpillFile, "rw").getChannel(); } 主要的function, add,加BufferOrEvent /** * Adds a buffer or event to the sequence of spilled buffers and events. * * @param boe The buffer or event to add and spill. * @throws IOException Thrown, if the buffer of event could not be spilled. */ public void add(BufferOrEvent boe) throws IOException { hasWritten = true; try { ByteBuffer contents; if (boe.isBuffer()) { //分为buffer或event来提取contents Buffer buf = boe.getBuffer(); contents = buf.getMemorySegment().wrap(0, buf.getSize()); } else { contents = EventSerializer.toSerializedEvent(boe.getEvent()); } headBuffer.clear(); //更新headBuffer headBuffer.putInt(boe.getChannelIndex()); headBuffer.putInt(contents.remaining()); headBuffer.put((byte) (boe.isBuffer() ? 0 : 1)); headBuffer.flip(); sources[1] = contents; //为什么加在1,因为0是headBuffer currentChannel.write(sources); //写入文件 } finally { if (boe.isBuffer()) { boe.getBuffer().recycle(); } } } rollOverInternal,把当前的spill文件返回, 生成新的spill文件 private SpilledBufferOrEventSequence rollOverInternal(boolean newBuffer) throws IOException { if (!hasWritten) { return null; } ByteBuffer buf; if (newBuffer) { //newBuffer的区别是,是否重新创建ByteBuffer还是直接用readBuffer buf = ByteBuffer.allocateDirect(READ_BUFFER_SIZE); buf.order(ByteOrder.LITTLE_ENDIAN); } else { buf = readBuffer; } // create a reader for the spilled data currentChannel.position(0L); SpilledBufferOrEventSequence seq = new SpilledBufferOrEventSequence(currentSpillFile, currentChannel, buf, pageSize); //把当前的spill文件封装成SpilledBufferOrEventSequence // create ourselves a new spill file createSpillingChannel(); //生成新的spill文件 hasWritten = false; return seq; } 对于SpilledBufferOrEventSequence,主要是提供读取的api,所以关键的函数是getNext /** * This class represents a sequence of spilled buffers and events, created by the * {@link BufferSpiller}. The sequence of buffers and events can be read back using the * method {@link #getNext()}. */ public static class SpilledBufferOrEventSequence { /** * Gets the next BufferOrEvent from the spilled sequence, or {@code null}, if the * sequence is exhausted. * * @return The next BufferOrEvent from the spilled sequence, or {@code null} (end of sequence). * @throws IOException Thrown, if the reads failed, of if the byte stream is corrupt. */ public BufferOrEvent getNext() throws IOException { if (buffer.remaining() < HEADER_LENGTH) { buffer.compact(); while (buffer.position() < HEADER_LENGTH) { if (fileChannel.read(buffer) == -1) { //从文件channel你们把数据读到buffer中 if (buffer.position() == 0) { // no trailing data return null; } else { throw new IOException("Found trailing incomplete buffer or event"); } } } buffer.flip(); } final int channel = buffer.getInt(); final int length = buffer.getInt(); final boolean isBuffer = buffer.get() == 0; if (isBuffer) { //如果是buffer // deserialize buffer MemorySegment seg = MemorySegmentFactory.allocateUnpooledSegment(pageSize); //创建 MemorySegment,这里是allocate unpooled的segment int segPos = 0; int bytesRemaining = length; while (true) { int toCopy = Math.min(buffer.remaining(), bytesRemaining); if (toCopy > 0) { seg.put(segPos, buffer, toCopy); //将buffer中的数据写入MemorySegment segPos += toCopy; bytesRemaining -= toCopy; } if (bytesRemaining == 0) { break; } else { buffer.clear(); if (fileChannel.read(buffer) == -1) { throw new IOException("Found trailing incomplete buffer"); } buffer.flip(); } } Buffer buf = new Buffer(seg, FreeingBufferRecycler.INSTANCE); //将MemorySegment封装成Buffer buf.setSize(length); return new BufferOrEvent(buf, channel); } else { //如果是event // deserialize event if (buffer.remaining() < length) { buffer.compact(); while (buffer.position() < length) { if (fileChannel.read(buffer) == -1) { throw new IOException("Found trailing incomplete event"); } } buffer.flip(); } int oldLimit = buffer.limit(); buffer.limit(buffer.position() + length); AbstractEvent evt = EventSerializer.fromSerializedEvent(buffer, getClass().getClassLoader()); //将buffer封装成event buffer.limit(oldLimit); return new BufferOrEvent(evt, channel); } } } BarrierTracker,这个比Barrier buffer的实现简单的多, 因为不会去block input channel,所以无法实现exactly once,只能实现at-least once /** * The BarrierTracker keeps track of what checkpoint barriers have been received from * which input channels. Once it has observed all checkpoint barriers for a checkpoint ID, * it notifies its listener of a completed checkpoint. * * <p>Unlike the {@link BarrierBuffer}, the BarrierTracker does not block the input * channels that have sent barriers, so it cannot be used to gain "exactly-once" processing * guarantees. It can, however, be used to gain "at least once" processing guarantees.</p> * * <p>NOTE: This implementation strictly assumes that newer checkpoints have higher checkpoint IDs.</p> */ public class BarrierTracker implements CheckpointBarrierHandler { @Override public BufferOrEvent getNextNonBlocked() throws IOException, InterruptedException { while (true) { BufferOrEvent next = inputGate.getNextBufferOrEvent(); if (next == null) { return null; } else if (next.isBuffer() || next.getEvent().getClass() != CheckpointBarrier.class) { //如果是数据就直接返回 return next; } else { processBarrier((CheckpointBarrier) next.getEvent()); //如果是barrier就处理 } } } private void processBarrier(CheckpointBarrier receivedBarrier) { // general path for multiple input channels final long barrierId = receivedBarrier.getId(); // find the checkpoint barrier in the queue of bending barriers CheckpointBarrierCount cbc = null; int pos = 0; for (CheckpointBarrierCount next : pendingCheckpoints) { //找找看,这个barrier是否直接收到过 if (next.checkpointId == barrierId) { cbc = next; break; } pos++; } if (cbc != null) { //如果收到过 // add one to the count to that barrier and check for completion int numBarriersNew = cbc.incrementBarrierCount(); //计数加一 if (numBarriersNew == totalNumberOfInputChannels) { //判断是否所有的barrier已经到全了 // checkpoint can be triggered // first, remove this checkpoint and all all prior pending // checkpoints (which are now subsumed) for (int i = 0; i <= pos; i++) { pendingCheckpoints.pollFirst(); //当一个checkpoint被触发时,prior的所有checkpoint就已经过期了,也一起remove掉 } // notify the listener if (checkpointHandler != null) { checkpointHandler.onEvent(receivedBarrier); //如果有checkpoint handler,就调用进行check point } } } else { //新的barrier // first barrier for that checkpoint ID // add it only if it is newer than the latest checkpoint. // if it is not newer than the latest checkpoint ID, then there cannot be a // successful checkpoint for that ID anyways if (barrierId > latestPendingCheckpointID) { latestPendingCheckpointID = barrierId; pendingCheckpoints.addLast(new CheckpointBarrierCount(barrierId)); // make sure we do not track too many checkpoints if (pendingCheckpoints.size() > MAX_CHECKPOINTS_TO_TRACK) { pendingCheckpoints.pollFirst(); //删除过多的checkpoints } } } } }

优秀的个人博客,低调大师

Flink - watermark

watermark,只有在有window的情况下才用到,所以在window operator前加上assignTimestampsAndWatermarks即可 不一定需要从source发出 1. 首先,source可以发出watermark 我们就看看kafka source的实现 protected AbstractFetcher( SourceContext<T> sourceContext, List<KafkaTopicPartition> assignedPartitions, SerializedValue<AssignerWithPeriodicWatermarks<T>> watermarksPeriodic, //在创建KafkaConsumer的时候assignTimestampsAndWatermarks SerializedValue<AssignerWithPunctuatedWatermarks<T>> watermarksPunctuated, ProcessingTimeService processingTimeProvider, long autoWatermarkInterval, //env.getConfig().setAutoWatermarkInterval() ClassLoader userCodeClassLoader, boolean useMetrics) throws Exception { //判断watermark的类型 if (watermarksPeriodic == null) { if (watermarksPunctuated == null) { // simple case, no watermarks involved timestampWatermarkMode = NO_TIMESTAMPS_WATERMARKS; } else { timestampWatermarkMode = PUNCTUATED_WATERMARKS; } } else { if (watermarksPunctuated == null) { timestampWatermarkMode = PERIODIC_WATERMARKS; } else { throw new IllegalArgumentException("Cannot have both periodic and punctuated watermarks"); } } // create our partition state according to the timestamp/watermark mode this.allPartitions = initializePartitions( assignedPartitions, timestampWatermarkMode, watermarksPeriodic, watermarksPunctuated, userCodeClassLoader); // if we have periodic watermarks, kick off the interval scheduler if (timestampWatermarkMode == PERIODIC_WATERMARKS) { //如果是定期发出WaterMark KafkaTopicPartitionStateWithPeriodicWatermarks<?, ?>[] parts = (KafkaTopicPartitionStateWithPeriodicWatermarks<?, ?>[]) allPartitions; PeriodicWatermarkEmitter periodicEmitter= new PeriodicWatermarkEmitter(parts, sourceContext, processingTimeProvider, autoWatermarkInterval); periodicEmitter.start(); } } FlinkKafkaConsumerBase public FlinkKafkaConsumerBase<T> assignTimestampsAndWatermarks(AssignerWithPeriodicWatermarks<T> assigner) { checkNotNull(assigner); if (this.punctuatedWatermarkAssigner != null) { throw new IllegalStateException("A punctuated watermark emitter has already been set."); } try { ClosureCleaner.clean(assigner, true); this.periodicWatermarkAssigner = new SerializedValue<>(assigner); return this; } catch (Exception e) { throw new IllegalArgumentException("The given assigner is not serializable", e); } } 这个接口的核心函数,定义,如何提取Timestamp和生成Watermark的逻辑 public interface AssignerWithPeriodicWatermarks<T> extends TimestampAssigner<T> { Watermark getCurrentWatermark(); } public interface TimestampAssigner<T> extends Function { long extractTimestamp(T element, long previousElementTimestamp); } 如果在初始化KafkaConsumer的时候,没有assignTimestampsAndWatermarks,就不会产生watermark 可以看到watermark有两种, PERIODIC_WATERMARKS,定期发送的watermark PUNCTUATED_WATERMARKS,由element触发的watermark,比如有element的特征或某种类型的element来表示触发watermark,这样便于开发者来控制watermark initializePartitions case PERIODIC_WATERMARKS: { @SuppressWarnings("unchecked") KafkaTopicPartitionStateWithPeriodicWatermarks<T, KPH>[] partitions = (KafkaTopicPartitionStateWithPeriodicWatermarks<T, KPH>[]) new KafkaTopicPartitionStateWithPeriodicWatermarks<?, ?>[assignedPartitions.size()]; int pos = 0; for (KafkaTopicPartition partition : assignedPartitions) { KPH kafkaHandle = createKafkaPartitionHandle(partition); AssignerWithPeriodicWatermarks<T> assignerInstance = watermarksPeriodic.deserializeValue(userCodeClassLoader); partitions[pos++] = new KafkaTopicPartitionStateWithPeriodicWatermarks<>( partition, kafkaHandle, assignerInstance); } return partitions; } KafkaTopicPartitionStateWithPeriodicWatermarks 这个类里面最核心的函数, public long getTimestampForRecord(T record, long kafkaEventTimestamp) { return timestampsAndWatermarks.extractTimestamp(record, kafkaEventTimestamp); } public long getCurrentWatermarkTimestamp() { Watermark wm = timestampsAndWatermarks.getCurrentWatermark(); if (wm != null) { partitionWatermark = Math.max(partitionWatermark, wm.getTimestamp()); } return partitionWatermark; } 可以看到是调用你定义的AssignerWithPeriodicWatermarks来实现 PeriodicWatermarkEmitter private static class PeriodicWatermarkEmitter implements ProcessingTimeCallback { public void start() { timerService.registerTimer(timerService.getCurrentProcessingTime() + interval, this); //start定时器,定时触发 } @Override public void onProcessingTime(long timestamp) throws Exception { //触发逻辑 long minAcrossAll = Long.MAX_VALUE; for (KafkaTopicPartitionStateWithPeriodicWatermarks<?, ?> state : allPartitions) { //对于每个partitions // we access the current watermark for the periodic assigners under the state // lock, to prevent concurrent modification to any internal variables final long curr; //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (state) { curr = state.getCurrentWatermarkTimestamp(); //取出当前partition的WaterMark } minAcrossAll = Math.min(minAcrossAll, curr); //求min,以partition中最小的partition作为watermark } // emit next watermark, if there is one if (minAcrossAll > lastWatermarkTimestamp) { lastWatermarkTimestamp = minAcrossAll; emitter.emitWatermark(new Watermark(minAcrossAll)); //emit } // schedule the next watermark timerService.registerTimer(timerService.getCurrentProcessingTime() + interval, this); //重新设置timer } } 2. DataStream也可以设置定时发送Watermark 其实实现是加了个chain的TimestampsAndPeriodicWatermarksOperator DataStream /** * Assigns timestamps to the elements in the data stream and periodically creates * watermarks to signal event time progress. * * <p>This method creates watermarks periodically (for example every second), based * on the watermarks indicated by the given watermark generator. Even when no new elements * in the stream arrive, the given watermark generator will be periodically checked for * new watermarks. The interval in which watermarks are generated is defined in * {@link ExecutionConfig#setAutoWatermarkInterval(long)}. * * <p>Use this method for the common cases, where some characteristic over all elements * should generate the watermarks, or where watermarks are simply trailing behind the * wall clock time by a certain amount. * * <p>For the second case and when the watermarks are required to lag behind the maximum * timestamp seen so far in the elements of the stream by a fixed amount of time, and this * amount is known in advance, use the * {@link BoundedOutOfOrdernessTimestampExtractor}. * * <p>For cases where watermarks should be created in an irregular fashion, for example * based on certain markers that some element carry, use the * {@link AssignerWithPunctuatedWatermarks}. * * @param timestampAndWatermarkAssigner The implementation of the timestamp assigner and * watermark generator. * @return The stream after the transformation, with assigned timestamps and watermarks. * * @see AssignerWithPeriodicWatermarks * @see AssignerWithPunctuatedWatermarks * @see #assignTimestampsAndWatermarks(AssignerWithPunctuatedWatermarks) */ public SingleOutputStreamOperator<T> assignTimestampsAndWatermarks( AssignerWithPeriodicWatermarks<T> timestampAndWatermarkAssigner) { // match parallelism to input, otherwise dop=1 sources could lead to some strange // behaviour: the watermark will creep along very slowly because the elements // from the source go to each extraction operator round robin. final int inputParallelism = getTransformation().getParallelism(); final AssignerWithPeriodicWatermarks<T> cleanedAssigner = clean(timestampAndWatermarkAssigner); TimestampsAndPeriodicWatermarksOperator<T> operator = new TimestampsAndPeriodicWatermarksOperator<>(cleanedAssigner); return transform("Timestamps/Watermarks", getTransformation().getOutputType(), operator) .setParallelism(inputParallelism); } TimestampsAndPeriodicWatermarksOperator public class TimestampsAndPeriodicWatermarksOperator<T> extends AbstractUdfStreamOperator<T, AssignerWithPeriodicWatermarks<T>> implements OneInputStreamOperator<T, T>, Triggerable { private transient long watermarkInterval; private transient long currentWatermark; public TimestampsAndPeriodicWatermarksOperator(AssignerWithPeriodicWatermarks<T> assigner) { super(assigner); //AbstractUdfStreamOperator(F userFunction) this.chainingStrategy = ChainingStrategy.ALWAYS; //一定是chain } @Override public void open() throws Exception { super.open(); currentWatermark = Long.MIN_VALUE; watermarkInterval = getExecutionConfig().getAutoWatermarkInterval(); if (watermarkInterval > 0) { registerTimer(System.currentTimeMillis() + watermarkInterval, this); //注册到定时器 } } @Override public void processElement(StreamRecord<T> element) throws Exception { final long newTimestamp = userFunction.extractTimestamp(element.getValue(), //由element中基于AssignerWithPeriodicWatermarks提取时间戳 element.hasTimestamp() ? element.getTimestamp() : Long.MIN_VALUE); output.collect(element.replace(element.getValue(), newTimestamp)); //更新element的时间戳,再次发出 } @Override public void trigger(long timestamp) throws Exception { //定时器触发trigger // register next timer Watermark newWatermark = userFunction.getCurrentWatermark(); //取得watermark if (newWatermark != null && newWatermark.getTimestamp() > currentWatermark) { currentWatermark = newWatermark.getTimestamp(); // emit watermark output.emitWatermark(newWatermark); //发出watermark } registerTimer(System.currentTimeMillis() + watermarkInterval, this); //重新注册到定时器 } @Override public void processWatermark(Watermark mark) throws Exception { // if we receive a Long.MAX_VALUE watermark we forward it since it is used // to signal the end of input and to not block watermark progress downstream if (mark.getTimestamp() == Long.MAX_VALUE && currentWatermark != Long.MAX_VALUE) { currentWatermark = Long.MAX_VALUE; output.emitWatermark(mark); //forward watermark } } 可以看到在processElement会调用AssignerWithPeriodicWatermarks.extractTimestamp提取event time 然后更新StreamRecord的时间 然后在Window Operator中, @Override public void processElement(StreamRecord<IN> element) throws Exception { final Collection<W> elementWindows = windowAssigner.assignWindows( element.getValue(), element.getTimestamp(), windowAssignerContext); 会在windowAssigner.assignWindows时以element的timestamp作为assign时间

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册