FLINK笔记

几个常用的和基本的数据库sql差不多的:SELECT,WHERE,DISTINCT,GROUP BY,UNION,UNION ALL,JOIN,LEFT JOIN,RIGHT JOIN以及FULL JOIN

Window:根据窗口的不同我们大致可以划分为以下几种窗口

Tumble Window:滚动窗口,窗口数据有固定的大小,窗口数据无限叠加

基本语法:

SELECT 
    [gk],
    [TUMBLE_START(timeCol, size)], 
    [TUMBLE_END(timeCol, size)], 
    agg1(col1), 
    ... 
    aggn(colN)
FROM Tab1
GROUP BY [gk], TUMBLE(timeCol, size)

(1)TUMBLE_START表示了窗口开始的时间;

(2)TUMBLE_END表示了窗口结束的时间;

(3)timeCol表示流表中的时间字段;(4)size表示窗口的大小,如秒、分、时、天等

举例:假如我们需要计算每个人每天的订单量,按照user进行聚合分组

SELECT user
       ,TUMBLE_START(rowtime,INTERVAL '1' DAY) AS Wstart
       ,SUM(amount) 
FROM  Orders
GROUP BY user,TUMBLE_START(rowtime,INTERVAL '1' DAY);

Hop Window:滑动窗口,窗口同样有固定的大小,但是与滚动窗口不同的是滑动窗口可以通过控制参数Side来控制滑动窗口的新建频率,因此当slide的值小于窗口的大小size的时候, 会出现窗口重叠的情况

基本语法

SELECT 
    [gk], 
    [HOP_START(timeCol, slide, size)] ,  
    [HOP_END(timeCol, slide, size)],
    agg1(col1), 
    ... 
    aggN(colN) 
FROM Tab1
GROUP BY [gk], HOP(timeCol, slide, size)

(1)HOP_START表示窗口开始的时间;

(2)HOP_END表示窗口结束的时间;

(3)timeCol表示流表中的时间字段;

(4)size表示窗口的大小,如秒、分、时、天等;

(5)slide表示每次滑动的大小

举例:每过一小时计算过去24小时的销量

SELECT product
       ,SUM(amount)
FROM  Orders 
GROUP BY product,HOP(rowtime,INTERVAL '1' HOUR,INTERVAL '1' DAY);

Session Window:会话窗口,会话窗口没有持续大的时间,但是他们的下限由internal不活动的时间定义,如果在定义的时间内没有会话出线,那么关闭窗口

基本语法

SELECT 
    [gk], 
    SESSION_START(timeCol, gap) AS winStart,  
    SESSION_END(timeCol, gap)   AS winEnd,
    agg1(col1),
     ... 
    aggn(colN)
FROM Tab1
GROUP BY [gk], SESSION(timeCol, gap)

(1)SESSION_START表示窗口开始的时间;

(2)SESSION_END表示窗口结束的时间;

(3)timeCol表示流表中的时间字段;

(4)gap表示窗口非活跃周期的时长

举例:计算每个用户访问时间12小时内的订单量

SELECT user
       ,SESSION_START(rowtime,INTERVAL '12' HOUR)   AS Start
       ,SESSION_ROWTIME(rowtime,INTERVAL '12' HOUR) AS End
       ,SUM(amount) 
FROM Orders 
GROUP BY SESSION(rowtime, INTERVAL '12' HOUR),user

1.13之后flink窗口使用windowing TVFs

窗口TVF的返回值是一个新的关系,它包括原逻辑中的所有的列,同时还多出来3列,分别是"window_start","window_end"和"window_time",用着3个列以指示分配的窗口。

滚动窗口的格式

TUMBLE(TABLE data, DESCRIPTOR(timecol), size)
- data 表名
- timecol 时间属性字段
- 时间窗口的大小  INTERVAL '10' MINUTES

测试样例:

Flink SQL> desc user_log;
+-------------+-----------------------------+-------+-----+---------------+----------------------------+
|        name |                        type |  null | key |        extras |                  watermark |
+-------------+-----------------------------+-------+-----+---------------+----------------------------+
|     user_id |                      STRING |  true |     |               |                            |
|     item_id |                      STRING |  true |     |               |                            |
| category_id |                      STRING |  true |     |               |                            |
|    behavior |                      STRING |  true |     |               |                            |
|          ts |      TIMESTAMP(3) *ROWTIME* |  true |     |               | `ts` - INTERVAL '5' SECOND |
|    proctime | TIMESTAMP_LTZ(3) *PROCTIME* | false |     | AS PROCTIME() |                            |
+-------------+-----------------------------+-------+-----+---------------+----------------------------+
6 rows in set
1.窗口函数不允许单独使用,需要和聚合函数搭配使用,且必须按照window_start和window_end进行分区
SELECT window_start, window_end, count(user_id) 
    FROM TABLE(TUMBLE(TABLE user_log, DESCRIPTOR(ts), INTERVAL '1' MINUTES))
    GROUP BY window_start, window_end;

