轻量级容错机制(全局异步,局部同步)
保证exactly-once 语义
用于内部失败的恢复
基本原理:通过往source 注入barrier,barrier作为checkpoint的标志
流处理过程中的状态历史版本
具有可以replay的功能
外部恢复(应用重启和升级)
两种方式触发:Cancel with savepoint,手动主动触发
$s bin/flink savepoint :jobId [:targetDirectory]
$ bin/flink cancel -s [:targetDirectory] :jobId
Job Manager
Task Manger
Client
角色间的通信(Akka)
数据的传输(Netty)
Job模式
独享Dispatcher和ResourceManager
资源按需申请
适合⼤大作业
Session模式
共享Dispatcher和ResourceManager
共⽤用TaskManager
适合规模小,运⾏时间短的作业
全量聚合:ProcessWindowFunction
增量聚合:ReduceFunction、AggregateFunction、FoldFunction
GlobalPartitioner数据会被分发到下游算子的第一个实例中进行处理。
ShufflePartitioner数据会被随机分发到下游算子的每一个实例中进行处理。
RebalancePartitioner数据会被循环发送到下游的每一个实例中进行处理。
RescalePartitioner这种分区器会根据上下游算子的并行度,循环的方式输出到下游算子的每个实例。这里有点难以理解,假设上游并行度为 2,编号为 A 和 B。下游并行度为 4,编号为 1,2,3,4。那么 A 则把数据循环发送给 1 和 2,B 则把数据循环发送给 3 和 4。假设上游并行度为 4,编号为 A,B,C,D。下游并行度为 2,编号为 1,2。那么 A 和 B 则把数据发送给 1,C 和 D 则把数据发送给 2。
BroadcastPartitioner广播分区会将上游数据输出到下游算子的每个实例中。适合于大数据集和小数据集做 Jion 的场景。
ForwardPartitionerForwardPartitioner 用于将记录输出到下游本地的算子实例。它要求上下游算子并行度一样。简单的说,ForwardPartitioner 用来做数据的控制台打印。
KeyGroupStreamPartitionerHash 分区器。会将数据按 Key 的 Hash 值输出到下游算子实例中。
CustomPartitionerWrapper用户自定义分区器。需要用户自己实现 Partitioner接口,来定义自己的分区逻辑
Flink 实现了多种重启策略。
固定延迟重启策略(Fixed Delay Restart Strategy)
故障率重启策略(Failure Rate Restart Strategy)
没有重启策略(No Restart Strategy)
Fallback 重启策略(Fallback Restart Strategy)
Flink 是并行的,计算过程可能不在一个 Slot 中进行,那么有一种情况即:当我们需要访问同一份数据。那么 Flink 中的广播变量就是为了解决这种情况。
我们可以把广播变量理解为是一个公共的共享变量,我们可以把一个 dataset 数据集广播出去,然后不同的 task 在节点上都能够获取到,这个数据在每个节点上只会存在一份。
在一个 Flink Job 中,数据需要在不同的 task 中进行交换,整个数据交换是有 TaskManager 负责的,TaskManager 的网络组件首先从缓冲 buffer 中收集 records,然后再发送。Records 并不是一个一个被发送的,是积累一个批次再发送,batch 技术可以更加高效的利用网络资源。
Flink 的开发者认为批处理是流处理的一种特殊情况。批处理是有限的流处理。Flink 使用一个引擎支持了 DataSet API 和 DataStream API。
Flink 的分布式快照是根据 Chandy-Lamport 算法量身定做的。简单来说就是持续创建分布式数据流及其状态的一致快照。
核心思想是在 input source 端插入 barrier,控制 barrier 的同步来实现 snapshot 的备份和 exactly-once 语义。
Flink 通过实现两阶段提交和状态保存来实现端到端的一致性语义。分为以下几个步骤:
开始事务(beginTransaction)创建一个临时文件夹,来写把数据写入到这个文件夹里面
预提交(preCommit)将内存中缓存的数据写入文件并关闭
正式提交(commit)将之前写完的临时文件放入目标目录下。这代表着最终的数据会有一些延迟
丢弃(abort)丢弃临时文件
若失败发生在预提交成功后,正式提交前。可以根据状态来提交预提交的数据,也可删除预提交的数据。
Flink 并不是将大量对象存在堆上,而是将对象都序列化到一个预分配的内存块上。此外,Flink 大量的使用了堆外内存。如果需要处理的数据超出了内存限制,则会将部分数据存储到硬盘上。Flink 为了直接操作二进制数据实现了自己的序列化框架。
理论上 Flink 的内存管理分为三部分:
**Network Buffers:**这个是在 TaskManager 启动的时候分配的,这是一组用于缓存网络数据的内存,每个块是 32K,默认分配 2048 个,可以通过“taskmanager.network.numberOfBuffers”修改
**Memory Manage pool:**大量的 Memory Segment 块,用于运行时的算法(Sort/Join/Shuffle 等),这部分启动的时候就会分配。下面这段代码,根据配置文件中的各种参数来计算内存的分配方法。(heap or off-heap,这个放到下节谈),内存的分配支持预分配和 lazy load,默认懒加载的方式。
User Code,这部分是除了 Memory Manager 之外的内存用于 User code 和 TaskManager 本身的数据结构。
window 产生数据倾斜指的是数据在不同的窗口内堆积的数据量相差过多。本质上产生这种情况的原因是数据源头发送的数据量速度不同导致的。出现这种情况一般通过两种方式来解决:
在数据进入窗口前做预聚合
重新设计窗口聚合的 key
link 摒弃了 Java 原生的序列化方法,以独特的方式处理数据类型和序列化,包含自己的类型描述符,泛型类型提取和类型序列化框架。
TypeInformation 是所有类型描述符的基类。它揭示了该类型的一些基本属性,并且可以生成序列化器。TypeInformation 支持以下几种类型:
BasicTypeInfo: 任意 Java 基本类型或 String 类型
BasicArrayTypeInfo: 任意 Java 基本类型数组或 String 数组
WritableTypeInfo: 任意 Hadoop Writable 接口的实现类
TupleTypeInfo: 任意的 Flink Tuple 类型(支持 Tuple1 to Tuple25)。Flink tuples 是固定长度固定类型的 Java Tuple 实现
CaseClassTypeInfo: 任意的 Scala CaseClass(包括 Scala tuples)
PojoTypeInfo: 任意的 POJO (Java or Scala),例如,Java 对象的所有成员变量,要么是 public 修饰符定义,要么有 getter/setter 方法
GenericTypeInfo: 任意无法匹配之前几种类型的类
在 Flink 的后台任务管理中,我们可以看到 Flink 的哪个算子和 task 出现了反压。最主要的手段是资源调优和算子调优。
资源调优即是对作业中的 Operator 的并发数(parallelism)、CPU(core)、堆内存(heap_memory)等参数进行调优。
作业参数调优包括:并行度的设置,State 的设置,checkpoint 的设置。
Flink 内部是基于 producer-consumer 模型来进行消息传递的,Flink 的反压设计也是基于这个模型。Flink 使用了高效有界的分布式阻塞队列,就像 Java 通用的阻塞队列(BlockingQueue)一样。下游消费者消费变慢,上游就会受到阻塞。
为了更高效地分布式执行,Flink 会尽可能地将 operator 的 subtask 链接(chain)在一起形成 task。每个 task 在一个线程中执行。将 operators 链接成 task 是非常有效的优化:它能减少线程之间的切换,减少消息的序列化/反序列化,减少数据在缓冲区的交换,减少了延迟的同时提高整体的吞吐量。这就是我们所说的算子链。
两个 operator chain 在一起的的条件:
上下游的并行度一致
下游节点的入度为 1 (也就是说下游节点没有来自其他节点的输入)
上下游节点都在同一个 slot group 中(下面会解释 slot group)
下游节点的 chain 策略为 ALWAYS(可以与上下游链接,map、flatmap、filter 等默认是 ALWAYS)
上游节点的 chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source 默认是 HEAD)
两个节点间数据分区方式是 forward(参考理解数据流的分区)
用户没有禁用 chain
用户提交的 Flink Job 会被转化成一个 DAG 任务运行,分别是:StreamGraph、JobGraph、ExecutionGraph,Flink 中 JobManager 与 TaskManager,JobManager 与 Client 的交互是基于 Akka 工具包的,是通过消息驱动。整个 Flink Job 的提交还包含着 ActorSystem 的创建,JobManager 的启动,TaskManager 的启动和注册。
一个 Flink 任务的 DAG 生成计算图大致经历以下三个过程:
StreamGraph最接近代码所表达的逻辑层面的计算拓扑结构,按照用户代码的执行顺序向 StreamExecutionEnvironment 添加 StreamTransformation 构成流式图。
JobGraph从 StreamGraph 生成,将可以串联合并的节点进行合并,设置节点之间的边,安排资源共享 slot 槽位和放置相关联的节点,上传任务所需的文件,设置检查点配置等。相当于经过部分初始化和优化处理的任务图。
ExecutionGraph由 JobGraph 转换而来,包含了任务具体执行所需的内容,是最贴近底层实现的执行图。
JobManager 负责整个 Flink 集群任务的调度以及资源的管理,从客户端中获取提交的应用,然后根据集群中 TaskManager 上 TaskSlot 的使用情况,为提交的应用分配相应的 TaskSlot 资源并命令 TaskManager 启动从客户端中获取的应用。
JobManager 相当于整个集群的 Master 节点,且整个集群有且只有一个活跃的 JobManager ,负责整个集群的任务管理和资源管理。
JobManager 和 TaskManager 之间通过 Actor System 进行通信,获取任务执行的情况并通过 Actor System 将应用的任务执行情况发送给客户端。
同时在任务执行的过程中,Flink JobManager 会触发 Checkpoint 操作,每个 TaskManager 节点 收到 Checkpoint 触发指令后,完成 Checkpoint 操作,所有的 Checkpoint 协调过程都是在 Fink JobManager 中完成。
当任务完成后,Flink 会将任务执行的信息反馈给客户端,并且释放掉 TaskManager 中的资源以供下一次提交任务使用。
JobManager 的职责主要是接收 Flink 作业,调度 Task,收集作业状态和管理 TaskManager。它包含一个 Actor,并且做如下操作:
RegisterTaskManager: 它由想要注册到 JobManager 的 TaskManager 发送。注册成功会通过 AcknowledgeRegistration 消息进行 Ack。
SubmitJob: 由提交作业到系统的 Client 发送。提交的信息是 JobGraph 形式的作业描述信息。
CancelJob: 请求取消指定 id 的作业。成功会返回 CancellationSuccess,否则返回 CancellationFailure。
UpdateTaskExecutionState: 由 TaskManager 发送,用来更新执行节点(ExecutionVertex)的状态。成功则返回 true,否则返回 false。
RequestNextInputSplit: TaskManager 上的 Task 请求下一个输入 split,成功则返回 NextInputSplit,否则返回 null。
JobStatusChanged: 它意味着作业的状态(RUNNING, CANCELING, FINISHED,等)发生变化。这个消息由 ExecutionGraph 发送。
TaskManager 相当于整个集群的 Slave 节点,负责具体的任务执行和对应任务在每个节点上的资源申请和管理。
客户端通过将编写好的 Flink 应用编译打包,提交到 JobManager,然后 JobManager 会根据已注册在 JobManager 中 TaskManager 的资源情况,将任务分配给有资源的 TaskManager 节点,然后启动并运行任务。
TaskManager 从 JobManager 接收需要部署的任务,然后使用 Slot 资源启动 Task,建立数据接入的网络连接,接收数据并开始数据处理。同时 TaskManager 之间的数据交互都是通过数据流的方式进行的。
可以看出,Flink 的任务运行其实是采用多线程的方式,这和 MapReduce 多 JVM 进行的方式有很大的区别,Flink 能够极大提高 CPU 使用效率,在多个任务和 Task 之间通过 TaskSlot 方式共享系统资源,每个 TaskManager 中通过管理多个 TaskSlot 资源池进行对资源进行有效管理。
TaskManager 的启动流程较为简单:启动类:org.apache.flink.runtime.taskmanager.TaskManager核心启动方法 : selectNetworkInterfaceAndRunTaskManager启动后直接向 JobManager 注册自己,注册完成后,进行部分模块的初始化
TaskManager 中最细粒度的资源是 Task slot,代表了一个固定大小的资源子集,每个 TaskManager 会将其所占有的资源平分给它的 slot。
通过调整 task slot 的数量,用户可以定义 task 之间是如何相互隔离的。每个 TaskManager 有一个 slot,也就意味着每个 task 运行在独立的 JVM 中。每个 TaskManager 有多个 slot 的话,也就是说多个 task 运行在同一个 JVM 中。
而在同一个 JVM 进程中的 task,可以共享 TCP 连接(基于多路复用)和心跳消息,可以减少数据的网络传输,也能共享一些数据结构,一定程度上减少了每个 task 的消耗。 每个 slot 可以接受单个 task,也可以接受多个连续 task 组成的 pipeline,如下图所示,FlatMap 函数占用一个 taskslot,而 key Agg 函数和 sink 函数共用一个 taskslot
Flink 为了避免 JVM 的固有缺陷例如 java 对象存储密度低,FGC 影响吞吐和响应等,实现了自主管理内存。
MemorySegment 就是 Flink 的内存抽象。默认情况下,一个 MemorySegment 可以被看做是一个 32kb 大的内存块的抽象。这块内存既可以是 JVM 里的一个 byte[],也可以是堆外内存(DirectByteBuffer)。
在 MemorySegment 这个抽象之上,Flink 在数据从 operator 内的数据对象在向 TaskManager 上转移,预备被发给下个节点的过程中,使用的抽象或者说内存对象是 Buffer。
对接从 Java 对象转为 Buffer 的中间对象是另一个抽象 StreamRecord。
Flink 的容错机制的核心部分是制作分布式数据流和操作算子状态的一致性快照。 这些快照充当一致性 checkpoint,系统可以在发生故障时回滚。 Flink 用于制作这些快照的机制在“分布式数据流的轻量级异步快照”中进行了描述。 它受到分布式快照的标准 Chandy-Lamport 算法的启发,专门针对 Flink 的执行模型而定制。
barriers 在数据流源处被注入并行数据流中。快照 n 的 barriers 被插入的位置(我们称之为 Sn)是快照所包含的数据在数据源中最大位置。例如,在 Apache Kafka 中,此位置将是分区中最后一条记录的偏移量。 将该位置 Sn 报告给 checkpoint 协调器(Flink 的 JobManager)。
然后 barriers 向下游流动。当一个中间操作算子从其所有输入流中收到快照 n 的 barriers 时,它会为快照 n 发出 barriers 进入其所有输出流中。 一旦 sink 操作算子(流式 DAG 的末端)从其所有输入流接收到 barriers n,它就向 checkpoint 协调器确认快照 n 完成。在所有 sink 确认快照后,意味快照着已完成。
一旦完成快照 n,job 将永远不再向数据源请求 Sn 之前的记录,因为此时这些记录(及其后续记录)将已经通过整个数据流拓扑,也即是已经被处理结束。
构建抽象语法树的事情交给了 Calcite 去做。
SQL query 会经过 Calcite 解析器转变成 SQL 节点树,通过验证后构建成 Calcite 的抽象语法树(也就是图中的 Logical Plan)。
另一边,Table API 上的调用会构建成 Table API 的抽象语法树,并通过 Calcite 提供的 RelBuilder 转变成 Calcite 的抽象语法树。然后依次被转换成逻辑执行计划和物理执行计划。
在提交任务后会分发到各个 TaskManager 中运行,在运行时会使用 Janino 编译器编译代码后运行。
Flink 程序的基础构建单元是流(streams)与转换(transformations)。DataSet API 中使用的数据集也是一种流。数据流(stream)就是一组永远不会停止的数据记录流,而转换(transformation)是将一个或多个流作为输入,并生成一个或多个输出流的操作。
执行时,Flink程序映射到 streaming dataflows,由流(streams)和转换操作(transformation operators)组成。每个 dataflow 从一个或多个源(source)开始,在一个或多个接收器(sink)中结束。
Flink程序由多个任务(Source、Transformation、Sink)组成。任务被分成多个并行实例来执行,每个并行实例处理任务的输入数据的子集。任务的并行实例的数量称之为并行度。
Flink中的并行度可以从多个不同层面设置:
操作算子层面(Operator Level)、执行环境层面(Execution Environment Level)、客户端层面(Client Level)、系统层面(System Level)。
Flink可以设置好几个level的parallelism,其中包括Operator Level、Execution Environment Level、Client Level、System Level。
在flink-conf.yaml中通过parallelism.default配置项给所有execution environments指定系统级的默认parallelism;在ExecutionEnvironment里头可以通过setParallelism来给operators、data sources、data sinks设置默认的parallelism;如果operators、data sources、data sinks自己有设置parallelism则会覆盖ExecutionEnvironment设置的parallelism。
Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件,并把它放在taskmanager节点中,防止task重复拉取。
此缓存的工作机制如下:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。
当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,仅会执行一次。用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它。
在一个运行的application中,它的tasks在持续交换数据。TaskManager负责做数据传输。
TaskManager的网络组件首先从缓冲buffer中收集records,然后再发送。也就是说,records并不是一个接一个的发送,而是先放入缓冲,然后再以batch的形式发送。这个技术可以高效使用网络资源,并达到高吞吐。类似于网络或磁盘 I/O 协议中使用的缓冲技术。
Flink基于分布式快照与可部分重发的数据源实现了容错。用户可自定义对整个Job进行快照的时间间隔,当任务失败时,Flink会将整个Job恢复到最近一次快照,并从数据源重发快照之后的数据。
Flink源码中有一个独立的connector模块,所有的其他connector都依赖于此模块,Flink 在1.9版本发布的全新kafka连接器,摒弃了之前连接不同版本的kafka集群需要依赖不同版本的connector这种做法,只需要依赖一个connector即可。
数据倾斜和数据热点是所有大数据框架绕不过去的问题。 处理这类问题主要从3个方面入手:
在业务上规避这类问题:例如一个假设订单场景,北京和上海两个城市订单量增长几十倍,其余城市的数据量不变。 这时候我们在进行聚合的时候,北京和上海就会出现数据堆积,我们可以单独数据北京和上海的数据。
Key的设计上:把热key进行拆分,比如上个例子中的北京和上海,可以把北京和上海按照地区进行拆分聚合。
参数设置:Flink 1.9.0 SQL(Blink Planner) 性能优化中一项重要的改进就是升级了微批模型,即 MiniBatch。 原理是缓存一定的数据后再触发处理,以减少对State的访问,从而提升吞吐和减少数据的输出量。
Storm 是通过监控 Bolt 中的接收队列负载情况,如果超过高水位值就会将反压信息写到 Zookeeper ,Zookeeper 上的 watch 会通知该拓扑的所有 Worker 都进入反压状态,最后 Spout 停止发送 tuple。
Flink中的反压使用了高效有界的分布式阻塞队列,下游消费变慢会导致发送端阻塞。
二者最大的区别是Flink是逐级反压,而Storm是直接从源头降速。
可以在处理前加一个fliter算子,将不符合规则的数据过滤出去。
Flink的窗口处理流式数据虽然提供了基础EventTime的WaterMark机制,但是只能在一定程度上解决数据乱序问题。而某些极端情况下数据延迟会非常严重,即便通过WaterMark机制也无法等到数据全部进入窗口再进行处理。默认情况下,Flink会将这些严重迟到的数据丢弃掉;如果用户希望即使数据延迟到达,也能够按照流程处理并输出结果,此时可以借助Allowed Lateness机制来对迟到的数据进行额外的处理。
通过watermark机制来处理out-of-order的问题,属于第一层防护,属于全局性的防护,通常说的乱序问题的解决办法,就是指这类;
通过窗口上的allowedLateness机制来处理out-of-order的问题,属于第二层防护,属于特定window operator的防护,late element的问题就是指这类。
flink采用
watermark
allowedLateness()
sideOutputLateData()
三个机制来保证获取数据
涉及到state,可以通过Queryable State来查询和修改state
通过制定历史时间戳回滚历史数据,(这个地方需要注意的细节很多,你回滚历史数据,你之前写入的数据如何处理,是删除还是可以覆盖写入,自己思考清楚)
任务异常挂掉,可以通过checkpoint启动任务
解答: 1. 我们使用 yarn session 模式提交任务。每次提交都会创建一个新的 Flink 集群,为每一个 job 提供一个 yarn-session,任务之间互相独立,互不影响, 方便管理。任务执行完成之后创建的集群也会消失。线上命令脚本如下: bin/yarn-session.sh -n 7 -s 8 -jm 3072 -tm 32768 -qu root.. -nm - -d 其中申请 7 个 taskManager,每个 8 核,每个 taskmanager 有 32768M 内存。
2. 集群默认只有一个 Job Manager。但为了防止单点故障,我们配置了高可用。 我们公司一般配置一个主 Job Manager,两个备用 Job Manager,然后结合 ZooKeeper 的使用,来达到高可用。
解答:我们一般碰到的压力来自以下几个方面:
一,产生数据流的速度如果过快,而下游的算子消费不过来的话,会产生背压。 背压的监控可以使用 Flink Web UI(localhost:8081) 来可视化监控,一旦报警就能知 道。一般情况下背压问题的产生可能是由于 sink 这个 操作符没有优化好,做一下 优化就可以了。比如如果是写入 ElasticSearch, 那么可以改成批量写入,可以调 大 ElasticSearch 队列的大小等等策略。
二,设置 watermark 的最大延迟时间这个参数,如果设置的过大,可能会造成 内存的压力。可以设置最大延迟时间小一些,然后把迟到元素发送到侧输出流中去。 晚一点更新结果。或者使用类似于 RocksDB 这样的状态后端, RocksDB 会开辟 堆外存储空间,但 IO 速度会变慢,需要权衡。
三,还有就是滑动窗口的长度如果过长,而滑动距离很短的话,Flink 的性能 会下降的很厉害。我们主要通过时间分片的方法,将每个元素只存入一个“重叠窗 口”,这样就可以减少窗口处理中状态的写入。
四,状态后端使用 RocksDB,还没有碰到被撑爆的问题。
解答:主要考虑的是 flink 的低延迟、高吞吐量和对流式数据应用场景更好的支 持;另外,flink 可以很好地处理乱序数据,而且可以保证 exactly-once 的状态一致性。
解答:可以是内存,文件系统,或者 RocksDB。
解答:端到端的 exactly-once 对 sink 要求比较高,具体实现主要有幂等写入和 事务性写入两种方式。幂等写入的场景依赖于业务逻辑,更常见的是用事务性写入。 而事务性写入又有预写日志(WAL)和两阶段提交(2PC)两种方式。 如果外部系统不支持事务,那么可以用预写日志的方式,把结果数据先当成状 态保存,然后在收到 checkpoint 完成的通知时,一次性写入 sink 系统
解答:Flink 内置的很多算子,包括源 source,数据存储 sink 都是有状态的。在 Flink 中,状态始终与特定算子相关联。Flink 会以 checkpoint 的形式对各个任务的 状态进行快照,用于保证故障恢复时的状态一致性。Flink 通过状态后端来管理状态 和 checkpoint 的存储,状态后端可以有不同的配置选择。
解答:使用类似于 scala 的 set 数据结构或者 redis 的 set 显然是不行的, 因为可能有上亿个 Key,内存放不下。所以可以考虑使用布隆过滤器(Bloom Filter) 来去重。
解答:spark streaming 的 checkpoint 仅仅是针对 driver 的故障恢复做了数据 和元数据的 checkpoint。而 flink 的 checkpoint 机制 要复杂了很多,它采用的是 轻量级的分布式快照,实现了每个算子的快照,及流动中的数据的快照。参
解答:Watermark 本质是 Flink 中衡量 EventTime 进展的一个机制,主要用来处 理乱序数据。
解答:Flink 依靠 checkpoint 机制来实现 exactly-once 语义,如果要实现端到端 的 exactly-once,还需要外部 source 和 sink 满足一定的条件。状态的存储通过状态 后端来管理,Flink 中可以配置不同的状态后端。
解答:在流式处理中,CEP 当然是要支持 EventTime 的,那么相对应的也要 支持数据的迟到现象,也就是 watermark 的处理逻辑。CEP 对未匹配成功的事件序 列的处理,和迟到数据是类似的。在 Flink CEP 的处理逻辑中,状态没有满足的和 迟到的数据,都会存储在一个 Map 数据结构中,也就是说,如果我们限定判断事件 序列的时长为 5 分钟,那么内存中就会存储 5 分钟的数据,这在我看来,也是对内 存的极大损伤之一。
解答:
解答:使用大容量的 Kafka 把数据先放到消息队列里面作为数据源,再使用 Flink 进行消费,不过这样会影响到一点实时性。