Flink性能调优

Flink性能调优

1、资源配置调优

​ Flink性能调优的第一步,就是为任务分配合适的资源,在一定范围内,增加资源的分配与性能的提升是成正比的,实现了最优的资源配置后,在此基础上再考虑进行后面论述的性能调优策略。

     提交方式主要是yarn-per-job,资源的分配在使用脚本提交Flink任务时进行指定。标准的Flink任务提交脚本(Generic CLI 模式)从1.11开始,增加了通用客户端模式,参数使用-D <property=value>指定。
bin/flink run
-t yarn-per-job
-d
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=1024mb \ 指定JM的总进程大小
-Dtaskmanager.memory.process.size=1024mb \ 指定每个TM的总进程大小
-Dtaskmanager.numberOfTaskSlots=2 \ 指定每个TM的slot数
-c com.atguigu.app.dwd.LogBaseApp
/opt/module/gmall-flink/gmall-realtime-1.0-SNAPSHOT-jar-with-dependencies.jar

1.1 内存设置

bin/flink run
-t yarn-per-job
-d
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=2048mb \ JM2~4G足够
-Dtaskmanager.memory.process.size=6144mb \ 单个TM2~8G足够
-Dtaskmanager.numberOfTaskSlots=2 \ 与容器核数1core:1 slot或1core:2 slot
-c com.atguigu.app.dwd.LogBaseApp
/opt/module/gmall-flink/gmall-realtime-1.0-SNAPSHOT-jar-with-dependencies.jar

​ Flink是实时流处理,关键在于资源情况能不能抗住高峰时期每秒的数据量,通常用QPS/TPS来描述数据情况。

1.2 并行度设置

1.2.1 最优并行度计算

开发完成后,先进行压测。任务并行度给10以下,测试单个并行度的处理上限。

然后 总QPS/单并行度的处理能力 = 并行度

不能只从QPS去得出并行度,因为有些字段少、逻辑简单的任务,单并行度一秒处理几万条数据。而有些数据字段多,处理逻辑复杂,单并行度一秒只能处理1000条数据。

最好根据高峰期的QPS压测,并行度*1.2倍,富余一些资源。

1.2.2 source端并行度的配置

数据源端是 Kafka,Source的并行度设置为Kafka对应Topic的分区数。如果已经等于 Kafka 的分区数,消费速度仍跟不上数据生产速度,考虑下Kafka 要扩大分区,同时调大并行度等于分区数。Flink 的一个并行度可以处理一至多个分区的数据,如果并行度多于 Kafka 的分区数,那么就会造成有的并行度空闲,浪费资源。

1.2.3 Transform端并行度的配置

Keyby之前的算子

一般不会做太重的操作,都是比如map、filter、flatmap等处理较快的算子,并行度可以和source保持一致。

Keyby之后的算子

如果并发较大,建议设置并行度为 2 的整数次幂,例如:128、256、512;

小并发任务的并行度不一定需要设置成 2 的整数次幂;

大并发任务如果没有 KeyBy,并行度也无需设置为 2 的整数次幂;

1.2.4 Sink端并行度的配置

Sink 端是数据流向下游的地方,可以根据 Sink 端的数据量及下游的服务抗压能力进行评估。如果Sink端是Kafka,可以设为Kafka对应Topic的分区数。

Sink 端的数据量小,比较常见的就是监控告警的场景,并行度可以设置的小一些。

Source 端的数据量是最小的,拿到 Source 端流过来的数据后做了细粒度的拆分,数据量不断的增加,到 Sink 端的数据量就非常大。那么在 Sink 到下游的存储中间件的时候就需要提高并行度。

另外 Sink 端要与下游的服务进行交互,并行度还得根据下游的服务抗压能力来设置,如果在 Flink Sink 这端的数据量过大的话,且 Sink 处并行度也设置的很大,但下游的服务完全撑不住这么大的并发写入,可能会造成下游服务直接被写挂,所以最终还是要在 Sink 处的并行度做一定的权衡。

1.3 RocksDB大状态调优

RocksDB 是基于 LSM Tree 实现的(类似HBase),写数据都是先缓存到内存中,所以RocksDB 的写请求效率比较高。RocksDB 使用内存结合磁盘的方式来存储数据,每次获取数据时,先从内存中 blockcache 中查找,如果内存中没有再去磁盘中查询。优化后差不多单并行度 TPS 5000 record/s,性能瓶颈主要在于 RocksDB 对磁盘的读请求,所以当处理性能不够时,仅需要横向扩展并行度即可提高整个Job 的吞吐量。以下几个调优参数:

设置本地RocksDB多目录
在flink-conf.yaml中配置:

state.backend.rocksdb.localdir: /data1/flink/rocksdb,/data2/flink/rocksdb,/data3/flink/rocksdb

注意:不要配置单块磁盘的多个目录,务必将目录配置到多块不同的磁盘上,让多块磁盘来分担压力。当设置多个 RocksDB 本地磁盘目录时,Flink 会随机选择要使用的目录,所以就可能存在三个并行度共用同一目录的情况。如果服务器磁盘数较多,一般不会出现该情况,但是如果任务重启后吞吐量较低,可以检查是否发生了多个并行度共用同一块磁盘的情况。

当一个 TaskManager 包含 3 个 slot 时,那么单个服务器上的三个并行度都对磁盘造成频繁读写,从而导致三个并行度的之间相互争抢同一个磁盘 io,这样务必导致三个并行度的吞吐量都会下降。设置多目录实现三个并行度使用不同的硬盘从而减少资源竞争。

如下所示是测试过程中磁盘的 IO 使用率,可以看出三个大状态算子的并行度分别对应了三块磁盘,这三块磁盘的 IO 平均使用率都保持在 45% 左右,IO 最高使用率几乎都是 100%,而其他磁盘的 IO 平均使用率相对低很多。由此可见使用 RocksDB 做为状态后端且有大状态的频繁读取时, 对磁盘IO性能消耗确实比较大。

如下图所示,其中两个并行度共用了 sdb 磁盘,一个并行度使用 sdj磁盘。可以看到 sdb 磁盘的 IO 使用率已经达到了 91.6%,就会导致 sdb 磁盘对应的两个并行度吞吐量大大降低,从而使得整个 Flink 任务吞吐量降低。如果每个服务器上有一两块 SSD,强烈建议将 RocksDB 的本地磁盘目录配置到 SSD 的目录下,从 HDD 改为 SSD 对于性能的提升可能比配置 10 个优化参数更有效。

state.backend.incremental**:开启增量检查点,默认false,改为true。
state.backend.rocksdb.predefined-options:SPINNING_DISK_OPTIMIZED_HIGH_MEM设置为机械硬盘+内存模式,有条件上SSD,指定为FLASH_SSD_OPTIMIZED
state.backend.rocksdb.block.cache-size: 整个 RocksDB 共享一个 block cache,读数据时内存的 cache 大小,该参数越大读数据时缓存命中率越高,默认大小为 8 MB,建议设置到 64 ~ 256 MB。
state.backend.rocksdb.thread.num: 用于后台 flush 和合并 sst 文件的线程数,默认为 1,建议调大,机械硬盘用户可以改为 4 等更大的值。
state.backend.rocksdb.writebuffer.size: RocksDB 中,每个 State 使用一个 Column Family,每个 Column Family 使用独占的 write buffer,建议调大,例如:32M
state.backend.rocksdb.writebuffer.count: 每个 Column Family 对应的 writebuffer 数目,默认值是 2,对于机械磁盘来说,如果内存⾜够大,可以调大到 5 左右
state.backend.rocksdb.writebuffer.number-to-merge: 将数据从 writebuffer 中 flush 到磁盘时,需要合并的 writebuffer 数量,默认值为 1,可以调成3。.
state.backend.local-recovery: 设置本地恢复,当 Flink 任务失败时,可以基于本地的状态信息进行恢复任务,可能不需要从 hdfs 拉取数据

1.4 Checkpoint设置

一般我们的 Checkpoint 时间间隔可以设置为分钟级别,例如 1 分钟、3 分钟,对于状态很大的任务每次 Checkpoint 访问 HDFS 比较耗时,可以设置为 5~10 分钟一次Checkpoint,并且调大两次 Checkpoint 之间的暂停间隔,例如设置两次Checkpoint 之间至少暂停 4或8 分钟。

如果 Checkpoint 语义配置为 EXACTLY_ONCE,那么在 Checkpoint 过程中还会存在 barrier 对齐的过程,可以通过 Flink Web UI 的 Checkpoint 选项卡来查看 Checkpoint 过程中各阶段的耗时情况,从而确定到底是哪个阶段导致 Checkpoint 时间过长然后针对性的解决问题。

RocksDB相关参数在1.3中已说明,可以在flink-conf.yaml指定,也可以在Job的代码中调用API单独指定,这里不再列出。

// 使⽤ RocksDBStateBackend 做为状态后端,并开启增量 Checkpoint
RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend("hdfs://node01:8020/flink/checkpoints", true);
env.setStateBackend(rocksDBStateBackend);

// 开启Checkpoint,间隔为 3 分钟
env.enableCheckpointing(TimeUnit.MINUTES.toMillis(3));
// 配置 Checkpoint
CheckpointConfig checkpointConf = env.getCheckpointConfig();
checkpointConf.setCheckpointingMode(CheckpointingMode.EXACTLY_ONCE)
// 最小间隔 4分钟
checkpointConf.setMinPauseBetweenCheckpoints(TimeUnit.MINUTES.toMillis(4))
// 超时时间 10分钟
checkpointConf.setCheckpointTimeout(TimeUnit.MINUTES.toMillis(10));
// 保存checkpoint
checkpointConf.enableExternalizedCheckpoints(
CheckpointConfig.ExternalizedCheckpointCleanup.RETAIN_ON_CANCELLATION);

