当前位置: 首页 > news >正文

Flink系列知识讲解之:深入了解 Flink 的网络协议栈

Flink系列知识之:深入了解 Flink 的网络协议栈

Flink 的网络协议栈是组成 flink-runtime 模块的核心组件之一,也是每个 Flink 任务的核心。它连接着来自所有任务管理器的各个工作单元(子任务)。这是流数据流过的地方,因此对 Flink 作业的吞吐量和延迟性能都至关重要。与通过 Akka 使用 RPC 的任务管理器和工作管理器之间的协调通道不同,任务管理器之间的网络堆栈依赖于使用 Netty 的低级应用程序接口。

本文初步介绍Flink的网络协议栈实现原理和各种优化策略,以及Flink在吞吐量和延迟之间的权衡。

网络协议栈-逻辑视角

Flink 的网络协议栈在子任务之间进行通信时,例如在 keyBy() 表示的网络shuffle过程中,为子任务提供了以下逻辑视图。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
Flink会将整个数据流DAG切分成多个subtask子任务的拓扑结构来表示。上图显示了上下游子任务间的网络shuffle逻辑图,它对以下三个概念的不同设置进行了抽象:

  • 子任务输出类型(ResultPartitionType)
    • 流水线(有界或无界):数据产生后立即向下游发送,可能是逐一发送,可以是有界记录流,也可以是无界记录流。
    • 阻塞:只在产生完整结果时才向下游发送数据。
  • 子任务调度类型:
    • 同时部署(急迫): 同时部署作业的所有子任务(用于流式应用)。
    • 在首次输出的下一阶段部署(懒惰): 一旦下游任务中的任何一个生产者产生了输出,就立即部署下游任务。
    • 完整输出后的下一阶段部署: 当任何或所有下游任务的生产者都生成了完整的输出数据集时,再部署下游任务。
  • 吞吐:
    • 高吞吐量: Flink 不会逐一发送每条记录,而是将大量记录缓冲到网络缓冲区,然后一起发送。这降低了每条记录的成本,提高了吞吐量。
    • 通过缓冲区超时降低延迟: 通过缩短发送未完全填满缓冲区的超时时间,可以牺牲吞吐量来换取低延迟。

我们将在下面介绍网络协议栈物理层的章节中了解吞吐量和低延迟优化。在这一部分,让我们再详细介绍一下输出和调度类型。首先,我们必须知道,子任务输出类型和调度类型密切相关,只有两者的特定组合才有效。

流水线式结果分区(result partitions)是一种流式输出,需要一个实时的下游目标子任务来发送数据。该下游子任务可以在上游子任务产生结果之前或首次输出时被调度启动。批处理任务会产生有界的结果分区,而流水作业则会产生无界的结果。

批处理任务也可能以阻塞方式生成结果,这取决于所使用的操作符和连接模式。在这种情况下,必须先生成完整结果,然后才能调度起下游的接收任务。这样,批处理任务就能以更低的资源使用率更高效地工作。

下表总结了有效的组合:

Output TypeScheduling TypeApplies to…
pipelined, unboundedall at once
next stage on first output
Streaming jobs
n/a¹
pipelined, boundedall at once
next stage on first output
n/a²
Batch jobs
blockingnext stage on complete outputBatch jobs

¹ 目前Flink还未使用。
² 批处理/流处理统一完成后,这可能会适用于流作业。

网路协议栈-物理传输

为了理解数据的物理传输,请记住,在 Flink 中,不同的子任务可以通过插槽共享组(slot sharing groups)共享同一个插槽。TaskManager也可以提供多个插槽,以便将同一任务的多个子任务调度到同一个TaskManager上。

在下图示例中,我们假设并行度为 4,部署两个TaskManager,每个TaskManager提供 2 个插槽(slot)。TaskManager 1 执行子任务 A.1、A.2、B.1 和 B.2,TaskManager 2 执行子任务 A.3、A.4、B.3 和 B.4。在任务 A 和任务 B 之间的网络shuffle连接中,例如利用 keyBy() 进行的连接,每个TaskManager需要处理 2x4 个逻辑连接,其中有些是本地连接,有些是远程连接:
在这里插入图片描述

在 Flink 的网络协议栈中,不同任务之间的每个(远程)网络连接都有自己的 TCP 通道。但是,如果同一任务的不同子任务被调度到同一个TaskManager上,它们与下游的网络连接将被多路复用,共享一个 TCP 通道,以减少资源使用。因此,需要注意在Flink中,网络数据交换是发生在TaskManager之间的,而不是task之间,在同一个TaskManager中的不同task会复用同一个TCP网络连接。
在我们的示例中,这将适用于 A.1 → B.3、A.1 → B.4,以及 A.2 → B.3 和 A.2 → B.4,如下图所示:

在这里插入图片描述