滑动窗口的格式

HOP(TABLE data, DESCRIPTOR(timecol), slide, size [, offset ])
- data 表
- timecol 时间字段
- slide 滑动长度
- size 窗口大小
1.一个滑动窗口,窗口长度是1分钟,滑动大的距离是30s
 SELECT window_start, window_end, count(1)
  FROM TABLE(
    HOP(TABLE user_log, DESCRIPTOR(ts), INTERVAL '30' SECONDS, INTERVAL '1' MINUTES))
  GROUP BY window_start, window_end;

累计窗口

CUMULATE(TABLE data, DESCRIPTOR(timecol), step, size)
- data 表
- timecol 时间字段
- step 步长
- size 窗口大小
1.每隔30秒统计最近1分钟的数据量
SELECT window_start, window_end, count(1)
  FROM TABLE(
    CUMULATE(TABLE user_log, DESCRIPTOR(ts), INTERVAL '30' SECONDS, INTERVAL '1' MINUTES))
  GROUP BY window_start, window_end;
窗口聚合我们可以采用GROUP BY GROUPING SETS使用,使用方法我们可以参考官网同时在hive中我们有记载

时间属性的介绍

处理时间:处理时间是基于本地的机器时间来处理数据,他是最简单的时间概念,但是他提供很大的不确定性.这种方式既不需要从数据中获取时间也不需要生成watermark

创建表的时候用DDL的方式定义,用PROCTIME()的方式就可以定义处理时间,函数PROCTIME()返回的数据类型时TIMESTAMP_LTZ

CREATE TABLE user_actions (
  user_name STRING,
  data STRING,
  user_action_time AS PROCTIME() -- 声明一个额外的列作为处理时间属性
) WITH (
  ...
);

事件时间:事件时间允许程序按照数据中包含的时间来处理,这样可以在有乱序或者晚到的数据的情况下产生一致的处理结果

使用事件时间需要定义watermark

CREATE TABLE user_actions (
  user_name STRING,
  data STRING,
  user_action_time TIMESTAMP(3),
  -- 声明 user_action_time 是事件时间属性,并且用 延迟 5 秒的策略来生成 watermark
  WATERMARK FOR user_action_time AS user_action_time - INTERVAL '5' SECOND
) WITH (
  ...
);

Flink EventTime语义

众所周知,再流计算中时间是一个非常重要的概念,Flink中的语义主要包括以下三种 EventTime,ProcessingTime,IngestionTime

 

Event Time

Event Time是数据流中每一个元素或者是每一个时间发生时自带的时间属性,一般是事件时间。由于时间自发生到传入flink算子之间存在很多个环节,一个较早发生的事件可能会到达比较迟,因此使用事件事件可能会导致事件是乱序的。

使用Event Time最理想的情况事情我们可以一直等待所有的事件全部到达窗口之后在进行计算。假设所有的数据都已经到达窗口,基于Event Time会得到准确一致的结果:无论我们将同一个程序部署在相同还是不同的计算环境总会得到一致的的计算结果。我们不用担心乱序的情况,但这只是理想的情况,现实情况几乎无法实现,因为我们不可能知道等待多长的事件事件才会到达,更加不可能无止境的等下去。再实际应用的环境中,当涉及到Event Time的情况,Flink窗口会将接收到的所有的时间全部缓存下来,直到接收到一个WaterMark,用以确定不会再有更晚的数据会到达了。WaterMark意味着在一个时间窗口下,Flink会等待一个有限的时间,这在一定程度上降低了计算的准确性,而且增加了系统的延迟。在流处理领域,比起其他几种时间语义,使用Event Time的好处是某个事件的时间是确定的,这样能够保证计算结果在一定程度上的可预测性。

一个基于Event Time定义的Flink程序中必须定义Event Time,以及如何生成WaterMark。我们可以使用元素也就是表数据中自带的时间也可以在时间到达Flink之后人为给Event Time赋值

使用Event Time的优点是结果的可预测性,缺点是延迟较高,缓存较大且调试和排错难度比较大。