在实际开发中,有各种环境(开发、测试、预发、生产),作业也有很多的配置:算子的并行度配置、Kafka 数据源的配置(broker 地址、topic 名、group.id)、Checkpoint 是否开启、状态后端存储路径、数据库地址、用户名和密码等各种各样的配置,可能每个环境的这些配置对应的值都是不一样的。

如果你是直接在代码⾥⾯写死的配置,每次换个环境去运行测试作业,都要重新去修改代码中的配置,然后编译打包,提交运行,这样就要花费很多时间在这些重复的劳动力上了。在 Flink 中可以通过使用 ParameterTool 类读取配置,它可以读取环境变量、运行参数、配置文件。

ParameterTool 是可序列化的,所以你可以将它当作参数进行传递给算子的自定义函数类。

1.5.1 读取运行参数

我们可以在Flink的提交脚本添加运行参数,格式:

–参数名 参数值
在 Flink 程序中可以直接使用 ParameterTool.fromArgs(args) 获取到所有的参数,也可以通过 parameterTool.get(“username”) 方法获取某个参数对应的值。

举例:通过运行参数指定jobname

bin/flink run
-t yarn-per-job
-d
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=1024mb \ 指定JM的总进程大小
-Dtaskmanager.memory.process.size=1024mb \ 指定每个TM的总进程大小
-Dtaskmanager.numberOfTaskSlots=2 \ 指定每个TM的slot数
-c com.atguigu.app.dwd.LogBaseApp
/opt/module/gmall-flink/gmall-realtime-1.0-SNAPSHOT-jar-with-dependencies.jar
–jobname dwd-LogBaseApp //参数名自己随便起,代码里对应上即可

在代码里获取参数值:

ParameterTool parameterTool = ParameterTool.fromArgs(args);
String myJobname = parameterTool.get("jobname"); //参数名对应
env.execute(myJobname);

1.5.2 读取系统属性

arameterTool 还⽀持通过 ParameterTool.fromSystemProperties() 方法读取系统属性。做个打印:

ParameterTool parameterTool = ParameterTool.fromSystemProperties();
System.out.println(parameterTool.toMap().toString());

1.5.3 读取配置文件

可以使用ParameterTool.fromPropertiesFile("/application.properties")读取 properties 配置文件。可以将所有要配置的地方(比如并行度和一些 Kafka、MySQL 等配置)都写成可配置的,然后其对应的 key 和 value 值都写在配置文件中,最后通过 ParameterTool 去读取配置文件获取对应的值。

1.5.4 注册全局参数

在ExecutionConfig 中可以将 ParameterTool 注册为全作业参数的参数,这样就可以被 JobManager 的web 端以及用户⾃定义函数中以配置值的形式访问。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.getConfig().setGlobalJobParameters(ParameterTool.fromArgs(args));

可以不用将ParameterTool当作参数传递给算子的自定义函数,直接在用户⾃定义的Rich 函数中直接获取到参数值了。

env.addSource(new RichSourceFunction() {
@Override
public void run(SourceContext sourceContext) throws Exception {
while (true) {
ParameterTool parameterTool = (ParameterTool)getRuntimeContext().getExecutionConfig().getGlobalJobParameters();
}
}

@Override
public void cancel() {
}
})

1.6 压测方式

压测的方式很简单,先在kafka中积压数据,之后开启Flink任务,出现反压,就是处理瓶颈。相当于水库先积水,一下子泄洪。数据可以是自己造的模拟数据,也可以是生产中的部分数据。

2、反压处理

反压(BackPressure)通常产生于这样的场景:短时间的负载高峰导致系统接收数据的速率远高于它处理数据的速率。许多日常问题都会导致反压,例如,垃圾回收停顿可能会导致流入的数据快速堆积,或遇到大促、秒杀活动导致流量陡增。反压如果不能得到正确的处理,可能会导致资源耗尽甚至系统崩溃。

反压机制是指系统能够自己检测到被阻塞的 Operator,然后自适应地降低源头或上游数据的发送速率,从而维持整个系统的稳定。Flink 任务一般运行在多个节点上,数据从上游算子发送到下游算子需要网络传输,若系统在反压时想要降低数据源头或上游算子数据的发送速率,那么肯定也需要网络传输。所以下面先来了解一下 Flink 的网络流控(Flink 对网络数据流量的控制)机制。

2.1 反压现象及定位

Flink 的反压太过于天然了,导致无法简单地通过监控 BufferPool 的使用情况来判断反压状态。Flink 通过对运行中的任务进行采样来确定其反压,如果一个 Task 因为反压导致处理速度降低了,那么它肯定会卡在向 LocalBufferPool 申请内存块上。那么该 Task 的 stack trace 应该是这样:

java.lang.Object.wait(Native Method)
o.a.f.[...].LocalBufferPool.requestBuffer(LocalBufferPool.java:163) o.a.f.[...].LocalBufferPool.requestBufferBlocking(LocalBufferPool.java:133) [...]
1
2
监控对正常的任务运行有一定影响,因此只有当 Web 页面切换到 Job 的 BackPressure 页面时,JobManager 才会对该 Job 触发反压监控。默认情况下,JobManager 会触发 100 次 stack trace 采样,每次间隔 50ms 来确定反压。Web 界面看到的比率表示在内部方法调用中有多少 stack trace 被卡在LocalBufferPool.requestBufferBlocking(),例如: 0.01 表示在 100 个采样中只有 1 个被卡在LocalBufferPool.requestBufferBlocking()。采样得到的比例与反压状态的对应关系如下:

OK: 0 <= 比例 <= 0.10
LOW: 0.10 < 比例 <= 0.5
HIGH: 0.5 < 比例 <= 1
Task 的状态为 OK 表示没有反压,HIGH 表示这个 Task 被反压。

flink反压现象模拟与分析

systemctl stop ntp
ntpdate ntp.myhuaweicloud.com
systemctl start ntp
systemctl status ntp
date


image-20220215182236586

在 Flink Web UI 中有 BackPressure 的页面,通过该页面可以查看任务中 subtask 的反压状态,如下两图所示,分别展示了状态是 OK 和 HIGH 的场景。

排查的时候,先把operator chain禁用,方便定位。

利用Metrics定位反压位置
当某个 Task 吞吐量下降时,基于 Credit 的反压机制,上游不会给该 Task 发送数据,所以该 Task 不会频繁卡在向 Buffer Pool 去申请 Buffer。反压监控实现原理就是监控 Task 是否卡在申请 buffer 这一步,所以遇到瓶颈的 Task 对应的反压⻚⾯必然会显示 OK,即表示没有受到反压。

如果该 Task 吞吐量下降,造成该Task 上游的 Task 出现反压时,必然会存在:该 Task 对应的 InputChannel 变满,已经申请不到可用的Buffer 空间。如果该 Task 的 InputChannel 还能申请到可用 Buffer,那么上游就可以给该 Task 发送数据,上游 Task 也就不会被反压了,所以说遇到瓶颈且导致上游 Task 受到反压的 Task 对应的InputChannel 必然是满的(这⾥不考虑⽹络遇到瓶颈的情况)。从这个思路出发,可以对该 Task 的 InputChannel 的使用情况进行监控,如果 InputChannel 使用率 100%,那么该 Task 就是我们要找的反压源。Flink 1.9 及以上版本inPoolUsage 表示 inputFloatingBuffersUsage 和inputExclusiveBuffersUsage 的总和。

反压时,可以看到遇到瓶颈的该Task的inPoolUage为1。

2.2 反压的原因及处理

先检查基本原因,然后再深入研究更复杂的原因,最后找出导致瓶颈的原因。下面列出从最基本到比较复杂的一些反压潜在原因。

注意:反压可能是暂时的,可能是由于负载高峰、CheckPoint 或作业重启引起的数据积压而导致反压。如果反压是暂时的,应该忽略它。另外,请记住,断断续续的反压会影响我们分析和解决问题。

2.2.1 系统资源

检查涉及服务器基本资源的使用情况,如CPU、网络或磁盘I/O,目前 Flink 任务使用最主要的还是内存和 CPU 资源,本地磁盘、依赖的外部存储资源以及网卡资源一般都不会是瓶颈。如果某些资源被充分利用或大量使用,可以借助分析工具,分析性能瓶颈(JVM Profiler+ FlameGraph生成火焰图)。

针对特定的资源调优Flink
通过增加并行度或增加集群中的服务器数量来横向扩展
减少瓶颈算子上游的并行度,从而减少瓶颈算子接收的数据量(不建议,可能造成整个Job数据延迟增大)

2.2.2 垃圾回收(GC)

长时间GC暂停会导致性能问题。可以通过打印调试GC日志(通过-XX:+PrintGCDetails)或使用某些内存或 GC 分析器(GCViewer工具)来验证是否处于这种情况。

在Flink提交脚本中,设置JVM参数,打印GC日志:

bin/flink run
-t yarn-per-job
-d
-p 5 \ 指定并行度
-Dyarn.application.queue=test \ 指定yarn队列
-Djobmanager.memory.process.size=1024mb \ 指定JM的总进程大小
-Dtaskmanager.memory.process.size=1024mb \ 指定每个TM的总进程大小
-Dtaskmanager.numberOfTaskSlots=2 \ 指定每个TM的slot数
-Denv.java.opts="-XX:+PrintGCDetails -XX:+PrintGCDateStamps"
-c com.atguigu.app.dwd.LogBaseApp
/opt/module/gmall-flink/gmall-realtime-1.0-SNAPSHOT-jar-with-dependencies.jar