每个子任务的输出结果被称为结果分区(ResultPartition),每个分区又被分成不同的结果子分区(ResultSubpartition)— 每个逻辑通道一个。此时,Flink 不再处理单个记录,而是将一组序列化记录放入到网络缓冲区中。 每个子任务在其本地缓冲池(发送端和接收端各一个)中可使用的缓冲区数量最多限制为:

#channels * buffers-per-channel + floating-buffers-per-gate

单个 TaskManager 上的缓冲区总数通常不需要配置。如有需要,请参阅配置网络缓冲区文档,了解如何进行配置。

反压(一)

每当子任务的发送缓冲区池用完时(缓冲区位于结果子分区的缓冲区队列或下层 Netty 支持的网络堆栈内),生产者就会受阻,无法继续工作,并承受反向压力(Backpressure)。接收器的工作方式与此类似:下层网络堆栈中任何传入的 Netty 缓冲区都需要通过网络缓冲区提供给 Flink。如果相应子任务的缓冲池中没有可用的网络缓冲区,Flink 将停止从该通道读取数据,直到有可用的缓冲区为止。 这将对该子任务上的所有上游发送子任务造成有效的反向压力,因此也会扼杀其他接收子任务。下图说明了子任务 B.4 的反压情况,它将对上游任务造成反向压力,并阻止子任务 B.3 接收和处理更多缓冲区,即使它仍有容量。
在这里插入图片描述

为了防止这种情况发生,Flink 1.5 引入了自己的流量控制机制。

基于Credit-based的流控

基于Credit-based的流量控制可确保接收方有能力处理接收到的任何内容。它基于网络缓冲区的可用性。在这种流控机制下,每个远程输入通道都有自己的一组专属缓冲区(exclusive buffers),而不是只有一个共享的本地缓冲池。相反,本地缓冲池中的缓冲区被称为浮动缓冲区(floating buffers),因为它们会四处浮动,每个输入通道都可以使用。

接收方将向发送方发送基于credit的缓冲区可用性(1 buffer = 1 credit)。每个结果子分区(ResultSubPartition)都将跟踪其信道credit(channel credits)。当接收方将自己的可用credit值发给上游的输出方时,输出方会判断该credit值,只有在credit可用的情况下(credit > 0)才会被转发到下层网络协议栈尝试往下游发送。同时,每发送一个缓冲区的数据,credit值就会减少一个。在发送端,除了发送缓冲区的数据外,还会发送有关当前积压大小的信息,其中z说明上游有多少缓冲区正在该子分区的队列中等待被消费。接收方根据该积压信息会去请求适当数量的浮动缓冲区(Floating Buffers),以加快积压处理速度。它将尝试获取与积压大小一样多的浮动缓冲区,但这并不总是可能的,我们可能会只得到一些缓冲区或根本没有缓冲区。接收器将使用已获取的缓冲区,并等待有更多缓冲区可用时再继续申请使用。
在这里插入图片描述

比如,上图显示了credit-based的流控机制流程:

  • TaskManager 2的子任务B.4的独占缓冲区(exclustion buffers)只包含了两个buffer缓冲池,因此会通知给上游TaskManager 1的子任务A.2,告知其channel credit = 2。
  • Subtask A.2发送端根据下游接收端发送的credit值,选择发送2个buffer缓冲区的数据到下游,同时还会发送当前输出队列中积压的大小信息,比如这里的Backlog size = 5。
  • Subtask B.4接收端的独占缓冲区接收Subtask A.2发送的2个buffer缓冲区的数据,此时独占缓冲区中没有额外的可用缓冲区可以使用,因此更新其channel credit = 0。
  • Subtask B.4接收端接收到Backlog size的积压大小后,向共享的浮动缓冲区申请可用的buffer缓冲池,以此来加快处理上游的积压数据。
  • 如果能申请到额外的可用缓冲池,则可以继续处理上游的积压数据。否则,会将新的channel credit=0发送给Subtask A.2,从而使用Subtask A.2无法继续发送上游数据,从而产生反压。