Processing Time

对某一个算子来说,Processing Time表示算子按照当前机器的系统时钟来定义时间。在Processing Time的时间窗口下,不管事件什么时候发生,只要事件在某一个时间段到达某一个算子都会被归结到该窗口下,不需要WaterMark机制。对于同一个程序,同一个计算环境,由于每一个算子计算都有一定大的耗时,同一个事件的Processing Time,第n个算子和第n+1个算子不同。如果同一个程序在不同的环境下进行,由于软硬件之间的差异,不同环境前序算子处理速度不一样,那么对于下游算子来说,Processing Time也会不一样,这样就会导致不同环境之下,窗口的计算结果不一样。因此,在Processing Time在时间窗口下的计算会有不确定性。

Processing Time只依赖当前执行机器的系统时钟,不需要依赖Watermark,无需缓存。Processing Time是实现起来非常简单也是延迟最小的一种时间语义。

Ingestion Time

Ingestion Time是事件到达Flink Souce的时间。从Source到下游各个算子中间可能有很多计算环节,任何一个算子的处理速度快慢可能影响到下游算子的Processing Time。而Ingestion Time定义的是数据流最早进入Flink的时间,因此不会被算子处理速度影响。

Ingestion Time通常是Event Time和Processing Time之间的一个折中方案。比起Event Time,Ingestion Time可以不需要设置复杂的Watermark,因此也不需要太多缓存,延迟较低。比起Processing Time,Ingestion Time的时间是Souce赋值的,一个事件在整个处理过程从头至尾都使用这个时间,而且后续算子不受前序算子处理速度的影响,计算结果相对准确一些,但计算成本稍高。

设置事件语义

在Flink中,我们在需要在执行环境设置使用哪种时间语义。使用下面的方法设置语义

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)
env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)
注:在flink1.12之后已经不用指定了,系统默认了为eventTime

Event Time和WaterMark

Flink的三种时间语义中,Processing Time和Ingestion Time都可以不用设置Watermark。

如果我们要使用Event Time语义,以下两项配置缺一不可:第一,使用一个时间戳为数据流中每个事件的Event Time赋值;第二,生成Watermark。

实际上,Event Time是每个事件的元数据,Flink并不知道每个事件的发生时间是什么,我们必须要为每个事件的Event Time赋值一个时间戳。关于时间戳,包括Flink在内的绝大多数系统都支持Unix时间戳系统(Unix time或Unix epoch)。Unix时间戳系统以1970-01-01 00:00:00.000 为起始点,其他时间记为距离该起始时间的整数差值,一般是毫秒(millisecond)精度。

有了Event Time时间戳,我们还必须生成Watermark。Watermark是Flink插入到数据流中的一种特殊的数据结构,它包含一个时间戳,并假设后续不会有小于该时间戳的数据。下图展示了一个乱序数据流,其中方框是单个事件,方框中的数字是其对应的Event Time时间戳,圆圈为Watermark,圆圈中的数字为Watermark对应的时间戳。

 

WaterMark的生成有以下的几点需要注意:

1.WaterMark与事件的时间戳之间关系十分的密切。一个时间戳为T的WaterMark假定后续所有到达事件时间戳都必须大于该WaterMark所带的时间戳.

2.假如一个事件违背了上述1的规则,那么这个事件会被看作是一个迟到的数据,如上图所示的19这个事件比WaterMark(20)到的更晚。对于这种情况Flink提供了一些其他的机制去解决。

3.WaterMark事件戳必须单调递增,以保证时间不会倒流。

4.WaterMark机制允许用户来控制准确度和延迟。如果WaterMark设置的与事件时间十分的紧凑,就会产生很多的延迟数据,影响计算结果的准确性,整个应用的延迟很低。如果设置的十分宽松,准确度虽然能够提升但是应用的延迟就会很高,因为Flink需要等很久才能进行计算。

分布式环境下WaterMark的传播

在实际计算过程中,Flink的算子一般分布在多个并行的分区(或者称为实例)上,Flink需要将Watermark在并行环境下向前传播。如下图所示,Flink的每个并行算子子任务会维护针对该子任务的Event Time时钟,这个时钟记录了这个算子子任务Watermark处理进度,随着上游Watermark数据不断向下发送,算子子任务的Event Time时钟也要不断向前更新。由于上游各分区的处理速度不同,到达当前算子的Watermark也会有先后快慢之分,每个算子子任务会维护来自上游不同分区的Watermark信息,这是一个列表,列表内对应上游算子各分区的Watermark时间戳等信息。

 