下载GC日志的方式:
因为是on yarn模式,运行的节点一个一个找比较麻烦。可以打开WebUI,选择JobManager或者TaskManager,点击Stdout,即可看到GC日志,点击下载按钮即可将GC日志通过HTTP的方式下载下来。

分析GC日志:
通过 GC 日志分析出单个 Flink Taskmanager 堆总大小、年轻代、老年代分配的内存空间、Full GC 后老年代剩余大小等,相关指标定义可以去 Github 具体查看。

扩展:最重要的指标是Full GC后,老年代剩余大小这个指标,按照《Java性能优化权威指南》这本Java堆大小计算法则,设Full GC后老年代剩余大小空间为M,那么堆的大小建议3 ~ 4倍M,新生代为1 ~ 1.5倍M,老年代应为2 ~ 3倍M。

2.2.4 线程竞争

与上⾯的 CPU/线程瓶颈问题类似,subtask 可能会因为共享资源上高负载线程的竞争而成为瓶颈。同样,可以考虑使用2.2.1提到的分析工具,考虑在用户代码中查找同步开销、锁竞争,尽管避免在用户代码中添加同步。

2.4.5 负载不平衡

如果瓶颈是由数据倾斜引起的,可以尝试通过将数据分区的 key 进行加盐或通过实现本地预聚合来减轻数据倾斜的影响。(关于数据倾斜的详细解决方案,会在下一章节详细讨论)

2.4.6 外部依赖

如果发现我们的 Source 端数据读取性能比较低或者 Sink 端写入性能较差,需要检查第三方组件是否遇到瓶颈。例如,Kafka 集群是否需要扩容,Kafka 连接器是否并行度较低,HBase 的 rowkey 是否遇到热点问题。关于第三方组件的性能问题,需要结合具体的组件来分析。

3、数据倾斜

3.1 判断是否存在数据倾斜

相同 Task 的多个 Subtask 中,个别Subtask 接收到的数据量明显大于其他 Subtask 接收到的数据量,通过 Flink Web UI 可以精确地看到每个 Subtask 处理了多少数据,即可判断出 Flink 任务是否存在数据倾斜。通常,数据倾斜也会引起反压。

3.2 数据倾斜的解决

3.2.1 keyBy后的聚合操作存在数据倾斜

使用LocalKeyBy的思想:在 keyBy 上游算子数据发送之前,首先在上游算子的本地对数据进行聚合后再发送到下游,使下游接收到的数据量大大减少,从而使得 keyBy 之后的聚合操作不再是任务的瓶颈。类似MapReduce 中 Combiner 的思想,但是这要求聚合操作必须是多条数据或者一批数据才能聚合,单条数据没有办法通过聚合来减少数据量。从Flink LocalKeyBy 实现原理来讲,必然会存在一个积攒批次的过程,在上游算子中必须攒够一定的数据量,对这些数据聚合后再发送到下游。

注意:Flink是实时流处理,如果keyby之后的聚合操作存在数据倾斜,且没有开窗口的情况下,简单的认为使用两阶段聚合,是不能解决问题的。因为这个时候Flink是来一条处理一条,且向下游发送一条结果,对于原来keyby的维度(第二阶段聚合)来讲,数据量并没有减少,且结果重复计算(非FlinkSQL,未使用回撤流),如下图所示:

实现方式:以计算PV为例,keyby之前,使用flatMap实现LocalKeyby
class LocalKeyByFlatMap extends RichFlatMapFunction<String, Tuple2<String,
//Checkpoint 时为了保证 Exactly Once,将 buffer 中的数据保存到该 ListState 中
private ListState<Tuple2<String, Long>> localPvStatListState;

//本地 buffer,存放 local 端缓存的 app 的 pv 信息
private HashMap<String, Long> localPvStat;

//缓存的数据量大小,即:缓存多少数据再向下游发送
private int batchSize;

//计数器,获取当前批次接收的数据量
private AtomicInteger currentSize;

//构造器,批次大小传参
LocalKeyByFlatMap(int batchSize){
this.batchSize = batchSize;
}

@Override
public void flatMap(String in, Collector collector) throws Exception {
// 将新来的数据添加到 buffer 中
Long pv = localPvStat.getOrDefault(in, 0L);
localPvStat.put(in, pv + 1);
// 如果到达设定的批次,则将 buffer 中的数据发送到下游
if(currentSize.incrementAndGet() >= batchSize){
// 遍历 Buffer 中数据,发送到下游
for(Map.Entry<String, Long> appIdPv: localPvStat.entrySet()) {
collector.collect(Tuple2.of(appIdPv.getKey(), appIdPv.getValue()
}
// Buffer 清空,计数器清零
localPvStat.clear();
currentSize.set(0);
}
}

@Override
public void snapshotState(FunctionSnapshotContext functionSnapshotConte
// 将 buffer 中的数据保存到状态中,来保证 Exactly Once
localPvStatListState.clear();
for(Map.Entry<String, Long> appIdPv: localPvStat.entrySet()) {
localPvStatListState.add(Tuple2.of(appIdPv.getKey(), appIdPv.ge
}
}

@Override
public void initializeState(FunctionInitializationContext context) {
// 从状态中恢复 buffer 中的数据
localPvStatListState = context.getOperatorStateStore().getListState
new ListStateDescriptor<>("localPvStat",
TypeInformation.of(new TypeHint<Tuple2<String, Long>>})));
localPvStat = new HashMap();
if(context.isRestored()) {
// 从状态中恢复数据到 localPvStat 中
for(Tuple2<String, Long> appIdPv: localPvStatListState.get()){
long pv = localPvStat.getOrDefault(appIdPv.f0, 0L);
// 如果出现 pv != 0,说明改变了并行度,
// ListState 中的数据会被均匀分发到新的 subtask中
// 所以单个 subtask 恢复的状态中可能包含两个相同的 app 的数据
localPvStat.put(appIdPv.f0, pv + appIdPv.f1);
}
// 从状态恢复时,默认认为 buffer 中数据量达到了 batchSize,需要向下游发
currentSize = new AtomicInteger(batchSize);
} else {
currentSize = new AtomicInteger(0);
}
}
}

3.2.2 keyBy之前发生数据倾斜

如果 keyBy 之前就存在数据倾斜,上游算子的某些实例可能处理的数据较多,某些实例可能处理的数据较少,产生该情况可能是因为数据源的数据本身就不均匀,例如由于某些原因 Kafka 的 topic 中某些 partition 的数据量较大,某些 partition 的数据量较少。对于不存在 keyBy 的 Flink 任务也会出现该情况。

这种情况,需要让 Flink 任务强制进行shuffle。使用shuffle、rebalance 或 rescale算子即可将数据均匀分配,从而解决数据倾斜的问题。

3.2.3 keyBy后的窗口聚合操作存在数据倾斜

因为使用了窗口,变成了有界数据的处理(3.2.1已分析过),窗口默认是触发时才会输出一条结果发往下游,所以可以使用两阶段聚合的方式:

实现思路:

第一阶段聚合:key拼接随机数前缀或后缀,进行keyby、开窗、聚合
注意:聚合完不再是WindowedStream,要获取WindowEnd作为窗口标记作为第二阶段分组依据,避免不同窗口的结果聚合到一起)

第二阶段聚合:去掉随机数前缀或后缀,按照原来的key及windowEnd作keyby、聚合

4、KafkaSource调休

4.1 动态发现分区

当 FlinkKafkaConsumer 初始化时,每个 subtask 会订阅一批 partition,但是当 Flink 任务运行过程中,如果被订阅的 topic 创建了新的 partition,FlinkKafkaConsumer 如何实现动态发现新创建的 partition 并消费呢?

在使用 FlinkKafkaConsumer 时,可以开启 partition 的动态发现。通过 Properties指定参数开启(单位是毫秒):

FlinkKafkaConsumerBase.KEY_PARTITION_DISCOVERY_INTERVAL_MILLIS
1
该参数表示间隔多久检测一次是否有新创建的 partition。默认值是Long的最小值,表示不开启,大于0表示开启。开启时会启动一个线程根据传入的interval定期获取Kafka最新的元数据,新 partition 对应的那一个 subtask 会自动发现并从earliest 位置开始消费,新创建的 partition 对其他 subtask 并不会产生影响。

properties.setProperty(FlinkKafkaConsumerBase.KEY_PARTITION_DISCOVERY_INTERVAL_MILLIS, 30 * 1000 + "");
1

4.2 从kafka数据源生成watermark

Kafka单分区内有序,多分区间无序。在这种情况下,可以使用 Flink 中可识别 Kafka 分区的 watermark 生成机制。使用此特性,将在 Kafka 消费端内部针对每个 Kafka 分区生成 watermark,并且不同分区 watermark 的合并方式与在数据流 shuffle 时的合并方式相同。

在单分区内有序的情况下,使用时间戳单调递增按分区生成的 watermark 将生成完美的全局 watermark。

可以不使用 TimestampAssigner,直接用 Kafka 记录自身的时间戳:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "node01:9092,node01:9092,node03:9092");
properties.setProperty("group.id", "dsjlg");
FlinkKafkaConsumer<String> kafkaSourceFunction = new FlinkKafkaConsumer<>(
                "flinktest",
                new SimpleStringSchema(),
                properties
        );

kafkaSourceFunction.assignTimestampsAndWatermarks(
                WatermarkStrategy
                        .forBoundedOutOfOrderness(Duration.ofMinutes(2))
);

env.addSource(kafkaSourceFunction);

4.3 设置空闲等待

如果数据源中的某一个分区/分片在一段时间内未发送事件数据,则意味着 WatermarkGenerator 也不会获得任何新数据去生成 watermark。我们称这类数据源为空闲输入或空闲源。在这种情况下,当某些其他分区仍然发送事件数据的时候就会出现问题。比如Kafka的Topic中,由于某些原因,造成个别Partition一直没有新的数据。由于下游算子 watermark 的计算方式是取所有不同的上游并行数据源 watermark 的最小值,则其 watermark 将不会发生变化,导致窗口、定时器等不会被触发。