credit-based的流量控制将使用`buffers-per-channel来指定独占缓冲区的数量(必选),并使用 floating-buffers-per-gate来指定本地缓冲区池的数量(可选),从而达到与无流量控制时相同的缓冲区限制。选择这两个参数的默认值,是为了在网络健康、延迟正常的情况下,使用流量控制时的最大(理论)吞吐量至少与不使用流量控制时相同。您可能需要根据实际的往返时间和带宽来调整这些参数。

对于本地缓冲池的数量设置来说,如果没有足够的可用缓冲区,每个缓冲池将获得相同份额的全局可用缓冲区(± 1)。

反压(二)

与没有流量控制的接收方反压机制相比,credit值提供了更直接的控制:如果接收方的消费速度无法跟上上游的生成速度,其可用credit值最终将为 0,从而阻止发送方将缓冲区转发到下层网络协议栈。同时,只有这个逻辑通道存在向上的反压,不需要阻止其他接收端从复用的 TCP 通道读取数据。因此,其他接收方在处理可用缓冲区时不会受到影响。

流控机制的作用

有了流量控制,相比与之前的没有流量机制方案,TaskManager间的TCP连接通道不会被过多的数据量所阻塞,从而能够实现多个子任务在复用同一个TCP连接时,一个逻辑通道不会阻塞另一个逻辑通道的处理,以此实现整体资源利用率的提高。
此外,通过对网络中传输数据量的完全控制,我们还能改进检查点对齐(checkpoint alignments):如果没有流量控制,发送端是无法感知到下游接收端的处理能力的,只会源源不断的将数据转发到下层网络协议栈然后发往下游。当一段时间后,接收端的处理速度跟不上数据生产速度,接收端缓冲区已满,源源不断的数据被堆积在网络连接通道中,这种情况下,任何的checkpoint barrier都需要在发送端的缓冲区后面排队,必须等待前面的所有缓冲区都处理完毕后才能开始(“障碍永远不会超过记录!”)。

不过,接收方需要发送的额外的credit信息可能会带来一些额外成本,尤其是在使用 SSL 加密通道的设置中。此外,单个输入通道无法使用缓冲池中的所有缓冲区,因为独占缓冲区不是共享的。同时,由于存在流控机制,发送端无法立即开始发送尽可能多的可用数据,因此如果上游任务产生数据的速度快于接收到的credit值代表的下游可用缓冲区大小,可能需要更长的时间来发送数据。

虽然这可能会影响任务的性能,但由于流量控制的所有优点,它通常是更好的选择。可能会希望通过增加每个输入通道的独占缓冲区的数量,但代价是使用更多内存。不过,与之前的实现相比,总体内存使用量可能仍然较低,因为较低的网络协议栈不再需要缓冲大量数据,因为总是可以立即将数据传输到 Flink。

在使用credit-based的流量控制时,可能还会注意到一件事:当在发送方和接收方之间的缓冲池设置的较小时,可能会更早地遇到反压。不过,这也是人们所希望的,毕竟缓冲更多数据并不会带来任何好处。如果想缓冲更多数据,但又想保持流量控制,可以考虑通过每门浮动缓冲区(floating-buffers-per-gate)来增加浮动缓冲区的数量。

AdvantagesDisadvantages
• 多个子任务在复用同一个TCP连接时,一个逻辑通道不会阻塞另一个逻辑通道的处理,提高资源利用率

• 改进了检查点对齐

• 减少内存使用量(减少低层网络中的数据量)
• 额外的credit信息

• 额外的积压信息(与缓冲信息捎带,几乎没有开销)

• 潜在的往返延迟

• 反压可能会出现较早

注意 如果需要关闭基于credit-based的流量控制,可将此添加到 flink-conf.yaml 中:

taskmanager.network.credit-model: false

不过,该参数已被弃用,最终将与非credit-based流量控制代码一起被删除。

网络协议栈内部原理详解

下图进一步详细介绍了从发送操作符算子收集记录到接收操作符算子获取记录的网络堆栈及其周边组件:
在这里插入图片描述

发送端算子创建记录并将其传递(例如通过 Collector#collect())后,记录将被交给 RecordWriter类,RecordWriter 会将记录从 Java 对象序列化为字节序列,最终进入网络缓冲区,如上所述进行传递。RecordWriter 首先使用 SpanningRecordSerializer 将记录序列化为灵活的堆上字节数组。然后,它会尝试将这些字节写入目标网络通道的相关网络缓冲区。我们将在下面的章节中讨论最后一部分。

在接收端,下层网络堆栈(netty)会将接收到的缓冲区记录写入相应的输入通道。接收端任务的线程最终会从这些队列中读取数据,并尝试在RecordReader的帮助下,通过 SpillingAdaptiveSpanningRecordDeserializer 将累积的字节反序列化为 Java 对象。与序列化器类似,该反序列化器也必须处理跨越多个网络缓冲区的记录等特殊情况,这可能是因为记录大于网络缓冲区(默认为 32KiB,通过 taskmanager.memory.segment-size 设置),也可能是因为序列化的记录被添加到了网络缓冲区,而该缓冲区没有足够的剩余字节。不过,Flink 会使用这些字节,并继续将其余字节写入新的网络缓冲区。

将缓冲区刷新到 Netty

在上图中,基于credit-based的流量控制机制实际上位于 “Netty 服务器”(和 “Netty 客户端”)组件中,RecordWriter 正在写入的缓冲区总是以空的状态(empty state)添加到结果子分区中,然后逐渐填入(序列化的)记录。但 Netty 到底什么时候才能获得缓冲区呢?显然,它不能在字节可用时就获取它们,因为这不仅会因跨线程通信和同步而增加大量成本(需要线程通信频繁地从缓冲区获取字节数据),还会使整个缓冲区过时。

在Flink中,有三种情况可以使缓冲区可供Netty服务器使用:

  • 向缓冲区写入记录时,缓冲区已满
  • 缓冲区超时
  • 发送checkpoint barrier之类的特殊事件。
缓冲区满后刷新

RecordWriter 使用本地序列化缓冲区处理当前记录,并逐步将这些字节写入相应结果子分区队列中的一个或多个网络缓冲区。尽管一个 RecordWriter 可以在多个子分区上工作,但每个子分区只有一个 RecordWriter 在向其写入数据。另一方面,Netty 服务器会从多个结果子分区读取数据,并如上所述将相应的结果子分区复用到一个通道中。这是一种经典的生产者-消费者模式,网络缓冲区位于中间,如下图所示。
在这里插入图片描述

  • 在(1)将数据序列化和(2)将数据写入缓冲区后,RecordWriter 会相应地更新缓冲区的writer index。
  • 一旦缓冲区完全填满,RecordWriter将 (3) 从本地缓冲区中获取一个新缓冲区,用于当前记录的剩余字节或下一条记录,并将新缓冲区添加到子分区队列中。
  • 这将 (4) 通知 Netty 服务器数据可用。只要 Netty 有能力处理该通知,它就会 (5) 提取缓冲区并通过适当的 TCP 通道发送。
缓冲区超时后刷新

为了支持低延迟使用场景,我们不能仅仅依靠缓冲区满来向下游发送数据。在某些情况下,某个通信通道可能没有太多记录流过,从而不必要地增加了实际拥有的少量记录的延迟。因此,存在一个周期性的进程(the output flusher),它会周期性地尝试将缓冲区中等待的可用数据发往下游。周期性间隔可通过 StreamExecutionEnvironment#setBufferTimeout 进行配置,并作为延迟的上限(适用于低吞吐量通道)。但是严格来说,output fluster不提供任何保证–它只向 Netty 发送通知,而 Netty 可以随意/按容量接收。这也意味着,如果通道被反压,即使满足了超时条件,也无法将缓冲区数据flush到下游。

下图显示了它与其他组件的交互方式:

  • RecordWriter 会像之前一样序列化并写入网络缓冲区。
  • 与此同时,如果 Netty 尚未意识到数据可用,Output Flusher可能会(3,4)通知 Netty 服务器数据可用(类似于上述 "缓冲区已满 "的情况)。
  • Netty 处理该通知 (5) 时,会从缓冲区中读取可用数据,并更新缓冲区的writer index。缓冲区将保留在队列中–Netty 服务器端对该缓冲区的任何进一步操作都将在下一次继续从reader index中读取数据。
    在这里插入图片描述
特殊事件后刷新

如果通过 RecordWriter 发送了一些特殊事件后也会立即触发缓冲区刷新。最重要的事件是checkpoint barrier或分区结束事件(end-of-partition events),这些事件显然应该快速处理,而不是等待Output Flusher启动。

字节缓冲区在两个Task之间的传输

在这里插入图片描述

上面这张图展示了一个细节更加丰富的流程,描述了一条数据记录从生产者传输到消费者的完整生命周期如下:

  1. 最初,MapDriver生成数据记录(通过Collector收集)并传递给RecordWriter对象。RecordWriter包含一组序列化器,每个消费数据的Task分别对应一个。ChannelSelector会选择一个或多个序列化器处理记录。例如,如果记录需要被广播,那么就会被交给每一个序列化器进行处理;如果记录是按照hash进行分区的,ChannelSelector会计算记录的哈希值,然后选择对应的序列化器。
  2. 序列化器会将记录序列化为二进制数据,并将其存放在固定大小的buffer中(一条记录可能需要跨越多个buffer)。这些buffer被交给BufferWriter处理,写入到ResulePartition(RP)中。RP有多个子分区(ResultSubpartitions-RSs)构成,每一个子分区都只收集特定消费者需要的数据。在上图中,需要被第二个reducer(在TaskManager2中)消费的记录被放在RS2中。由于第一个Buffer已经生成,RS2就变成可被消费的状态了(注意,这个行为实现了一个streaming shuffle),接着它通知JobManager。
  3. JobManager查找RS2的消费者,然后通知TaskManager2一个数据块已经可以访问了。通知TM2的消息会被发送到InputChannel,该inputchannel被认为是接收这个buffer的,接着通知RS2可以初始化一个网络传输了。然后,RS2通过TM1的网络栈请求该buffer,然后双方基于Netty准备进行数据传输。网络连接是在TaskManager(而非特定的task)之间长时间存在的。
  4. 一旦Buffer被TM2接收,它同样会经过一个类似的结构,起始于InputChannel,进入InputGate(它包含多个IC),最终进入一个反序列化器(RecordDeserializer),它会从buffer中将记录还原成指定类型的对象,然后将其传递给接收数据的Task。

数据交换机制的具体实现

数据交换从本质上来说就是一个典型的生产者-消费者模型,上游算子生产数据到ResultPartition中,下游算子通过InputGate消费数据。由于不同的Task可能在同一个TaskManager中运行,也可能在不同的TaskManager中运行:对于前者,不同的Task其实就是同一个TaskManager进程中的不同的线程,它们的数据交换就是在本地不同线程间进行的;对于后者,必须要通过网络进行通信。下图所示分别为数据在一个taskmanager内的流转、以及在不同的taskmanager之间的流转:
在这里插入图片描述

在这里插入图片描述

缓冲区生成器和缓冲区消费者

如果您想深入了解 Flink 是如何实现生产者-消费者机制的,请仔细研究一下 Flink 1.5 中引入的 BufferBuilder 和 BufferConsumer类。虽然读取可能只针对每个缓冲区,但写入则是针对每条记录,因此在 Flink 中,所有网络通信都是在热路径(hot path)上进行的。因此,我们很清楚,我们需要在任务线程和 Netty 线程之间建立一个轻量级连接,这样就不会产生太多同步开销。如需了解更多详情,建议查看源代码。

延迟与吞吐量

引入网络缓冲区是为了提高资源利用率和吞吐量,但代价是某些记录在缓冲区等待的时间会更长一些。虽然可以通过缓冲区超时给出等待时间的上限,但您可能想知道更多关于延迟和吞吐量这两个维度之间的权衡,因为很明显,两者是不可兼得的。
下图显示了缓冲区超时的各种值,从 0(每条记录刷新一次)到 100ms(默认值)不等,并显示了在一个有 100 个节点、每个节点有 8 个插槽(slot)的集群上运行一项没有业务逻辑、因此只测试网络协议栈的作业时产生的吞吐率。为便于比较,我们还绘制了 Flink 1.4 在添加低延迟改进(如上所述)之前的情况。
在这里插入图片描述

正如上图所示,在 Flink 1.5 以上版本中,即使缓冲超时时间设置的很低,如 1 毫秒(用于低延迟场景),也能提供高达默认超时 75% 的最大吞吐量。

总结

现在我们已经了解了结果分区(ResultPartition)、不同的网络连接以及批处理和流式处理的调度类型。还了解了基于credit-based的流量控制和网络协议栈的内部工作原理,从而可以推理出与网络相关的调优参数和某些作业行为。本系列未来的博文将以这些知识为基础,深入探讨更多操作细节,包括需要关注的相关指标、进一步的网络协议栈调整以及需要避免的常见反模式。

相关文章:

Flink系列知识讲解之:深入了解 Flink 的网络协议栈

Flink系列知识之:深入了解 Flink 的网络协议栈 Flink 的网络协议栈是组成 flink-runtime 模块的核心组件之一,也是每个 Flink 任务的核心。它连接着来自所有任务管理器的各个工作单元(子任务)。这是流数据流过的地方,…...

动态库dll与静态库lib编程4:MFC规则DLL讲解

文章目录 前言一、说明二、具体实现2.1新建项目2.2 模块切换的演示 总结 前言 动态库dll与静态库lib编程4:MFC规则DLL讲解。 一、说明 1.前面介绍的均为Win32DLL,即不使用MFC的DLL。 2.MFC规则DLL的特点:DLL内部可以使用MFC类库、可以被其他…...

TypeScript 后端开发中的热重载编译处理

在一些除了nest框架外的一些其他nodejs框架中没有提供对ts编译和热重载,如果使用typescript我们需要自己进行配置。 方法一(推荐) 使用bun运行环境(快)。注:一些不是使用js,ts代码编写的第三方…...

ORB-SLAM3源码学习:LoopClosing.cc:LoopClosing::NewDetectCommonRegions检测共同区域

前言 在ORB-SLAM2中校验闭环候选关键帧时,需要满足时序上连续3次成功校验才能通过。这就需要检测至少3个新进来的关键帧,这种方法牺牲了召回率来提升精度。由于必须严格的满足至少连续的三个条件这使得闭环的条件更加严格,即使存在真实的闭环…...

前端React Router从入门到进阶实战

React Router 是 React 应用中的一个重要库,它用于实现客户端的路由管理,能够将 URL 路径与 React 组件关联起来,从而实现页面之间的导航。React Router 不会像传统的多页面应用那样重新加载页面,而是通过组件切换来呈现不同的视图…...

关于华硕Armoury Crate(奥创中心)安装程序失败、卡进度条问题解决方案

关于华硕Armoury Crate(奥创中心)安装失败解决方案 清理旧版本文件 如果之前安装过Armoury Crate,可能有残留文件导致冲突: 利用官方的卸载工具,卸载旧版本: https://www.asus.com.cn/supportonly/armoury%20crate/…...

【WPF】 数据绑定机制之INotifyPropertyChanged

INotifyPropertyChanged 是 WPF 中的一个接口,用于实现 数据绑定 中的 属性更改通知。它的主要作用是,当对象的某个属性值发生更改时,通知绑定到该属性的 UI 控件更新其显示内容。 以下是有关 INotifyPropertyChanged 的详细信息和实现方法&…...

QPushButton的基础用法

概述 本文将详细介绍 QPushButton 的概念、常规用法、样式表以及一些特殊用法,帮助您深入理解和高效应用 QPushButton。当前Qt版本为6.8。 1. QPushButton 的基本概念 1.1 概述 QPushButton 是 Qt 中最常用的按钮控件之一,它表示一个可以被点击的按钮…...

股指期货有哪些优势?

股指期货,作为一种重要的金融衍生品,以其独特的交易方式和多样的优势,吸引了众多投资者的关注。本文将详细解析股指期货的定义、优势以及特点,帮助投资者更好地理解和把握这一市场。 一、股指期货的定义 股指期货,顾…...

STM32完全学习——FATFS0.15移植SD卡

一、下载FATFS源码 大家都知道使用CubMAX可以很快的将,FATFS文件管理系统移植到单片机上,但是别的芯片没有这么好用的工具,就需要自己从官网下载源码进行移植。我们首先解决SD卡的驱动问题,然后再移植FATFS文件管理系统。 二、SD…...

使用 Spring 状态机构建灵活的状态管理系统

引言 在软件开发中,状态机是一种非常重要的工具,尤其适用于需要处理复杂状态转换的场景。无论是订单管理系统中的订单状态、工作流引擎中的任务流转,还是审批流程的管理,状态机都能有效地帮助我们组织和管理不同的状态及其之间的…...

[SMARTFORMS] 添加设置图标

本地图片存放位置 使用事务码SE78进入表格图形管理页面,将导入图标到SAP系统 选择需要上传的图片地址,输入名称,描述和图片类型,点击导入按钮 完成上述操作以后,图标已导入到SAP系统中 提示Tips:如何将图标…...

网络世界的“交通规则”——TCP/IP(一)

一、非可靠传输的协议——UDP 1.1 UDP的报文格式 (1)UDP长度:表示整个UDP数据包的长度(报头载荷); (2)校验和:用于验证UDP数据包在传输过程中有没发生比特翻转&#xff…...

Excel中公式和函数的区别

Excel中公式和函数的区别 概念讲解例子公式函数 详细介绍函数面板最后再次进行演示操作文档 概念讲解 公式是用户自己编写的表达式,而函数是由Excel预定义的操作。公式可以包含各种数学运算符和逻辑表达式,函数则是执行特定任务的工具。公式可以引用其他…...

Node.js应用程序遇到了内存溢出的问题

vue 项目 跑起来,一直报错,内存溢出 在 文件node_modules 里 .bin > vue-cli-service.cmd 在依赖包这个文件第一行加上这个 node --max-old-space-size102400 "%~dp0\..\vue\cli-service\bin\vue-cli-service.js" %* node --max-old-s…...

MySQL关联关系理论与实践

MySQL 是一种关系型数据库管理系统,以其高性能、灵活性和易用性在开发者中广受欢迎。在 MySQL 中,数据存储以表格形式存在,表与表之间的关联关系构成了关系型数据库的核心。本篇文章将介绍 MySQL 关联关系的理论基础和常见实践,包…...

w138基于Spring Boot的宠物领养系统的设计与实现

🙊作者简介:多年一线开发工作经验,原创团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹赠送计算机毕业设计600个选题excel文…...

AMBA-CHI协议详解(十三)

AMBA-CHI协议详解(一)- Introduction AMBA-CHI协议详解(二)- Channel fields / Read transactions AMBA-CHI协议详解(三)- Write transactions AMBA-CHI协议详解(四)- Other transac…...

组合的能力

在《德鲁克最后的忠告》一书中,有这样一段话: 企业将由各种积木组建而成:人员、产品、理念和建筑。积木的设计组合至少和其供给一样重要。……对于一切程序、应用软件以及附件来说,重要的是掌握将已有的软件模块组合的能力&…...

若依使用 Undertow 替代 Tomcat 容器

文章目录 需求提出应用场景解决思路注意事项完整代码第一步:在 ruoyi-framework/pom.xml 文件中进行依赖配置第二步:修改 application.yml 配置文件第三步:修改文件上传工具类 FileUploadUtils.java 运行结果 需求提出 在开发若依框架的前后…...

STC单片机 IAP在线升级功能的使用介绍

STC单片机 IAP在线升级功能的使用介绍 从官方给出的IAP在线升级的demo示例来看,目前支持的型号有STC8、STC12、STC15、STC32,前提是需要使用64K/128K容量的型号。只支持用户程序容量小于60K 的项目。 🌿IAP在线升级方式支持:CAN、…...

visio 0xC004F017

1.好久没用的电脑突然显示Office tool plus 的软件(visio)失效了 显示错误代码: 0xC004F017 2.在软件中重新配置 关键:KMS用了 kms.03k.org 3.在软件中重新配置 许可选择 4.一些额外的检查方法 打开控制台输入 slmgr /dlv 中间过程 还遇到过【错误…...

IoC设计模式详解:控制反转的核心思想

前言:在软件开发中,设计模式是一种经过验证的、在特定场景下能有效解决问题的解决方案。控制反转(Inversion of Control,IoC) 作为一种设计模式,通过让程序的控制流和对象管理反转,从而使得代码…...

C语言延时实现

C语言延时实现 在C语言中,delay 函数通过空循环实现延时,而不是像其他高级语言(如Python)直接使用 sleep 函数。这种实现方式是基于单片机的特性和C语言的底层操作。下面详细解释为什么这种空循环可以实现延时,以及它…...

CDP集群安全指南-静态数据加密

[一]静态数据加密的架构 CDP 支持两种加密组件,这些组件可以组合成独特的解决方案。在选择密钥管理系统(KMS)时,您需要决定哪些组件能够满足企业的密钥管理和加密需求。 CDP 加密组件 以下是 Cloudera 用于静态数据加密的组件描…...

# LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game)

LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game) 在本篇博客中,我们将深入探讨 LeetCode 第2038题——如果相邻两个颜色均相同则删除当前颜色。该问题涉及字符串处理与游戏策略,旨在考察如何在给定规则下判断游戏的…...

极限学习机 (Extreme Learning Machine, ELM) 算法详解与PyTorch实现

极限学习机 (Extreme Learning Machine, ELM) 算法详解与PyTorch实现 目录 极限学习机 (Extreme Learning Machine, ELM) 算法详解与PyTorch实现1. 极限学习机 (ELM) 算法概述1.1 单隐层前馈神经网络1.2 ELM的优势2. ELM的核心技术2.1 模型定义2.2 随机初始化2.3 最小二乘法2.4…...

【insert 插入数据语法合集】.NET开源ORM框架 SqlSugar 系列

系列文章目录 🎀🎀🎀 .NET开源 ORM 框架 SqlSugar 系列 🎀🎀🎀 文章目录 系列文章目录一、前言 🍃二、插入方式 💯2.1 单条插入实体2.2 批量 插入实体2.3 根据字典插入2.4 根据 Dat…...

什么是.net framework,什么是.net core,什么是.net5~8,版本对应关系

我不知道有多少人和我一样,没学习过.netCore,想要学习,但是版本号太多就蒙了,不知道学什么了,这里解释下各个版本的关系 我们一般开始学习微软的时候,都是开始学习的.netframework,常用的就是4…...

【数据库系统概论】数据库安全性和存取控制和视图机制以及审计、数据加密--复习极简总结版

1. 数据库安全性概述 1.1 数据库安全性的重要性 数据库安全性是保护数据库防止非法访问、数据泄露、篡改或破坏的能力。它随着数据共享和网络化应用的普及而变得至关重要。现实案例: 2016年,某国医院遭受黑客攻击,黑客加密数据库并勒索赎金…...

深入理解计算机系统—虚拟内存(3)

9.9 动态内存分配 虽然可以使用低级的 mmap 和 munmap 函数来创建和删除虚拟内存的区域,但是 C程序员还是会觉得当运行时需要额外虚拟内存时,用 动态内存分配器 更方便,也有更好的可移植性。 动态内存分配器维护着一个进程的虚拟内存区域&…...

Vue项目整合与优化

前几篇文章,我们讲述了 Vue 项目构建的整体流程,从无到有的实现了单页和多页应用的功能配置,但在实现的过程中不乏一些可以整合的功能点及可行性的优化方案,就像大楼造完需要进行最后的项目验收改进一样,有待我们进一步…...

MyBatis 与 MyBatis-Plus 的区别

MyBatis 和 MyBatis-Plus 都是用于简化 Java 应用程序与数据库交互的持久层框架,但它们在功能、易用性和性能优化方面存在显著差异。下面将详细介绍两者之间的区别,并通过具体的代码示例进行对比。 概述 MyBatis:作为一款经典的持久层框架&a…...

如何让大模型不再“已读乱回”——RAG技术助力生成更精确的答案

随着大语言模型(LLM) 的迅猛发展,越来越多的领域开始受益于其强大的自然语言处理能力。从写作到编程,LLM已成为我们日常生活和工作的得力助手。然而,这些看似无所不能的大模型,却有一个致命的弱点&#xff…...

Anaconda环境配置(Windows11+python3.9)

文章目录 一、 下载ANACONDA(1)点击**Free Download**。(2)点击“skip registration”,跳过登录。(3)下载对应操作系统的ANACONDA版本。 二、 安装ANACONDA(1)双击运行安…...

Spring Boot 中的虚拟线程

什么是虚拟线程? 虚拟线程(Virtual Threads)是 Java 19 引入的一项新特性,它属于 Project Loom 项目的一部分。与传统的线程(平台线程)不同,虚拟线程并不是由操作系统直接管理,而是…...

el-table 实现纵向多级表头

为了实现上图效果,最开始打算用el-row、el-col去实现,但发现把表头和数据分成两大列时,数据太多时会导致所在格高度变高。但由于每一格数据肯定不一样,为保持高度样式一致,就需要我们手动去获取最高格的高度之后再设置…...

探秘Kafka源码:关键内容解析

文章目录 一、以kafka-3.0.0为例1.1安装 gradle 二、生产者源码2.1源码主流程图2.2 初始化2.3生产者sender线程初始化2.4 程序入口2.5生产者 main 线程初始化2.6 跳转到 KafkaProducer构造方法 一、以kafka-3.0.0为例 打开 IDEA,点击 File->Open…->源码包解…...

Promise编码小挑战

题目 我们将实现一个 createImage 函数,该函数返回一个 Promise,用于处理图片加载的异步操作。此外,还会实现暂停执行的 wait 函数。 Part 1: createImage 函数 该函数会: 创建一个新的图片元素。将图片的 src 设置为提供的路径…...

PyQt实战——将pcm文本数据转换成.pcm的二进制文件

系类往期文章: PyQt5实战——多脚本集合包,前言与环境配置(一) PyQt5实战——多脚本集合包,UI以及工程布局(二) PyQt5实战——多脚本集合包,程序入口QMainWindow(三&…...

数据结构之线性表

1.什么是线性表 线性表的概念 定义:线性表是由n个数据元素组成的有限序列。每个数据元素(除了第一个和最后一个)都有且仅有一个前驱和一个后继。逻辑结构:线性表的逻辑结构可以用一个序列来表示,例如 L(a1,a2,…,an)。…...

量子行走的干涉性和叠加性

需要注意公式的一些特殊情况,举例,当dj2和dj3 dj2 dj3...

Fabric环境部署-安装Go

安装go语言环境 国内镜像:Go下载 - Go语言中文网 - Golang中文社区 1.选择版本下载后解压:注意go1.11.linux-amd64.tar.gz换成你下的 sudo tar zxvf go1.21.linux-amd64.tar.gz -C /usr/local 2.. 创建Go目录 mkdir $HOME/go 3. 用vi打开~./bashrc&…...

网站设计总结后期维护与更新的重要性

当我们谈论网站设计时,往往会聚焦在初始阶段的创意和实现上。然而,一旦网站建成并上线,后期维护与更新的重要性就显得尤为突出。一个网站的成功不仅取决于其初始设计,更在于持续的维护与更新。 首先,后期维护能够确保网…...

『SQLite』详解运算符

内容摘要:本节讲解运算符,包括:算术运算符、比较运算符、逻辑运算符和位运算符。 什么是运算符? 运算符是一个保留字或字符,主要用于 SQLite 语句的 WHERE 子句中执行操作。它用于指定 SQLite 语句中的条件&#xff0…...

计算机网络--根据IP地址和路由表计算下一跳

一、必备知识 1.无分类地址IPV4地址网络前缀主机号 2.每个IPV4地址由32位二进制数组成 3. /15这个地址表示网络前缀有15位,那么主机号32-1517位。 4.地址掩码(子网掩码):所对应的网络前缀为1,主机号为0。 5.计算下…...

如何使用 Ansys OptiSlang 同时运行多个参数化设计研究

了解如何通过使用 OptiSLang 同时运行多个参数化设计研究来提高工作效率。 了解参数化设计研究的重要性 参数化设计研究在工程和设计过程中起着至关重要的作用。通过改变输入参数,工程师可以探索不同设计选择的效果,并优化其设计以满足性能、成本或其他…...

《 拼数 》

题目描述 设有 nn 个正整数 a1…ana1​…an​,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。 输入格式 第一行有一个整数,表示数字个数 nn。 第二行有 nn 个整数,表示给出的 nn 个整数 aiai​。 输出格…...

Memcached CAS 命令

Memcached CAS(Check-And-Set 或 Compare-And-Swap) 命令用于执行一个"检查并设置"的操作 它仅在当前客户端最后一次取值后,该key 对应的值没有被其他客户端修改的情况下, 才能够将值写入。 检查是通过cas_token参数进…...

ElasticSearch基础-文章目录

ElasticSearch学习总结1(环境安装) ElasticSearch学习总结2(基础查询) ElasticSearch学习总结3(.NetCore操作ES) ElasticSearch学习总结4(sql操作ES) ElasticSearch学习总结5&am…...