当上游某分区有Watermark进入该算子子任务后,Flink先判断新流入的Watermark时间戳是否大于Partition Watermark列表内记录的该分区的历史Watermark时间戳,如果新流入的更大,则更新该分区的Watermark。例如,某个分区新流入的Watermark时间戳为4,算子子任务维护的该分区Watermark为1,那么Flink会更新Partition Watermark列表为最新的时间戳4。接着,Flink会遍历Partition Watermark列表中的所有时间戳,选择最小的一个作为该算子子任务的Event Time。同时,Flink会将更新的Event Time作为Watermark发送给下游所有算子子任务。算子子任务Event Time的更新意味着该子任务将时间推进到了这个时间,该时间之前的事件已经被处理并发送到下游。例如,图中第二步和第三步,Partition Watermark列表更新后,导致列表中最小时间戳发生了变化,算子子任务的Event Time时钟也相应进行了更新。整个过程完成了数据流中的Watermark推动算子子任务Watermark的时钟更新过程。Watermark像一个幕后推动者,不断将流处理系统的Event Time向前推进。我们可以将这种机制总结为:

Flink某算子子任务根据各上游流入的Watermark来更新Partition Watermark列表。

选取Partition Watermark列表中最小的时间作为该算子的Event Time,并将这个时间发送给下游算子。

这样的设计机制满足了并行环境下Watermark在各算子中的传播问题,但是假如某个上游分区的Watermark一直不更新,Partition Watermark列表其他地方都在正常更新,唯独个别分区的时间停留在很早的某个时间,这会导致算子的Event Time时钟不更新,相应的时间窗口计算也不会被触发,大量的数据积压在算子内部得不到处理,整个流处理处于空转状态。这种问题可能出现在使用数据流自带的Watermark,自带的Watermark在某些分区下没有及时更新。针对这种问题,一种解决办法是根据机器当前的时钟周期性地生成Watermark。

此外,在union等多数据流处理时,Flink也使用上述Watermark更新机制,那就意味着,多个数据流的时间必须对齐,如果一方的Watermark时间较老,那整个应用的Event Time时钟也会使用这个较老的时间,其他数据流的数据会被积压。一旦发现某个数据流不再生成新的Watermark,我们要在SourceFunction中的SourceContext里调用markAsTemporarilyIdle设置该数据流为空闲状态。

抽取时间戳和生成WaterMark

显然,时间戳和WaterMark只对EventTime时间语义起作用,如果一个程序是基于Processing Time或者是Ingestion Time,那么WaterMark没有什么用处。同时因为时间会在后续的窗口之中用到,因此时间的设置需要在任何的窗口操作之前,总而言之,时间越早越好。Flink提供了以下方法的设置WaterMark

Source

我们可以在Source阶段,通过自定义SourceFunction或RichSourceFunction,在SourceContext里重写void collectWithTimestamp(T element, long timestamp)和void emitWatermark(Watermark mark)两个方法,其中,collectWithTimestamp给数据流中的每个元素T赋值一个timestamp作为Event Time,emitWatermark生成Watermark。下面的代码展示了使用Scala调用这两个方法抽取时间戳并生成Watermark。

case class MyType(data: Double, eventTime: Long, hasWatermark:Boolean, watermarkTime: Long)
class MySource extends RichSourceFunction[MyType] {
    override def run(ctx: SourceContext[MyType]): Unit = {
    while (/* condition */) {
      val next: MyType = getNext()
      ctx.collectWithTimestamp(next, next.eventTimestamp)
      if (next.hasWatermark) {
        ctx.emitWatermark(new Watermark(next.watermarkTime))
      }
    }
    }
}

在Source之后通过TimestampAssigner设置

如果我们不想修改Source,也可以在Source之后,通过时间戳指定器(TimestampAssigner)来设置。TimestampAssigner是一个在DataStream[T]上调用的算子,它会给数据流生成时间戳和Watermark,但不改变数据流的类型T。比如,我们可以在Source之后,先过滤掉不需要的内容,然后设置时间戳和Watermark。下面的代码展示了使用withTimestampAssigner的大致流程。

我们这里紧跟Flink版本的趋势,自Flink1.11之后采用WatermarkStrategy的方法,当创建DataStream对象之后,使用assignTimestampsAndWatermarks(WatermarkStrategy)其中的接口就可以,该接口中存在2个方法:onEvent方法在接收到每一个事件数据的时候都会触发,第一个参数event为接收的事件数据,第二个参数eventTimestamp表示事件时间戳,第三个参数output可用output.emitWatermark方法生成一个watermark。而另外一个方法onPeriodicEmit方法则会周期性的触发,相比较第一种方法每一个事件生成一个Watermark效率要更高一点,接收一个WatermarkOutput类型的参数output,内部可用output.emitWatermark方法生成一个Watermark。