为了解决这个问题,你可以使用 WatermarkStrategy 来检测空闲输入并将其标记为空闲状态。

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "node01:9092,node02:9092,node03:9092");
properties.setProperty("group.id", "gzhdsjlg");


FlinkKafkaConsumer<String> kafkaSourceFunction = new FlinkKafkaConsumer<>(
                "flinktest",
                new SimpleStringSchema(),
                properties
        );

kafkaSourceFunction.assignTimestampsAndWatermarks(
                WatermarkStrategy
                        .forBoundedOutOfOrderness(Duration.ofMinutes(2))
						.withIdleness(Duration.ofMinutes(5))
);

env.addSource(kafkaSourceFunction)

4.4 Kafka的offset消费策略

FlinkKafkaConsumer可以调用以下API,注意与auto.offset.reset区分开:

setStartFromGroupOffsets():默认消费策略,默认读取上次保存的offset信息,如果是应用第一次启动,读取不到上次的offset信息,则会根据这个参数auto.offset.reset的值来进行消费数据。建议使用这个。

setStartFromEarliest():从最早的数据开始进行消费,忽略存储的offset信息

setStartFromLatest():从最新的数据进行消费,忽略存储的offset信息

setStartFromSpecificOffsets(Map):从指定位置进行消费

setStartFromTimestamp(long):从topic中指定的时间点开始消费,指定时间点之前的数据忽略

当checkpoint机制开启的时候,KafkaConsumer会定期把kafka的offset信息还有其他operator的状态信息一块保存起来。当job失败重启的时候,Flink会从最近一次的checkpoint中进行恢复数据,重新从保存的offset消费kafka中的数据(也就是说,上面几种策略,只有第一次启动的时候起作用)。

为了能够使用支持容错的kafka Consumer,需要开启checkpoint

5、FlinkSQL调优

5.1 Group Aggregate优化

5.1.1 开启MiniBatch(提升吞吐)

MiniBatch是微批处理,原理是缓存一定的数据后再触发处理,以减少对State的访问,从而提升吞吐并减少数据的输出量。MiniBatch主要依靠在每个Task上注册的Timer线程来触发微批,需要消耗一定的线程调度性能。

MiniBatch默认关闭,开启方式如下:
// 初始化table environment
TableEnvironment tEnv = ...

// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 开启miniBatch
configuration.setString("table.exec.mini-batch.enabled", "true");
// 批量输出的间隔时间
configuration.setString("table.exec.mini-batch.allow-latency", "5 s");
// 防止OOM设置每个批次最多缓存数据的条数,可以设为2万条
configuration.setString("table.exec.mini-batch.size", "20000");

FlinkSQL参数配置列表:

https://ci.apache.org/projects/flink/flink-docs-release-1.12/dev/table/config.html

适用场景
微批处理通过增加延迟换取高吞吐,如果有超低延迟的要求,不建议开启微批处理。通常对于聚合的场景,微批处理可以显著的提升系统性能,建议开启。

注意事项
1)目前,key-value 配置项仅被 Blink planner 支持。

2)1.12之前的版本有bug,开启miniBatch,不会清理过期状态,也就是说如果设置状态的TTL,无法清理过期状态。1.12版本才修复这个问题。参考ISSUE:https://issues.apache.org/jira/browse/FLINK-17096

5.1.2 开启LocalGlobal(解决常见数据热点问题)

LocalGlobal优化将原先的Aggregate分成Local+Global两阶段聚合,即MapReduce模型中的Combine+Reduce处理模式。第一阶段在上游节点本地攒一批数据进行聚合(localAgg),并输出这次微批的增量值(Accumulator)。第二阶段再将收到的Accumulator合并(Merge),得到最终的结果(GlobalAgg)。

LocalGlobal本质上能够靠LocalAgg的聚合筛除部分倾斜数据,从而降低GlobalAgg的热点,提升性能。结合下图理解LocalGlobal如何解决数据倾斜的问题。

由上图可知:

未开启LocalGlobal优化,由于流中的数据倾斜,Key为红色的聚合算子实例需要处理更多的记录,这就导致了热点问题。
开启LocalGlobal优化后,先进行本地聚合,再进行全局聚合。可大大减少GlobalAgg的热点,提高性能。
LocalGlobal开启方式:

1)LocalGlobal优化需要先开启MiniBatch,依赖于MiniBatch的参数。

2)table.optimizer.agg-phase-strategy: 聚合策略。默认AUTO,支持参数AUTO、TWO_PHASE(使用LocalGlobal两阶段聚合)、ONE_PHASE(仅使用Global一阶段聚合)。

// 初始化table environment
TableEnvironment tEnv = ...

// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 开启miniBatch
configuration.setString("table.exec.mini-batch.enabled", "true");
// 批量输出的间隔时间
configuration.setString("table.exec.mini-batch.allow-latency", "5 s");
// 防止OOM设置每个批次最多缓存数据的条数,可以设为2万条
configuration.setString("table.exec.mini-batch.size", "20000");
// 开启LocalGlobal
configuration.setString("table.optimizer.agg-phase-strategy", "TWO_PHASE");

判断是否生效
观察最终生成的拓扑图的节点名字中是否包含GlobalGroupAggregate或LocalGroupAggregate。

适用场景
LocalGlobal适用于提升如SUM、COUNT、MAX、MIN和AVG等普通聚合的性能,以及解决这些场景下的数据热点问题。

注意事项:
1)需要先开启MiniBatch
2)开启LocalGlobal需要UDAF实现Merge方法。

5.1.3 开启split distinct(解决count distinct热点问题)

LocalGlobal优化针对普通聚合(例如SUM、COUNT、MAX、MIN和AVG)有较好的效果,对于COUNT DISTINCT收效不明显,因为COUNT DISTINCT在Local聚合时,对于DISTINCT KEY的去重率不高,导致在Global节点仍然存在热点。

之前,为了解决COUNT DISTINCT的热点问题,通常需要手动改写为两层聚合(增加按Distinct Key取模的打散层)。

从Flink1.9.0版本开始,提供了COU

NT DISTINCT自动打散功能,不需要手动重写。Split Distinct和LocalGlobal的原理对比参见下图。

举例:统计一天的UV

SELECT day, COUNT(DISTINCT user_id)
FROM T
GROUP BY day
1
2
3
如果手动实现两阶段聚合:

SELECT day, SUM(cnt)
FROM (
SELECT day, COUNT(DISTINCT user_id) as cnt
FROM T
GROUP BY day, MOD(HASH_CODE(user_id), 1024)
)
GROUP BY day
1
2
3
4
5
6
7
第一层聚合: 将Distinct Key打散求COUNT DISTINCT。

第二层聚合: 对打散去重后的数据进行SUM汇总。

Split Distinct开启方式

默认不开启,使用参数显式开启:

table.optimizer.distinct-agg.split.enabled: true,默认false。
table.optimizer.distinct-agg.split.bucket-num: Split Distinct优化在第一层聚合中,被打散的bucket数目。默认1024。
// 初始化table environment
TableEnvironment tEnv = ...

// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 开启Split Distinct
configuration.setString("table.optimizer.distinct-agg.split.enabled", "true");
// 第一层打散的bucket数目
configuration.setString("table.optimizer.distinct-agg.split.bucket-num", "1024");

判断是否生效
观察最终生成的拓扑图的节点名中是否包含Expand节点,或者原来一层的聚合变成了两层的聚合。

适用场景
使用COUNT DISTINCT,但无法满足聚合节点性能要求。

注意事项:
1)目前不能在包含UDAF的Flink SQL中使用Split Distinct优化方法。

2)拆分出来的两个GROUP聚合还可参与LocalGlobal优化。

3)从Flink1.9.0版本开始,提供了COUNT DISTINCT自动打散功能,不需要手动重写(不用像上面的例子去手动实现)。

5.1.4 改写为AGG WITH FILTER语法(提升大量count distinct场景性能)

在某些场景下,可能需要从不同维度来统计UV,如Android中的UV,iPhone中的UV,Web中的UV和总UV,这时,可能会使用如下CASE WHEN语法。

SELECT
day,
COUNT(DISTINCT user_id) AS total_uv,
COUNT(DISTINCT CASE WHEN flag IN ('android', 'iphone') THEN user_id ELSE NULL END) AS app_uv,
COUNT(DISTINCT CASE WHEN flag IN ('wap', 'other') THEN user_id ELSE NULL END) AS web_uv
FROM T
GROUP BY day

在这种情况下,建议使用FILTER语法, 目前的Flink SQL优化器可以识别同一唯一键上的不同FILTER参数。如,在上面的示例中,三个COUNT DISTINCT都作用在user_id列上。此时,经过优化器识别后,Flink可以只使用一个共享状态实例,而不是三个状态实例,可减少状态的大小和对状态的访问。

将上边的CASE WHEN替换成FILTER后,如下所示:

SELECT
day,
COUNT(DISTINCT user_id) AS total_uv,
COUNT(DISTINCT user_id) FILTER (WHERE flag IN ('android', 'iphone')) AS app_uv,
COUNT(DISTINCT user_id) FILTER (WHERE flag IN ('wap', 'other')) AS web_uv
FROM T
GROUP BY day

5.2 TopN优化

当TopN的输入是非更新流(例如Source),TopN只有一种算法AppendRank。当TopN的输入是更新流时(例如经过了AGG/JOIN计算),TopN有2种算法,性能从高到低分别是:UpdateFastRank 和RetractRank。算法名字会显示在拓扑图的节点名字上。

注意:apache社区版的Flink1.12目前还没有UnaryUpdateRank,阿里云实时计算版Flink才有

UpdateFastRank :最优算法
需要具备2个条件:

1)输入流有PK(Primary Key)信息,例如ORDER BY AVG。

2)排序字段的更新是单调的,且单调方向与排序方向相反。例如,ORDER BY COUNT/COUNT_DISTINCT/SUM(正数)DESC。

如果要获取到优化Plan,则您需要在使用ORDER BY SUM DESC时,添加SUM为正数的过滤条件。

AppendFast:结果只追加,不更新
RetractRank:普通算法,性能差
不建议在生产环境使用该算法。请检查输入流是否存在PK信息,如果存在,则可进行UpdateFastRank优化。

5.2.2 无排名优化(解决数据膨胀问题)

TopN语法
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER ([PARTITION BY col1[, col2..]]
ORDER BY col1 [asc|desc][, col2 [asc|desc]...]) AS rownum
FROM table_name)
WHERE rownum <= N [AND conditions]

数据膨胀问题
根据TopN的语法,rownum字段会作为结果表的主键字段之一写入结果表。但是这可能导致数据膨胀的问题。例如,收到一条原排名9的更新数据,更新后排名上升到1,则从1到9的数据排名都发生变化了,需要将这些数据作为更新都写入结果表。这样就产生了数据膨胀,导致结果表因为收到了太多的数据而降低更新速度。

使用方式
TopN的输出结果无需要显示rownum值,仅需在最终前端显式时进行1次排序,极大地减少输入结果表的数据量。只需要在外层查询中将rownum字段裁剪掉即可

// 最外层的字段,不写 rownum
SELECT col1, col2, col3
FROM (
SELECT col1, col2, col3
ROW_NUMBER() OVER ([PARTITION BY col1[, col2..]]
ORDER BY col1 [asc|desc][, col2 [asc|desc]...]) AS rownum
FROM table_name)
WHERE rownum <= N [AND conditions]

在无rownum的场景中,对于结果表主键的定义需要特别小心。如果定义有误,会直接导致TopN结果的不正确。 无rownum场景中,主键应为TopN上游GROUP BY节点的KEY列表。

5.2.3 增加TopN的Cache大小

TopN为了提升性能有一个State Cache层,Cache层能提升对State的访问效率。TopN的Cache命中率的计算公式为。

cache_hit = cache_size*parallelism/top_n/partition_key_num

例如,Top100配置缓存10000条,并发50,当PatitionBy的key维度较大时,例如10万级别时,Cache命中率只有10000*50/100/100000=5%,命中率会很低,导致大量的请求都会击中State(磁盘),性能会大幅下降。因此当PartitionKey维度特别大时,可以适当加大TopN的CacheS ize,相对应的也建议适当加大TopN节点的Heap Memory。

使用方式
// 初始化table environment
TableEnvironment tEnv = ...
// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 默认10000条,调整TopN cahce到20万,那么理论命中率能达200000*50/100/100000 = 100%
configuration.setString("table.exec.topn.cache-size", "200000");

注意:目前源码中标记为实验项,官网中未列出该参数

5.2.4 PartitionBy的字段中要有时间类字段

例如每天的排名,要带上Day字段。否则TopN的结果到最后会由于State ttl有错乱。

5.2.5 优化后的SQL示例

insert
into print_test
SELECT
cate_id,
seller_id,
stat_date,
pay_ord_amt --不输出rownum字段,能减小结果表的输出量(无排名优化)
FROM (
SELECT
*,
ROW_NUMBER () OVER (
PARTITION BY cate_id,
stat_date --注意要有时间字段,否则state过期会导致数据错乱(分区字段优化)
ORDER
BY pay_ord_amt DESC --根据上游sum结果排序。排序字段的更新是单调的,且单调方向与排序方向相反(走最优算法)
) as rownum
FROM (
SELECT
cate_id,
seller_id,
stat_date,
--重点。声明Sum的参数都是正数,所以Sum的结果是单调递增的,因此TopN能使用优化算法,只获取前100个数据(走最优算法)
sum (total_fee) filter (
where
total_fee >= 0
) as pay_ord_amt
FROM
random_test
WHERE
total_fee >= 0
GROUP
BY cate_name,
seller_id,
stat_date
) a
WHERE
rownum <= 100
);

5.3 高效去重方案

由于SQL上没有直接支持去重的语法,还要灵活的保留第一条或保留最后一条。因此我们使用了SQL的ROW_NUMBER OVER WINDOW功能来实现去重语法。去重本质上是一种特殊的TopN。

5.3.1 保留首行的去重策略

保留KEY下第一条出现的数据,之后出现该KEY下的数据会被丢弃掉。因为STATE中只存储了KEY数据,所以性能较优,示例如下:

SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY b ORDER BY proctime) as rowNum
FROM T
)
WHERE rowNum = 1

以上示例是将T表按照b字段进行去重,并按照系统时间保留第一条数据。Proctime在这里是源表T中的一个具有Processing Time属性的字段。如果按照系统时间去重,也可以将Proctime字段简化PROCTIME()函数调用,可以省略Proctime字段的声明。

5.3.2 保留末行的去重策略

保留KEY下最后一条出现的数据。保留末行的去重策略性能略优于LAST_VALUE函数,示例如下:

SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY b, d ORDER BY rowtime DESC) as rowNum
FROM T
)
WHERE rowNum = 1

以上示例是将T表按照b和d字段进行去重,并按照业务时间保留最后一条数据。Rowtime在这里是源表T中的一个具有Event Time属性的字段。

5.4 指定时区

本地时区定义了当前会话时区id。当本地时区的时间戳进行转换时使用。在内部,带有本地时区的时间戳总是以UTC时区表示。但是,当转换为不包含时区的数据类型时(例如TIMESTAMP, TIME或简单的STRING),会话时区在转换期间被使用。为了避免时区错乱的问题,可以参数指定时区。

// 初始化table environment
TableEnvironment tEnv = ...

// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 指定时区
configuration.setString("table.local-time-zone", "Asia/Shanghai");

5.5 设置参数总结

// 初始化table environment
TableEnvironment tEnv = ...

// 获取 tableEnv的配置对象
Configuration configuration = tEnv.getConfig().getConfiguration();

// 设置参数:
// 开启miniBatch
configuration.setString("table.exec.mini-batch.enabled", "true");
// 批量输出的间隔时间
configuration.setString("table.exec.mini-batch.allow-latency", "5 s");
// 防止OOM设置每个批次最多缓存数据的条数,可以设为2万条
configuration.setString("table.exec.mini-batch.size", "20000");
// 开启LocalGlobal
configuration.setString("table.optimizer.agg-phase-strategy", "TWO_PHASE");
// 开启Split Distinct
configuration.setString("table.optimizer.distinct-agg.split.enabled", "true");
// 第一层打散的bucket数目
configuration.setString("table.optimizer.distinct-agg.split.bucket-num", "1024");
// TopN 的缓存条数
configuration.setString("table.exec.topn.cache-size", "200000");
// 指定时区
configuration.setString("table.local-time-zone", "Asia/Shanghai");

tumbling time windows(翻滚时间窗口) -- 不会有窗口重叠,也就是一个元素只能出现在一个窗口中

sliding time windows(滑动时间窗口)--会有窗口重叠,也就是一个元素可以出现在多个窗口中



窗口Flink程序的一般结构如下所示。第一个片段指的是被Keys化流,而第二个片段指的是非被Keys化流。正如人们所看到的,唯一的区别是window(...)针对keyby之后的keyedStream,而windowAll(...)针对非被Key化的数据流。

简单来说,processing time 就是在flink集群上,当前数据被处理的时间;以flink集群当前时间为准。
不过就像上面说的,在分布式和异步的场景下,PT无法保证数据处理的时间跟数据真正发生的时间是一致的,因为MQ可能会乱序到达、重试之后到达;而数据在flink中处理时,并发下,某些线程处理速度的快慢也有可能会导致某些数据后发而先至。


简单来说,event time就是数据在各自的业务服务器上产生的时间,跟flink无关。

不过由于ET数据到达的方式可能会出现乱序,flink在处理数据的时候就需要等待一些时间,确保一些无序事件都被处理掉,也就导致了会出现延迟。

另外,由于ET数据处理和flink中时间无关,所以要指定watermark,即水印,用于表示当前数据处理的进度。

介于PT和ET之间,指数据进入到Flink中的时间。




3. 写出文件的滚动策略
数据写入文件时,查看源码可以知道
滚动策略是这么判断的:
没有处于inProgressPart状态的文件 或者 DefaultRollingPolicy.shouldRollOnEvent成立,即打开的文件大小超过了滚动器中设置的大小
滚动文件时,首先关闭当前处于progress的part文件,然后创建一个新的 assembleNewPartPath,并且partCounter++(计数器)

StreamingFileSink继承自RichSinkFunction,显然之后执行一次,
该方法中注册了一个定时器,定时器的执行时间为currentProcessingTime + bucketCheckInterval
其中bucketCheckInterval为调用StreamingFileSink.forRowFormat()时,默认创建的,其默认值为60000,也就是一分钟

onProcessingTime方法继承自ProcessingTimeCallback,此方法使用调度触发器的时间戳调用。
该方法中设定了60秒的定时器,定时每60秒执行一次该方法
该方法中会调用buckets.onProcessingTime(currentTime)
里面判断是否需要关闭part文件,注意是关闭而不是滚动
判断条件为:part文件不为空 并且 DefaultRollingPolicy.shouldRollOnProcessingTime条件成立
即part文件存在,并且 (当前时间-part的创建时间 >= 滚动时间 或者 当前时间-part的最后修改时间 >= 不活跃时间)