forBoundedOutOfOrderness(固定乱序长度策略)

通过调用WatermarkStrategy对象上的forBoundedOutOfOrderness方法来实现,接收一个Duration类型的参数作为最大乱序(out of order)长度。WatermarkStrategy对象上的withTimestampAssigner方法为从事件数据中提取时间戳提供了接口。

//在assignTimestampsAndWatermarks中用WatermarkStrategy.forBoundedOutOfOrderness方法抽取Timestamp和生成周期性水位线示例

 mySource.assignTimestampsAndWatermarks(
                //指定Watermark生成策略,最大延迟长度5毫秒
                WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofMillis(5))
                        .withTimestampAssigner(
                                //SerializableTimestampAssigner接口中实现了extractTimestamp方法来指定如何从事件数据中抽取时间戳
                                new SerializableTimestampAssigner("流数据的类型"){
                                   @Override
                                   public long extractTimestamp("流数据的类型", long recordTimestamp) {
                                       return "流数据中的时间,具体的数据根据具体的方法,比如如果是Json类型的数据就通过.key的方式获取";
                                   }
                                 })
       );
      
    }
}M

forMonotonousTimestamps(单调递增策略)

通过调用WatermarkStrategy对象上的forMonotonousTimestamps方法来实现,无需任何参数,相当于将forBoundedOutOfOrderness策略的最大乱序长度outOfOrdernessMillis设置为0。

SingleOutputStreamOperator<"流中的数据类型"> streamTS = mySource.assignTimestampsAndWatermarks(
                 WatermarkStrategy.<"流中的数据类型">forMonotonousTimestamps()
                .withTimestampAssigner((event, recordTimestamp) -> event.getDateTime())
);

noWatermarks(不生成策略)

WatermarkStrategy.noWatermarks()

当一个算子需要从上游的多个算子得到结果的时候,会去上游算子中最小的Watermark作为自身的Watermark,并检测是否满足窗口触发的条件。当达不到触发的条件,窗口中就会缓存大量的数据,从而就会导致内存不足的情况出现。

Flink提供了设置流状态为空闲的withIdleness方法。在设置的超时时间内,当某个数据流一直没有事件数据到达,就标记这个流为空闲。下游算子不需要等待这条数据流产生的Watermark,而取其他上游激活状态的Watermark,来决定是否需要触发窗口计算。

WatermarkStrategy.withIdleness(Duration.ofMillis(5))

上面代码设置超时时间5毫秒,超过这个时间,没有生成Watermark,将流状态设置空闲,当下次有新的Watermark生成并发送到下游时,重新设置为活跃。

Flink1.12之后的主要变更

容许在 KeyedStream.intervalJoin() 的配置时间属性,在 Flink 1.12 以前 KeyedStream.intervalJoin() 算子的时间属性依赖于全局设置的时间属性。在 Flink 1.12 中咱们能够在 IntervalJoin 方法后加上 inProcessingTime() 或 inEventTime() ,这样 Join 就再也不依赖于全局的时间属性。

在 Flink 1.12 中将 DataStream API 的 timeWindow() 方法标记为过时,请使用 window(WindowAssigner)、TumblingEventTimeWindows、 SlidingEventTimeWindows、TumblingProcessingTimeWindows 或者 SlidingProcessingTimeWindows。

将 StreamExecutionEnvironment.setStreamTimeCharacteristic() 和 TimeCharacteristic 方法标记为过时。在 Flink 1.12 中,默认的时间属性改变成 EventTime 了,因而你再也不须要该方法去开启 EventTime 了。在 EventTime 时间属性下,你使用 processing-time 的 windows 和 timers 也都依旧会生效。若是你想禁用水印,请使用 ExecutionConfig.setAutoWatermarkInterval(long) 方法。若是你想使用 IngestionTime,请手动设置适当的 WatermarkStrategy。若是你使用的是基于时间属性更改行为的通用 ‘time window’ 算子(eg: KeyedStream.timeWindow()),请使用等效操做明确的指定处理时间和事件时间。

容许在 CEP PatternStream 上显式配置时间属性在 Flink 1.12 以前,CEP 算子里面的时间依赖于全局配置的时间属性,在 1.12 以后能够在 PatternStream 上使用 inProcessingTime() 或 inEventTime() 方法。