snapshotState和initializeState方法继承自CheckpointedFunction,用来构建快照或者恢复历史状态
其中snapshotState方法会调用buckets.snapshotState()方法,对桶的状态进行快照处理
将所有处理活跃状态的桶全部进行快照处理,做快照时会检查是否需要滚动,滚动条件为:
part文件不为空 并且 DefaultRollingPolicy.shouldRollOnCheckpoint成立,即文件大小超过设定
满足该条件时,就会关闭partFile

notifyCheckpointComplete方法继承自CheckpointListener,用来通知检查点完成
该方法中会调用onSuccessfulCompletionOfCheckpoint方法
会将已经关闭的(其实是处于Pending状态的文件)part文件重命名
https://www.cnblogs.com/night-xing/p/12576919.html



对于TimeWindow(根据时间划分窗口), 可以根据窗口实现原理的不同分成三类:滚动窗口(Tumbling Window)、滑动窗口(Sliding Window)和会话窗口(Session Window)。

滚动窗口(Tumbling Windows)
将数据依据固定的窗口长度对数据进行切片。

特点:时间对齐,窗口长度固定,没有重叠。

滚动窗口分配器将每个元素分配到一个指定窗口大小的窗口中,滚动窗口有一个固定的大小,并且不会出现重叠。
适用场景:适合做BI统计等(做每个时间段的聚合计算)。

滑动窗口(Sliding Windows)
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。

特点:时间对齐,窗口长度固定,有重叠。

滑动窗口分配器将元素分配到固定长度的窗口中,与滚动窗口类似,窗口的大小由窗口大小参数来配置,另一个窗口滑动参数控制滑动窗口开始的频率。因此,滑动窗口如果滑动参数小于窗口大小的话,窗口是可以重叠的,在这种情况下元素会被分配到多个窗口中。
适用场景:对最近一个时间段内的统计(求某接口最近5min的失败率来决定是否要报警)。


会话窗口(Session Windows)
由一系列事件组合一个指定时间长度的timeout间隙组成,类似于web应用的session,也就是一段时间没有接收到新数据就会生成新的窗口。

特点:时间无对齐。

session窗口分配器通过session活动来对元素进行分组,session窗口跟滚动窗口和滑动窗口相比,不会有重叠和固定的开始时间和结束时间的情况,相反,当它在一个固定的时间周期内不再收到元素,即非活动间隔产生,那个这个窗口就会关闭。一个session窗口通过一个session间隔来配置,这个session间隔定义了非活跃周期的长度,当这个非活跃周期产生,那么当前的session将关闭并且后续的元素将被分配到新的session窗口中去。


Window API
TimeWindow
TimeWindow是将指定时间范围内的所有数据组成一个window,一次对一个window里面的所有数据进行计算(就是本文开头说的对一个边界内的数据进行计算)。

我们以 红绿灯路口通过的汽车数量 为例子:

红绿灯路口会有汽车通过,一共会有多少汽车通过,无法计算。因为车流源源不断,计算没有边界。

所以我们统计每15秒钟通过红路灯的汽车数量,如第一个15秒为2辆,第二个15秒为3辆,第三个15秒为1辆 ...

tumbling-time-window (无重叠数据)
我们使用 Linux 中的 nc 命令模拟数据的发送方

1.开启发送端口,端口号为9999
nc -lk 9999

2.发送内容(key 代表不同的路口,value 代表每次通过的车辆)
一次发送一行,发送的时间间隔代表汽车经过的时间间隔
9,3
9,2
9,7
4,9
2,6
1,5
2,3
5,7
5,4


tumbling-time-window (无重叠数据)
我们发送的数据并没有指定时间字段,所以Flink使用的是默认的 Processing Time,也就是Flink系统处理数据时的时间。

sliding-time-window (有重叠数据)

CountWindow
CountWindow根据窗口中相同key元素的数量来触发执行,执行时只计算元素数量达到窗口大小的key对应的结果。

注意:CountWindow的window_size指的是相同Key的元素的个数,不是输入的所有元素的总数。

tumbling-count-window (无重叠数据)


sliding-count-window (有重叠数据)
同样也是窗口长度和滑动窗口的操作:窗口长度是5,滑动长度是3


Window 总结
flink支持两种划分窗口的方式(time和count)

如果根据时间划分窗口,那么它就是一个time-window

如果根据数据划分窗口,那么它就是一个count-window

flink支持窗口的两个重要属性(size和interval)

如果size=interval,那么就会形成tumbling-window(无重叠数据)

如果size>interval,那么就会形成sliding-window(有重叠数据)

如果size<interval,那么这种窗口将会丢失数据。比如每5秒钟,统计过去3秒的通过路口汽车的数据,将会漏掉2秒钟的数据。

通过组合可以得出四种基本窗口

time-tumbling-window 无重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5))

time-sliding-window 有重叠数据的时间窗口,设置方式举例:timeWindow(Time.seconds(5), Time.seconds(3))

count-tumbling-window无重叠数据的数量窗口,设置方式举例:countWindow(5)

count-sliding-window 有重叠数据的数量窗口,设置方式举例:countWindow(5,3)

Window Reduce
WindowedStream → DataStream:给window赋一个reduce功能的函数,并返回一个聚合的结果。




Window Apply
apply方法可以进行一些自定义处理,通过匿名内部类的方法来实现。当有一些复杂计算时使用。

用法

实现一个 WindowFunction 类
指定该类的泛型为 [输入数据类型, 输出数据类型, keyBy中使用分组字段的类型, 窗口类型]
示例:使用apply方法来实现单词统计

步骤:

    获取流处理运行环境
    构建socket流数据源,并指定IP地址和端口号
    对接收到的数据转换成单词元组
    使用 keyBy 进行分流(分组)
    使用 timeWinodw 指定窗口的长度(每3秒计算一次)
    实现一个WindowFunction匿名内部类
    apply方法中实现聚合计算
    使用Collector.collect收集数据



Window Fold 
WindowedStream → DataStream:给窗口赋一个fold功能的函数,并返回一个fold后的结果。




Aggregation on Window
WindowedStream → DataStream:对一个window内的所有元素做聚合操作。min和 minBy的区别是min返回的是最小值,而minBy返回的是包含最小值字段的元素(同样的原理适用于 max 和 maxBy)。



EventTime与Window
EventTime的引入
与现实世界中的时间是不一致的,在flink中被划分为事件时间,提取时间,处理时间三种。
如果以EventTime为基准来定义时间窗口那将形成EventTimeWindow,要求消息本身就应该携带EventTime
如果以IngesingtTime为基准来定义时间窗口那将形成IngestingTimeWindow,以source的systemTime为准。
如果以ProcessingTime基准来定义时间窗口那将形成ProcessingTimeWindow,以operator的systemTime为准。
在Flink的流式处理中,绝大部分的业务都会使用eventTime,一般只在eventTime无法使用时,才会被迫使用ProcessingTime或者IngestionTime。

如果要使用EventTime,那么需要引入EventTime的时间属性,引入方式如下所示:

val env = StreamExecutionEnvironment.getExecutionEnvironment

// 从调用时刻开始给env创建的每一个stream追加时间特征
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

Watermark
引入
我们知道,流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,虽然大部分情况下,流到 operator 的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络、背压等原因,导致乱序的产生,所谓乱序,就是指 Flink 接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的,所以 Flink 最初设计的时候,就考虑到了网络延迟,网络乱序等问题,所以提出了一个抽象概念:水印(WaterMark);



如上图所示,就出现一个问题,一旦出现乱序,如果只根据 EventTime 决定 Window 的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,此时必须要有个机制来保证一个特定的时间后,必须触发 Window 去进行计算了,这个特别的机制,就是 Watermark。

Watermark 是用于处理乱序事件的,而正确的处理乱序事件,通常用 Watermark 机制结合 Window 来实现。

数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据,都已经到达了,因此,Window 的执行也是由 Watermark 触发的。

Watermark 可以理解成一个延迟触发机制,我们可以设置 Watermark 的延时时长 t,每次系统会校验已经到达的数据中最大的 maxEventTime,然后认定 EventTime 小于 maxEventTime - t 的所有数据都已经到达,如果有窗口的停止时间等于 maxEventTime – t,那么这个窗口被触发执行。

有序流的Watermarker如下图所示:(Watermark设置为0)


当 Flink 接收到每一条数据时,都会产生一条 Watermark,这条 Watermark 就等于当前所有到达数据中的 maxEventTime - 延迟时长,也就是说,Watermark 是由数据携带的,一旦数据携带的 Watermark 比当前未触发的窗口的停止时间要晚,那么就会触发相应窗口的执行。由于 Watermark 是由数据携带的,因此,如果运行过程中无法获取新的数据,那么没有被触发的窗口将永远都不被触发。



eT
v = maxT-t


eT<v


有序流:wm=0

有序流的Watermarker如下图所示:(Watermark设置为0)
乱序流的Watermarker如下图所示:(Watermark设置为2)









Flink对于迟到数据的处理
waterMark和Window机制解决了流式数据的乱序问题,对于因为延迟而顺序有误的数据,可以根据eventTime进行业务处理,于延迟的数据Flink也有自己的解决办法,主要的办法是给定一个允许延迟的时间,在该时间范围内仍可以接受处理延迟数据。

设置允许延迟的时间是通过 allowedLateness(lateness: Time) 设置

保存延迟数据则是通过 sideOutputLateData(outputTag: OutputTag[T]) 保存

获取延迟数据是通过 DataStream.getSideOutput(tag: OutputTag[X]) 获取

具体的用法如下:

allowedLateness(lateness: Time)
def allowedLateness(lateness: Time): WindowedStream[T, K, W] = {
  javaStream.allowedLateness(lateness)
  this
}