Flink程序中配置文件读取的几种方式

main方法传入

示例:flink run -m yarn-cluster -yjm 2048 -ytm 2048 -c 运行主类的绝对路径 执行的jar包 参数1 参数2 ……

上图中的参数1和参数2等其实就是main方法中通过arg()传入的,也就是下面的arg[0]和arg[1],可以直接

  public static void main(String[] args) throws Exception {
        String hostName = args[0];
        int port = Integer.parseInt(args[1]);
 
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataStreamSource =
                env.socketTextStream(hostName, port);
        dataStreamSource.print();
        env.execute("print");
    }

但是这样的方法弊端也是十分明显的,就是一旦少传了一个参数,代码就会报空指针异常,另外就是传入的参数顺序有问题就会导致解析异常的情况出现

ParameterTool,这个方式我们可以直接通过类获取传入的keyvalue

示例:
flink run -m yarn-cluster -yjm 2048 -ytm 2048 -c 运行的主类 执行的jar包 -host-name 参数1 -port 参数2 ……
public static void testFromArgs(String[] args) {
        ParameterTool tool = ParameterTool.fromArgs(args);
        String hostName = tool.get("host.name", "-");
        String port = tool.getRequired("port");
        System.out.println("=================" + hostName 
                + ":" + port + "==================");
    }

同样的ParameterTool还提供了我们读取配置文件的能力,其实底层不是很难的就是通过Fileinputstream读取一个File,然后我们把这个input流load加载到properties之中,最后再转化成ParameterTool,这样的方式下我们只需要更改配置文件然后再重启项目就可以了,这个配置文件注意并不是jar包中的配置文件,二十放在服务器flink目录下conf这个目录中的

示例:

配置文件如下:

max_value=10000
test_value=20000

代码如下:

    public static void testFromProperties() {
        try {
            ParameterTool tool = ParameterTool.fromPropertiesFile("/root/conf/application.properties");
            String maxValue = tool.get("max_value", "-");
            String testValue = tool.getRequired("test_value"); //必填参数
            System.out.println("=================" + maxValue 
                    + ":" + testValue + "==================");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

jar包中的资源文件

这个说的就是程序中resource目录下的配置文件了 ,配置其实和上面的Parameter没什么区别,区别就是放在resource之下,会被打包放进jar包中(如果修改就要重新打包)

import java.io.InputStream;
import java.util.Properties;
 
public class AnotherProperties {
    Properties properties;
    public synchronized Properties getKafkaProperties() {
        if (null != properties) {
            return properties;
        }
        //获取配置文件kafka.properties的内容
        Properties kafkaProperties = new Properties();
        try {
            //InputStream in = ClassLoader.getSystemResourceAsStream("kafka.properties");
            InputStream in = this.getClass().getResourceAsStream("/kafka.properties");
            kafkaProperties.load(in);
            System.out.println("========"+kafkaProperties.getProperty("bootstrap.servers")+"========");
            System.out.println("========"+kafkaProperties.getProperty("ssl.truststore.location")+"========");
        } catch (Exception e) {
            //没加载到文件,程序要考虑退出
            e.printStackTrace();
        }
        properties = kafkaProperties;
        return kafkaProperties;
    }
}
 public static void main(String[] args) throws Exception {
        AnotherProperties anotherProperties = new AnotherProperties();
        Properties kafkaProperties = anotherProperties.getKafkaProperties();
 
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataStreamSource =
                env.socketTextStream("master-1", 9999);
        dataStreamSource.print();
        env.execute("print");
    }

注意:1.当我们使用配置文件加载程序中需要隐藏的数据的时候我们需要注意int类型的值不能被加载,因为它默认key和value是string的数据,我们尝试再程序中使用integer.parse()进行转化,程序依旧报错,我们目前采用的是对于数字类型的,比如端口等信息直接写在程序中 2.我们再编译项目的时候,大仙resources下的配置文件无法被编译,通过查询,发现是pom文件中标签的原因,对于什么时候使用什么样子的打包方式是有区别的

Flink的状态后端

(MemoryStateBackend)基于内存的状态后端

MemoryStateBackend的状态数据全部存储在jvm堆内存中,其中自然也是包括用户使用Datastream API创建的key/value state,窗口创建的状态数据以及触发器等数据。MemoryStateBackend具有非常高效和快速的特点,但是同事存在内存大小方面的限制,一旦状态数据存储过多就会出现内存溢出OOM的情况,更严重如果机器出现问题,那么整个内存中存在的状态数据都会丢失,并且无法恢复状态数据,因此在生产环境中我们不推荐使用MemoryStateBackend这种方式,只适合本地调试。

(FsStateBackend)基于文件系统的状态后端

FsStateBackend是一种基于文件系统的状态后端,这里的文件系统可以是本地的文件系统也可以是分布式的文件系统,比如HDFS,创建FsStateBackend的代码构造如下

FsStateBackend(Path checkpointDataUri, boolean asynchronousSnapshots)

其中如果path为本地路径,格式为“file:///data/flink/checkpoints”,如果是分布式文件系统,格式"hdfs://nameservice/flink/checkpoints",其中第二参数指定是否以异步的方式进行状态记录,默认是采用异步的方式进行同步,因为异步的方式可以尽可能避免影响流计算任务的运行,如果用户像采用同步的方式进行,只需要参数填true即可。

相比较MemoryBackend方式,FsStateBackend更加的适合大状态的情况,例如我们有窗口时间非常长的的窗口计算,或者是key/value state数据非常大的情况,因为这时候系统的内存不支持存储如此大的数据。同事FsStateBackend最大的优点就是文件系统十分的稳定,在进行checkpoint的时候,能够最大程度的保证数据的安全性。

RocksDBStateBackend

与前面的文件系统不同的是,RocksDBStateBackend需要引入单独的依赖。RocksDB是存储key/value格式的内存存储系统,有点类似与HBase,是一种内存和磁盘混合的LSM DB。写数据的时候会先写入write buffer(类似于HBase的memstore),之后再flush到磁盘中,读取数据的时候会出现block cache(类似于HBase的block cache),所以速度会很快。

RocksDBStateBackend在性能上要比FsStateBackend高一些,主要是因为借助于RocksDB存储了最新热数据,然后通过异步的方式再同步到文件系统中,但RocksDBStateBackend和MemoryStateBackend相比性能就会较弱一些。

需要注意 RocksDB 不支持同步的 Checkpoint,构造方法中没有同步快照这个选项。不过 RocksDB 支持增量的 Checkpoint,也是目前唯一增量 Checkpoint 的 Backend,意味着并不需要把所有 sst 文件上传到 Checkpoint 目录,仅需要上传新生成的 sst 文件即可。它的 Checkpoint 存储在外部文件系统(本地或HDFS),其容量限制只要单个 TaskManager 上 State 总量不超过它的内存+磁盘,单 Key最大 2G,总大小不超过配置的文件系统容量即可。对于超大状态的作业,例如天级窗口聚合等场景下可以使会用该状态后端。

配置状态后端

Flink默认使用的状态后端是MemoryStateBackend,所以不需要显示配置。对于其他的状态后端,都需要进行显性配置。在Flink中包含了两种级别的StateBackend配置:一种是在程序中进行配置,该配置只对当前应用有效;另外一种是通过 flink-conf.yaml进行全局配置,一旦配置就会对整个Flink集群上的所有应用有效。

1.应用级别的配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));

如果使用RocksDBStateBackend则需要单独引入rockdb依赖库,如下:

<dependency>
    <groupId>org.apache.flink</groupId>
    <artifactId>flink-statebackend-rocksdb_2.11</artifactId>
    <version>1.10.0</version>
    <scope>provided</scope>
</dependency>

使用方式与FsStateBackend类似,如下:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new RocksDBStateBackend("hdfs://namenode:40010/flink/checkpoints"));

2.集群级别配置

具体的配置项在flink-conf.yaml文件中,如下代码所示,参数state.backend指明StateBackend类型,state.checkpoints.dir配置具体的状态存储路径,代码中使用filesystem作为StateBackend,然后指定相应的HDFS文件路径作为state的checkpoint文件夹。

使用filesystem存储

state.backend: filesystem
checkpoint存储路径
state.checkpoints.dir: hdfs://namenode:40010/flink/checkpoints

如果想用RocksDBStateBackend配置集群级别的状态后端,可以使用下面的配置:

操作RocksDBStateBackend的线程数量,默认值为1
state.backend.rocksdb.checkpoint.transfer.thread.num: 1# 指定RocksDB存储状态数据的本地文件路径
state.backend.rocksdb.localdir: /var/rockdb/checkpoints
用于指定定时器服务的工厂类实现类,默认为“HEAP”,也可以指定为“RocksDB”
state.backend.rocksdb.timer-service.factory: HEAP