该方法传入一个Time值,设置允许数据迟到的时间,这个时间和 WaterMark 中的时间概念不同。再来回顾一下:

WaterMark=数据的事件时间-允许乱序时间值

随着新数据的到来,waterMark的值会更新为最新数据事件时间-允许乱序时间值,但是如果这时候来了一条历史数据,waterMark值则不会更新。总的来说,waterMark是为了能接收到尽可能多的乱序数据。

那这里的Time值,主要是为了等待迟到的数据,在一定时间范围内,如果属于该窗口的数据到来,仍会进行计算,后面会对计算方式仔细说明

注意:该方法只针对于基于event-time的窗口,如果是基于processing-time,并且指定了非零的time值则会抛出异常。

sideOutputLateData(outputTag: OutputTag[T])
def sideOutputLateData(outputTag: OutputTag[T]): WindowedStream[T, K, W] = {
  javaStream.sideOutputLateData(outputTag)
  this
}

该方法是将迟来的数据保存至给定的outputTag参数,而OutputTag则是用来标记延迟数据的一个对象。

DataStream.getSideOutput(tag: OutputTag[X])
通过window等操作返回的DataStream调用该方法,传入标记延迟数据的对象来获取延迟的数据。

对延迟数据的理解
延迟数据是指:

在当前窗口【假设窗口范围为10-15】已经计算之后,又来了一个属于该窗口的数据【假设事件时间为13】,这时候仍会触发 Window 操作,这种数据就称为延迟数据。

那么问题来了,延迟时间怎么计算呢?

假设窗口范围为10-15,延迟时间为2s,则只要 WaterMark<15+2,并且属于该窗口,就能触发 Window 操作。而如果来了一条数据使得 WaterMark>=15+2,10-15这个窗口就不能再触发 Window 操作,即使新来的数据的 Event Time 属于这个窗口时间内 。

Flink 关联 Hive 分区表
Flink 1.12 支持了 Hive 最新的分区作为时态表的功能,可以通过 SQL 的方式直接关联 Hive 分区表的最新分区,并且会自动监听最新的 Hive 分区,当监控到新的分区后,会自动地做维表数据的全量替换。通过这种方式,用户无需编写 DataStream 程序即可完成 Kafka 流实时关联最新的 Hive 分区实现数据打宽。

具体用法:

在 Sql Client 中注册 HiveCatalog:




eT
v = maxT-t


eT<v


有序流:wm=0

有序流的Watermarker如下图所示:(Watermark设置为0)
乱序流的Watermarker如下图所示:(Watermark设置为2)

WaterMark=数据的事件时间-允许乱序时间值

waterMark和Window机制解决了流式数据的乱序问题

假设窗口范围为10-15,延迟时间为2s,则只要 WaterMark<15+2,并且属于该窗口,就能触发 Window 操作。而如果来了一条数据使得 WaterMark>=15+2,10-15这个窗口就不能再触发 Window 操作,即使新来的数据的 Event Time 属于这个窗口时间内 。

延迟数据是指:

在当前窗口【假设窗口范围为10-15】已经计算之后,又来了一个属于该窗口的数据【假设事件时间为13】,这时候仍会触发 Window 操作,这种数据就称为延迟数据。


dataStream.keyBy(0).window(TumblingEventTimeWindows.of(Time.seconds(5))); // Last 5 seconds of data


data.keyBy(1)
    .timeWindow(Time.minutes(1)) //tumbling time window 每分钟统计一次数量和
    .sum(1);

data.keyBy(1)
    .timeWindow(Time.minutes(1), Time.seconds(30)) //sliding time window 每隔 30s 统计过去一分钟的数量和
    .sum(1);





https://www.cnblogs.com/itlz/p/14327179.html




ParameterTool提供了fromPropertiesFile、fromArgs、fromSystemProperties、fromMap静态方法用于创建ParameterTool
ParameterTool提供了get、getRequired、getInt、getLong、getFloat、getDouble、getBoolean、getShort、getByte等方法,每种类型的get均提供了一个支持defaultValue的方法
ParameterTool继承了ExecutionConfig.GlobalJobParameters,其toMap方法返回的是data属性;使用env.getConfig().setGlobalJobParameters可以将ParameterTool的访问范围设置为global

作者:go4it
链接:https://www.jianshu.com/p/3dcbd1b241a1
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



表的记录数:hbase   org.apache.hadoop.hbase.mapreduce.RowCounter  '表名'

表大小:hdfs dfs -du -h /opt/hbase/data/default/ 


我们可以看到这个时候窗口会触发计算,那么我们这个时候应该可以发现一个问题,就是当某个分区的触发机制达到的时候,其他的分区触发机制迟迟未触发的时候,我们的窗口不能被触发计算。这样的话,假如数据倾斜比较严重,某个分区数据量很大,或者说一直都有数据,其他的分区迟迟没有更新watermark,那么这个时候时候就会出现问题,窗口无法被触发计算,极端情况下,等一辈子都不会触发计算。那么这个时候问题就可以依托withIdleness来进行解决。
  那么这个时候我们已经探索出来withidleness的秘密了。其实就是当某个分区的窗口触发条件达到,并且其他的分区没有数据的情况下持续我们约定好的空闲时间,那么窗口会触发计算。如果一直有数据但是无法达到触发条件的话,窗口并不会触发计算。

那么这个值一般设置为多少好呢,一般我认为根据业务场景来看吧,如果你的数据量小,并且倾斜比较严重,那么就设置的小一些,那么如果你的数据量比较大,并且很难出现一个分区或者多个分区 迟迟无数据的情况,那么可以设置的大一些。

一般我认为设置1-10分钟比较好。

  
在KafkaSource中,已经做了很好得优化,在生产中我们一般设置并行度与topic分区数相同

如果设置得并行度比topic分区数多,那必然有并行度消费不到数据,就会导致WaterMark一直保持在Long.min_value.

当这种WaterMark向下游广播之后,会导致所有正常并行度的窗口全部无法关闭,因为WaterMark取了各个并行度的最小值

但是当这种状态保持一段时间之后,程序会在计算WaterMark的时候,自动过滤掉迟迟没有数据的并行度传进来的WaterMark,这就是KafkaSource的优化.




//伴生对象
//Object里的对象全是静态

 //创建集合时不使用new,是因为用了它的伴生对象构建实例
      val array = Array(1,2,3,4)


总结:

如果对一个窗口的数据流执行allowedLateness())方法后,如果由迟到数据

当前watermark > 这个数据所属的窗口的endTime +lateness 那么这条数据被丢弃,不去计算
当前watermark < 这个数据所属的窗口的endTime +lateness 那么重新触发这条数据所属的窗口计算




set 存储单个大文本非结构化数据
hset 则存储结构化数据
此处选择的是
jedis.hset(“train”,x(0),x(1))
传入参数:key,文件,值
Long hset(String key, String field, String value)

1、状态机制 2、精确一次语义 3、高吞吐量 4、可弹性伸缩的应用 5、容错机制







对于是否是普通表,Flink使用is_generic属性进行标识。默认情况下,创建的表是普通表,即is_generic=true,如果要创建Hive兼容表,需要在建表属性中指定is_generic=false。

尖叫提示:

由于依赖Hive Metastore,所以必须开启Hive MetaStore服务


在FlinkSQL Cli中使用Hive Catalog很简单,只需要配置一下sql-cli-defaults.yaml文件即可。配置内容如下:

catalogs:
   - name: myhive
     type: hive
     default-database: default
     hive-conf-dir: /opt/modules/apache-hive-2.3.4-bin/conf
————————————————

https://blog.csdn.net/jmx_bigdata/article/details/111505282?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0.essearch_pc_relevant&spm=1001.2101.3001.4242.1


一旦切换到了hive dialect,就只能使用Hive的语法建表,如果尝试使用Flink的语法建表,则会报错


// 使用hive dialect
tableEnv.getConfig().setSqlDialect(SqlDialect.HIVE);
// 使用 default dialect
tableEnv.getConfig().setSqlDialect(SqlDialect.DEFAULT);
————————————————


以下是使用Hive方言的一些注意事项。