flink1.12与flink1.13状态后端和checkpoint之间的差异

Old

New

MemoryStateBackend()

HashMapStateBackend()+JobManagerCheckpointStorage()

FsStateBackend()

HashMapStateBackend()+FileSystemCheckpointStorage()

RocksDBStateBackend(new MemoryStateBackend)

EmbeddedRocksDBStateBackend()+JobManagerCheckpointStorage()

RocksDBStateBackend(new FsStateBackend)

EmbeddedRocksDBStateBackend()+FileSystemCheckpointStorage()

MemoryStateBackend("file://path")

HashMapStateBackend()+JobManagerCheckpointStorage()

MemoryStateBackend

旧版本的MemoryStateBackend等价于使用HashMapStateBackend()和JobManagerCheckpointStroage()

flink-conf.yaml

state.backend:hashmap
state.checkpoint-storage:jobmanager

代码配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new HashMapStateBackend());
env.getCheckpointConfig().setCheckpointStorage(new JobManagerStateBackend());

FsStateBackend

旧版本的FsStateBackend等价于使用HashMapStateBackend()+FileSystemCheckpointStroage()

flink-conf.yaml

state.backend: hashmap
state.checkpoint.dir: file:///checkpoint.dir/(对应什么文件系统填什么文件系统,可以是本地也可以是HDFS)
# 可选,当制定了checkpoint的目录的时候,默认自定使用filesystemCheckpointStorage
state.checkponit-storage: filesystem

代码配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new HashMapStateBackend());
//下面的2行代码实际使用过程中,只需要写一个,如果不指定什么系统存储,其实第4行默认也是使用filesystem
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));

RocksDBStateBackend

旧版本的 RocksDBStateBackend(非内嵌)等价于使用 EmbeddedRocksDBStateBackend 和 FileSystemCheckpointStorage

如果旧版本内嵌了 MemoryStateBackend,即 RocksDBStateBackend(new MemoryStateBackend()),则等价于使用 EmbeddedRocksDBStateBackend 和JobManagerCheckpointStorage

如果旧版本内嵌了 FsStateBackend,即 RocksDBStateBackend(new FsStateBackend()) ,则等价于使用 EmbeddedRocksDBStateBackend 和 FileSystemCheckpointStorage

flink-conf.yaml

state.backend: rockdb
state.checkpoint.dir: file:///checkpoint.dir/
# 可选,当制定了checkpoint的目录的时候,默认自定使用filesystemCheckpointStorage
state.checkpoint-storage: filesystem

代码配置

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("file:///checkpoint-dir");
env.getCheckpointConfig().setCheckpointStorage(new FileSystemCheckpointStorage("file:///checkpoint-dir"));

checkpoint恢复命令

应用定时触发,用于保存状态,会过期

内部应用失败时使用

bin/flink run -m yarn-cluster -yn 2 -yjm 1024 -ytm 1024 -s hdfs://master:9000/flink/checkpoints/467e17d2cc343e6c56255d222bae3421/chk-56/_metadata -c com.Streaming.SocketWindowWordCountCheckPoint flink-1.0-SNAPSHOT-jar-with-dependencies.jar --port 9002

savepoint恢复命令

用户手动执行,是只想checkpoint的指针,不会过期

在升级的情况下使用

注意:为了能够在作业的不同版本之间和Flink的不同版本之间顺利升级,强烈推荐程序员通过uid(string)方法手动给算子赋予ID,这些ID将用于确定每一个算子的状态范围。如果不手动给算子手动指定ID,则会由Flink自动给每个算子生成一个ID,只要这些ID没有改变就能从保存点(savepoint)将程序恢复过来。而这些自动生成的 ID 依赖于程序的结构,并且对代码的更改是很敏感的。因此,强烈建议用户手动的设置 ID。

首生成一个checkpoint,再flink的web页面查看job的ID
之后再终端上面使用bin/flink savepoint [jobId] [存放的快照地址,可以是HDFS]
最后执行恢复命令bin/flink run -n -s /tmp/flink/checkpoints/savepoint-93e589-957052f01c84(执行2的步骤之后日志中会出现)/_metadata -c com.pro.flink.sink.Demo1 /opt/modules/flink-1.0-SNAPSHOT.jar --hostname 192.168.135.237 --port 8888
删除savepoint一定是要删除通过savepoint命令生成的目录(也就是执行2之后出现的)bin/flink savepoint -d /tmp/flink/checkpoints/savepoint-93e589-957052f01c84/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值