Hive dialect只能用于操作Hive表,不能用于普通表。Hive方言应与HiveCatalog一起使用。
虽然所有Hive版本都支持相同的语法,但是是否有特定功能仍然取决于使用的Hive版本。例如,仅在Hive-2.4.0或更高版本中支持更新数据库位置。
Hive和Calcite具有不同的保留关键字。例如,default在Calcite中是保留关键字,在Hive中是非保留关键字。所以,在使用Hive dialect时,必须使用反引号(`)引用此类关键字,才能将其用作标识符。
在Hive中不能查询在Flink中创建的视图。
当然,一旦开启了Hive dialect,我们就可以按照Hive的操作方式在Flink中去处理Hive的数据了,具体的操作与Hive一致,本文不再赘述。

总结
本文主要介绍了Hive Catalog和Hive Dialect。其中Hive Catalog的作用是持久化Flink的元数据信息,Hive Dialect是支持Hive语法的一个配置参数,这两个概念是Flink集成Hive的关键。下一篇分享将介绍如何使用Flink读写Hive。


一是利用了 Hive 的 MetaStore 作为持久化的 Catalog
用户可通过HiveCatalog将不同会话中的 Flink 元数据存储到 Hive Metastore 中。 例如,用户可以使用HiveCatalog将其 Kafka 表或 Elasticsearch 表存储在 Hive Metastore 中,并后续在 SQL 查询中重新使用它们。

二是利用 Flink 来读写 Hive 的表。
HiveCatalog的设计提供了与 Hive 良好的兼容性,用户可以”开箱即用”的访问其已有的 Hive 数仓。 您不需要修改现有的 Hive Metastore,也不需要更改表的数据位置或分区。
————————————————

第一步:配置HADOOP_CLASSPATH,需要在/etc/profile文件中配置如下的环境变量

export HADOOP_CLASSPATH=`hadoop classpath`

第二步:将hive的jar包复制到flink的lib目录下

flink-connector-hive_2.11-1.12.1.jar
hive-exec-2.3.4.jar
flink-sql-connector-hive-2.3.6_2.11-1.12.1.jar
flink-connector-hive_2.11-1.12.1.jar这个包在maven仓库下载:

https://mvnrepository.com/artifact/org.apache.flink/flink-connector-hive_2.11/1.12.1
flink-sql-connector-hive-2.3.6_2.11-1.12.1.jar这个包在maven仓库下载:

https://mvnrepository.com/artifact/org.apache.flink/flink-sql-connector-hive-2.3.6_2.11/1.12.1


Hbase 作为 Hadoop 全家桶中,非常重要的存储组件,适用于海量数据的随机查询,使用是非常广泛的。

实时数仓项目使用 Kafka 作为数仓的基础表,我们也会把 Kafka 的数据往 Hbase 写一份,方便其他场景使用,比如:做维表

Flink Hbase 表默认使用 TableScan 一次性加载全量维表数据来关联,维表数据不能更新,适用场景比较少(由于 TableScan 完成后,算子状态为 Finish,导致任务不能完成 Checkpoint)

Flink Hbase 时态表 Lookup 功能(仅限于关联 Hbase 表主键,不支持非主键),支持缓存和透查外部系统,完美解决默认表维表数据不能更新和不能 完成 Checkpoint 的问题


Flink 关联 Hbase 场景也有关联非主键的场景,刚开始用的时候,为了方便就直接实现一个 UDF,启动的时候加载全量的 Hbase 表数据到内存中(维表数据并不多),根据策略定期去Hbase 重新加载最新的数据。

受 Lookup Source 的启发,想实现一个关联 Hbase 非主键的 UDF,支持缓存和缓存失效透查 Hbase

需求如下:
1、UDF 关联 Hbase 表非主键
2、支持缓存时间和缓存数量的控制
3、关联键缓存有值就从缓存出
4、关联键缓存没有值就从 Hbase 查,结果放到缓存中

基于这些需求,实现了这样的一个 UDTF(由于 Hbase 表非主键可能有重复值,所以使用 Table Function,如果有多条数据,返回多条数据到 SQL 端处理)





开启检查 HDFS 权限
dfs.permissions
设置为true


创建目录

su hdfs -c "hadoop fs -mkdir /spark-log"


分配权限

su hdfs -l -c "hadoop fs -chown -R root:root /spark-log"

查看目录
su hdfs -l -c "hadoop fs -ls /"


说明:

假设用户名为username,需要运行command程序,命令如下:
su username -l -c "comman"


其中“-l”表示以登录方式执行,这就意味着username的环境变量会被初始化,这在很多情况下是必须的。


id:作业编号
queue:作业所处队列名称
stata:当前作业状态
finalstatus:作业最终状态
running container:运行的container数量
allocated cpu vcores:分配的逻辑CPU数
allocate memeory :分配的内存大小
progress: 作业处理的进程,百分比
tracking ui : history ,需要开启history服务才可查询。application master 点击显示作业详细信息,如map以及reduce数



导入数据有两种,一种是通过文件导入,但是并不会真正的分桶 ;一种是通过从其他表插入的方式导入数据,这种方式才能真正的分桶;

(3)建一个普通的student1表

hive> create table student1(st_id int,st_name string,st_sex string,st_age int,

> st_dept string)  row format delimited fields terminated by ',';
(4)导入数据到student1表

hive> load data local inpath '/hive/student.txt' into table student1;
(5)导入数据到分桶的表

方法一:

<pre>//打开强制分桶开关:</pre>

hive (myhive)> set hive.enforce.bucketing=true;

//设置reduces数为-1:

hive (myhive)> set mapreduce.job.reduces=-1;

//通过其他表插入数据

hive (myhive)> insert into table student select id, name from stu ;
(通过这种方法,得到的分桶对应的文件,数据是无序的,也就是 sorted by 或 sort by无效)

如果没有设置 bucketing属性,我们需要自己设置和分桶个数相匹配的reducer个数。

方法二:

//关闭强制分桶开关:

hive (myhive)> set hive.enforce.bucketing=false;

//设置reduces数和分桶数一致:

hive (myhive)> set mapreduce.job.reduces=3;

//通过其他表插入数据,要添加 distribute by 以及 sort by。

hive (myhive)> insert into table student select id, name from stu distribute by st_dept;
注意:hive.enforce.bucketing为true时,reduce要设为-1;

hive.enforce.bucketing为false时,reduce要设为和分桶数一致;

如果bucketing为 true,reduce又设成大于1的输,会执行两个job。

(为什么通过 load data 的方式导入数据到 student表,并不会分桶?

load data只是把文件上传到 表所在的HDFS目录下。并没有做其他操作。)

总结:我们发现其实桶的概念就是MapReduce的分区的概念,两者完全相同。物理上每个桶就是目录里的一个文件,一个作业产生的桶(输出文件)数量和reduce任务个数相同。

而分区表的概念,则是新的概念。分区代表了数据的仓库,也就是文件夹目录。每个文件夹下面可以放不同的数据文件。通过文件夹可以查询里面存放的文件。但文件夹本身和数据的内容毫无关系。

桶则是按照数据内容的某个值进行分桶,把一个大文件散列称为一个个小文件。这些小文件可以单独排序。如果另外一个表也按照同样的规则分成了一个个小文件。

分桶的好处:

1、两个表join的时候,就不必要扫描整个表,只需要匹配相同分桶的数据即可。效率当然大大提升。

2、同样,对数据抽样的时候,也不需要扫描整个文件。只需要对每个分区按照相同规则抽取一部分数据即可。

2 分桶抽样查询
对于非常大的数据集,有时用户需要使用的是一个具有代表性的查询结果而不是全部结果。Hive可以通过对表进行抽样来满足这个需求。

查询表stu_buck中的数据。

hive (myhive)> select * from student tablesample(bucket 1 out of 3 on id);

注:tablesample是抽样语句,语法:TABLESAMPLE(BUCKET x OUT OF y) 。

y必须是table总bucket数的倍数或者因子。hive根据y的大小,决定抽样的比例。例如,table总共分了4份,当y=2时,抽取(4/2=)2个bucket的数据,当y=8时,抽取(4/8=)1/2个bucket的数据。

x表示从哪个bucket开始抽取,如果需要取多个分区,以后的分区号为当前分区号加上y。例如,table总bucket数为4,tablesample(bucket 1 out of 2),表示总共抽取(4/2=)2个bucket的数据,抽取第1(x)个和第3(x+y)个bucket的数据。

注意:x的值必须小于等于y的值,否则

FAILED: SemanticException [Error 10061]: Numerator should not be bigger than denominator in sample clause for table stu_buck

数据块抽样
Hive提供了另外一种按照百分比进行抽样的方式,这种是基于行数的,按照输入路径下的数据块百分比进行的抽样。


hive (myhive)> select * from student tablesample(0.1 percent) ;

提示:这种抽样方式不一定适用于所有的文件格式。另外,这种抽样的最小抽样单元是一个HDFS数据块。因此,如果表的数据大小小于普通的块大小128M的话,那么将会返回所有行

作者:博弈史密斯
链接:https://www.jianshu.com/p/004462037557
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。





我们的业务在飞速发展,说实话公司内外有很多双眼睛盯着这块蛋糕,3年内100倍的增长是保守估计。我自己还好说已经财富自由了,但你呢?部门给了你这么好的平台,不拿出一点不一样的成就,是无法说服大家的。

公司的文化是团队合作,拥抱变化,我觉得你是时候做出一点改变了。公司没有超人,包括我也一样,不要总想着自己是与众不同的,你需要找好自己的位置。只有认清了自己,你才能有相应的举措改善,否则连你自己都觉得没有依着感。来年3月份还会有一次答辩,多思考思考我说的话,你就会获得成长。没有提升,对你、对公司都没有什么好处,这些差距你需要搞得明明白白。

今年就由你勉强承接一下,我希望你能总结经验教训,让自己的感情软着陆,沉下心来做出一些改变,明年的3.75就是你的了。你应该知道,在我的手下培养出来的,没有弱兵。给你3.25,我自己也是感觉脸上无光。我希望你接下来的工作能够证明我的眼光,给我一个交代,也给自己一个交代。




log4j-api, log4j-core


尽快将Apache Log4j 2所有相关应用升级到最新的 log4j-2.15.0-rc2 版本,地址:https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc2

如果无法尽快更新版本,可以通过以下方法紧急缓解:

a、修改jvm参数 -Dlog4j2.formatMsgNoLookups=true
b、修改配置:log4j2.formatMsgNoLookups=True
c、系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS设置为true




jetlinks community ant design 演示地址:http://demo.jetlinks.cn 账号/密码: test/test123456 
http://demo.finebi.com/
https://gitcode.net/mirrors/discoproject/disco
https://www.wdbyte.com/

wiki       https://arthas.aliyun.com/doc
tutorials  https://arthas.aliyun.com/doc/arthas-tutorials.html
version    3.5.4

http://127.0.0.1:8563/

thread | grep pool 命令查看线程池里线程信息


-Xms128m -Xmx1024m -XX:-UseGCOverheadLimit
在启动脚本中添加-XX:-UseGCOverheadLimit命令。这个方法只会把“java.lang.OutOfMemoryError: GC overhead limit exceeded”变成更常见的java.lang.OutOfMemoryError: Java heap space错误。















posted @ 2022-09-13 14:56  三里清风18  阅读(1132)  评论(0)    收藏  举报