当前位置:   article > 正文

Pyflink教程(四):datastream_api_pyflink教程细列

pyflink教程细列

现pyflink环境为1.16 ,下面介绍下常用的datastream算子。

现我整理的都是简单的、常用的,后期会继续补充。

官网:https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/dev/python/datastream/intro_to_datastream_api/

  1. from pyflink.common import Configuration
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. #构建环境,构建环境时,可添加配置参数,也可默认
  4. # https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/dev/python/python_config/
  5. config = Configuration()
  6. config.set_integer("python.fn-execution.bundle.size", 1000)
  7. env = StreamExecutionEnvironment.get_execution_environment(config)
  8. #默认 无参
  9. env = StreamExecutionEnvironment.get_execution_environment()
  10. #添加配置
  11. env.set_parallelism(1) #添加配置:并行度 其他配置可参考官网 https://nightlies.apache.org/flink/flink-docs-release-1.16/docs/dev/datastream/execution/execution_configuration/

数据输入

1.from_collection

从集合Collection中读取数据,用的比较多的。但是一般都是本地测试或者单机运行的时候用。

主要就是两个参数

1:collection(必填) 数组类型-就是传入的数据,数据类型需要对应。

2:type_info(非必填) TypeInformation类型,定义数据的scheam,通过Types.ROW_NAMED定义列名,后面的数组是定义数据类型,三个对应上就行

  1. ds = env.from_collection(
  2. [('a', 'id=1', 1), ('a', 'id=2', 2), ('a', 'id=3', 3), ('b', 'home=1', 1), ('b', 'home=2', 2)],
  3. type_info=Types.ROW_NAMED(["key", "url", "value"], [Types.STRING(), Types.STRING(), Types.INT()]))
2.read_text_file

逐行读取给定的文件并创建一个数据流,该数据流包含一个字符串每一行的内容。

两个参数:

1.file_path :文件路径 类型 'file:///some/local/file' or 'hdfs://host:port/file/path'

2.charset_name:默认utf-8

  1. from pyflink.datastream import StreamExecutionEnvironment
  2. env = StreamExecutionEnvironment.get_execution_environment()
  3. env.set_parallelism(1)
  4. ds1 = env.read_text_file("D:\\tmp\\requirements.txt")
  5. ds1.print()
  6. env.execute()
3.from_source

传递source来获取datastream类型的数据。

主要参数:

  1. source:source数据源

  1. watermark_strategy: Watermark生成策略(也叫水位线策略),有

单调递增策略(for_monotonous_timestamps)、

固定乱序长度策略(for_bounded_out_of_orderness)

等等,只是这两个比较常用。

  1. source_name:名称

  1. type_info:类型

下面就是生成单调递增的1-10的数据

  1. seq_num_source = NumberSequenceSource(1, 10)
  2. ds = env.from_source(
  3. source=seq_num_source,
  4. watermark_strategy=WatermarkStrategy.for_monotonous_timestamps(), #单调递增的策略
  5. source_name='seq_num_source',
  6. type_info=Types.LONG())
  7. ds.print()
  8. ds.execute()

还可读取文件,通过FileSource.for_record_stream_format方法读取文件。input_path为文件路径

  1. ds = env.from_source(
  2. source=FileSource.for_record_stream_format(StreamFormat.text_line_format(),
  3. input_path)
  4. .process_static_file_set().build(),
  5. watermark_strategy=WatermarkStrategy.for_monotonous_timestamps(),
  6. source_name="file_source"
  7. )

简单说下for_monotonous_timestamps和for_bounded_out_of_orderness

for_monotonous_timestamps有序流,主要特点就是时间戳单调递增,所以永不会有迟到数据问题。最简单的场景,直接调用就可以。

for_bounded_out_of_orderness:乱序流,因为乱序流需要等待迟到的数据到来,所以需要设置延迟时间。这时生成水位线的时间戳,就是当前数据流中最大的时间戳去延迟的结果,相当于把表调慢,当前时钟会滞后于数据的最大时间戳。这个方法需要传入一个max_out_of_orderness参数,表示最大乱序成都,它表示数据流中乱序数据时间戳的最大差值;如果我们能确定乱序成都,那么设置对应时间长度的延迟,就可以等到所有的乱序数据了。

4.add_source

自定义添加数据源,一般都是添加kafka。

add_source(self, source_func: SourceFunction, source_name: str = 'Custom Source',  type_info: TypeInformation = None) -> 'DataStream':

用户可以自定义kakfak的FlinkKafkaConsumer和 FlinkKafkaProducer

  1. # 注意:file:///前缀不能省略
  2. env.add_jars("file:///.../flink-sql-connector-kafka_2.11-1.12.0.jar")
  3. deserialization_schema = JsonRowDeserializationSchema.builder() \
  4. .type_info(type_info=Types.ROW([Types.LONG(), Types.LONG()])).build()
  5. kafka_source1 = FlinkKafkaConsumer(
  6. topics='test_source_topic',
  7. deserialization_schema=deserialization_schema,
  8. properties={'bootstrap.servers': 'localhost:9092', 'group.id': 'test_group'})
  9. source_ds = env.add_source(kafka_source1).name('source_kafka')
  1. map_df = source_ds.map(lambda row: json.loads(row))
  2. kafka_producer = FlinkKafkaProducer(
  3. topic='test_source_topic',
  4. serialization_schema=SimpleStringSchema(),
  5. producer_config={'bootstrap.servers': 'localhost:9092'})
  6. map_df.add_sink(kafka_producer).name('sink_kafka')

但是你要用的是1.16版本,那么会提示你FlinkKafkaConsumer不支持了

  1. class KafkaSource(Source):
  2. """
  3. The Source implementation of Kafka. Please use a :class:`KafkaSourceBuilder` to construct a
  4. :class:`KafkaSource`. The following example shows how to create a KafkaSource emitting records
  5. of String type.
  6. ::
  7. >>> source = KafkaSource \\
  8. ... .builder() \\
  9. ... .set_bootstrap_servers('MY_BOOTSTRAP_SERVERS') \\
  10. ... .set_group_id('MY_GROUP') \\
  11. ... .set_topics('TOPIC1', 'TOPIC2') \\
  12. ... .set_value_only_deserializer(SimpleStringSchema()) \\
  13. ... .set_starting_offsets(KafkaOffsetsInitializer.earliest()) \\
  14. ... .build()
  15. .. versionadded:: 1.16.0
  16. """

所以1.16之后 官方建议用KafkaSource和KafkaSink

  1. from pyflink.common import SimpleStringSchema, WatermarkStrategy
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. from pyflink.datastream.connectors import DeliveryGuarantee
  4. from pyflink.datastream.connectors.kafka import KafkaSource, KafkaOffsetsInitializer, KafkaSink, \
  5. KafkaRecordSerializationSchema
  6. env = StreamExecutionEnvironment.get_execution_environment()
  7. env.add_jars("file:///path/to/flink-sql-connector-kafka.jar")
  8. kafka_source = KafkaSource \
  9. .builder() \
  10. .set_bootstrap_servers('localhost:9092') \
  11. .set_group_id('MY_GROUP') \
  12. .set_topics('test_source_topic') \
  13. .set_value_only_deserializer(SimpleStringSchema()) \
  14. .set_starting_offsets(KafkaOffsetsInitializer.earliest()) \
  15. .build()
  16. ds = env.from_source(kafka_source, WatermarkStrategy.no_watermarks(), "kafka source")
  17. ds = ds.map(lambda x: x[0])
  18. env.execute('datastream_api_demo')

但是把这个报错,跟自定义分区一样,不知道啥情况,等以后在研究研究,1.16也可以使用FlinkKafkaConsumer和 FlinkKafkaProducer,通过add_source来使用。

  1. Caused by: org.apache.flink.runtime.JobException: Recovery is suppressed by NoRestartBackoffTimeStrategy
  2. at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.handleFailure(ExecutionFailureHandler.java:139)
  3. at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.getFailureHandlingResult(ExecutionFailureHandler.java:83)
  4. at org.apache.flink.runtime.scheduler.DefaultScheduler.recordTaskFailure(DefaultScheduler.java:256)
  5. at org.apache.flink.runtime.scheduler.DefaultScheduler.handleTaskFailure(DefaultScheduler.java:247)
  6. at org.apache.flink.runtime.scheduler.DefaultScheduler.onTaskFailed(DefaultScheduler.java:240)
  7. at org.apache.flink.runtime.scheduler.SchedulerBase.onTaskExecutionStateUpdate(SchedulerBase.java:738)
  8. at org.apache.flink.runtime.scheduler.SchedulerBase.updateTaskExecutionState(SchedulerBase.java:715)

map(映射)

map是大家非常熟悉的大数据操作算子,主要用于将流中进行转换形成新的数据流,简单来说 就是一一映射,进来什么样出去就什么样。

  1. from pyflink.common import Types
  2. from pyflink.datastream import MapFunction, StreamExecutionEnvironment
  3. ##函数类实现,有点类似java的富含数类。
  4. class MyMapFunction(MapFunction):
  5.     # 这个还可以实现open方法,当作全局调用
  6. def __init__(self, str_p):
  7. self.str_p = str_p
  8. def map(self, value):
  9. dd = value[1].split('|')
  10. return dd[0] + "_" + self.str_p
  11. env = StreamExecutionEnvironment.get_execution_environment()
  12. env.set_parallelism(1)
  13. data_stream = env.from_collection(
  14. collection=[(1, 'aaa|bb'), (2, 'bb|a'), (3, 'aaa|a')],
  15. type_info=Types.ROW([Types.INT(), Types.STRING()]))
  16. # mapped_stream = data_stream.map(MyMapFunction(), output_type=Types.ROW([Types.STRING(), Types.STRING()]))
  17. mapped_stream = data_stream.map(MyMapFunction("pj"), output_type=Types.STRING())
  18. #亦可以使用lambda匿名函数来实现
  19. # maplambda_fun = data_stream.map(lambda x: x * 3)
  20. # mapped_stream.print()
  21. mapped_stream.print()
  22. env.execute("1")
  1. aaa_pj
  2. bb_pj
  3. aaa_pj

filter(过滤)

filter转换操作,顾名思义是对数据流执行一个过滤,通过一个布尔条件表达式设置转换过滤条件,对于一个流内元素进行判断,若为true则输出正常,为false则过滤元素。

filter只负责筛选数据,数据操作不要在filter实现

  1. from pyflink.common import Types
  2. from pyflink.datastream import StreamExecutionEnvironment, FilterFunction
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1)
  5. data_stream = env.from_collection(
  6. collection=[(1, 'aaa|bb'), (2, 'bb|a'), (3, 'aaa|a')],
  7. type_info=Types.ROW([Types.INT(), Types.STRING()]))
  8. class myFilterFunction(FilterFunction):
  9. def __init__(self, condition):
  10. self.condition = condition
  11. def filter(self, value):
  12. if value[1] == self.condition:
  13. return value
  14. result = data_stream.filter(myFilterFunction('aaa|bb'))
  15. result1 = data_stream.filter(lambda x: x[1] == 'aaa|bb')
  16. result.print()
  17. env.execute()
+I[1, aaa|bb]

flatMap(扁平映射)

flatMap操作又称为扁平映射,主要是将数据流中的整体(一般集合类型)拆分成一个一个的个体使用。

map:进去是个体,出来也是个体;进去是集合,出来也是集合。

flatmap:进去是集合,出来是个体,当都是个体时跟map就没差了。

  1. from pyflink.common import Types
  2. from pyflink.datastream import StreamExecutionEnvironment, FlatMapFunction
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1)
  5. data_stream = env.from_collection([(1, 'aaa@bb'), (2, 'bb@a'), (3, 'aaa@a')])
  6. #继承 FlatMapFunction ,这种写法主要作用就是为了实现抽象类,比如open实现全局变量长连接等
  7. class myFlatMapFunciotn(FlatMapFunction):
  8. def flat_map(self, value):
  9. yield value[1].split("@")[0]
  10.      
  11. result = data_stream.flat_map(myFlatMapFunciotn(), result_type=Types.STRING())
  12. # 如果业务处理中只是为了实现一些数据处理任务,可以直接编写函数实现,效果是一样的。
  13. def split(value):
  14. yield value[1].split("@")[0]
  15. result1 = data_stream.flat_map(split)
  16. result1.print()
  17. env.execute("1")
  1. aaa
  2. bb
  3. aaa

聚合算子( Aggregation)

直观上看,基本转换算子确实是在"转换",因为它们都是基于当前数据,去做了处理和输出。而在实际应用中,我们往需要对大量的数据进行统计或整合,从而提炼出更有用的信息。

计算的结果不仅依赖当前数据,还跟之前的数据有关,相当于要把所有的数据聚在一起进行汇总合并,这就是所谓的聚合,也对应这mapreduce中的reduce操作。

1.key_by

key_by是聚合前必须要用到的一个算子。keyby通过指定键,可以将一条流从逻辑上划分成不同的分区(partitions),这里说的分区,其实就是并行处理的子任务,也就对应这人物槽(task slot).

通过分区将数据流到不同的分区中,这样一来所有相同的key的数据都会发往同一个分区,也就是同一个人物槽中进行处理了。

  1. from pyflink.common import Types
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1)
  5. # data_stream = env.from_collection([(1, 'aaa@bb'), (2, 'bb@a'), (3, 'aaa@a')])
  6. ds = env.from_collection(
  7. [('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2)],
  8. type_info=Types.ROW_NAMED(["key", "value"], [Types.STRING(), Types.INT()]))
  9. result = ds.key_by(lambda x: x[0])
  10. result.print(“1”)
  11. env.execute()
  1. +I[a,1]
  2. +I[a,2]
  3. +I[a,3]
  4. +I[b,1]
  5. +I[b,2]
2.简单聚合

有了keyby分区之后我们就可以使用简单的聚合了,其实无论是sum、max、min等操作,直接DataStream 是不能直接使用的,因为必须分区后才能使用,这也是1.16版本后才可以使用的。

  1. from pyflink.common import Types
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1)
  5. env.set_parallelism(2)
  6. ds = env.from_collection(
  7. [('a', 'id=1', 1), ('a', 'id=2', 2), ('a', 'id=3', 3), ('b', 'home=1', 1), ('b', 'home=2', 2)],
  8. type_info=Types.ROW_NAMED(["key", "url", "value"], [Types.STRING(), Types.STRING(), Types.INT()]))
  9. # sum的参数可以是列名,也可以是position
  10. # 该方法通过第一位(也就是key列)分区后,然后根据value列相加分别统计总数
  11. #result = ds.key_by(lambda x: x[0]).sum("value")
  12. result1 = ds.key_by(lambda x: x[0]).max("value")
  13. result2 = ds.key_by(lambda x: x[0]).max_by("value")
  14. result1.print("max:")
  15. result2.print("max_by:")
  16. env.execute()

简单说下max和max_by的区别,它俩都是求指定字段的最小值,但是min只计算指定字段的最小心,其他字段会保留最初第一个数据的值,而min_by则会返回包含字段最小值的整条数据,min一样.

比如下面,观察下结果,max的结果中的url列的值是不变的,因为你只用了value来统计,而max_by是都变化的,用哪个就按照实际业务来把。

  1. max:> +I[a,id=1,1]
  2. max:> +I[a,id=1,2]
  3. max:> +I[a,id=1,3]
  4. max:> +I[b,home=1,1]
  5. max:> +I[b,home=1,2]
  6. max_by:> +I[a,id=1,1]
  7. max_by:> +I[a,id=2,2]
  8. max_by:> +I[a,id=3,3]
  9. max_by:> +I[b,home=1,1]
  10. max_by:> +I[b,home=2,2]
3.归约聚合reduce

如果说简单聚合是对一些特定统计需求的实现,那么reduce算子就是一个一般化的聚合统计操作了。从MapReduce开始,我们就对reduce操作就不陌生了,他可以对已有的数据进行归约处理,把每一个新输入的数据和当前已经归约出来的值,在做一个聚合计算。

  1. import random
  2. from pyflink.common import Types
  3. from pyflink.datastream import StreamExecutionEnvironment, ReduceFunction
  4. env = StreamExecutionEnvironment.get_execution_environment()
  5. env.set_parallelism(1)
  6. ##先生成一些随机数据
  7. name_list = ["wang", "zhao", "yu"]
  8. url_list = ["baidu.com", "taobao.com", "google.com", "yangshi.com"]
  9. #name+url 统计下
  10. result_list = []
  11. for num in range(100):
  12. result_tuple = (random.choice(name_list), random.choice(url_list))
  13. result_list.append(result_tuple)
  14. class MyReduceFunction(ReduceFunction):
  15.     # value1是归约后的结果,value2是新数据
  16. def reduce(self, value1, value2):
  17. return value1[0], value1[1] + value2[1]
  18. ds = env.from_collection(result_list, type_info=Types.ROW_NAMED(["name", "url"], [Types.STRING(), Types.STRING()]))
  19. result = ds \
  20. .map(lambda x: (x.name + "_" + x.url, 1)) \ # 通过map先将数据映射成name_url 1的格式
  21. .key_by(lambda x: x[0]) \ #通过name分区
  22. .reduce(MyReduceFunction()) #事先reducefunction方法
  23. result.print("reduce:")
  24. env.execute()

截取最后的输出,可以看到会累计打印出每个分区的访问累计数。

  1. reduce:> ('wang_google.com', 14)
  2. reduce:> ('wang_yangshi.com', 9)
  3. reduce:> ('wang_taobao.com', 7)
  4. reduce:> ('zhao_google.com', 9)
  5. reduce:> ('yu_baidu.com', 8)
  6. reduce:> ('zhao_taobao.com', 6)
  7. reduce:> ('yu_yangshi.com', 12)
  8. reduce:> ('zhao_yangshi.com', 10)
  9. reduce:> ('zhao_google.com', 10)
  10. reduce:> ('wang_baidu.com', 4)
  11. reduce:> ('yu_yangshi.com', 13)

但是就想要累计数最多的那个分区(也就是name_url),我们可以通过使用max,也可以使用reduce

  1. import random
  2. from pyflink.common import Types
  3. from pyflink.datastream import StreamExecutionEnvironment, ReduceFunction
  4. env = StreamExecutionEnvironment.get_execution_environment()
  5. env.set_parallelism(1)
  6. name_list = ["wang", "zhao", "yu"]
  7. url_list = ["baidu.com", "taobao.com", "google.com", "yangshi.com"]
  8. result_list = []
  9. for num in range(100):
  10. result_tuple = (random.choice(name_list), random.choice(url_list))
  11. result_list.append(result_tuple)
  12. class MyReduceFunction(ReduceFunction):
  13. def reduce(self, value1, value2):
  14. return value1[0], value1[1] + value2[1]
  15. class MyReduceFunction2(ReduceFunction):
  16. def reduce(self, value1, value2):
  17. return value1 if value1[1] > value2[1] else value2
  18. ds = env.from_collection(result_list, type_info=Types.ROW_NAMED(["name", "url"], [Types.STRING(), Types.STRING()]))
  19. result = ds \
  20. .map(lambda x: (x.name + "_" + x.url, 1)) \
  21. .key_by(lambda x: x[0]) \
  22. .reduce(MyReduceFunction()) \
  23. .key_by(lambda x: "key") \ ##我们将所有分区结果都放到一个分区内,当数据量大时不要这样干,数据量小可以
  24. .reduce(MyReduceFunction2()) ##通过第二个reducefunction 获取结果
  25. result.print("reduce:")
  26. env.execute()

截取最后输出结果,通过打印可以看出来,这次只会打印当前累计数最大的组合名称,当有别的组合超过当前最大组合的累计数量才会替换,否则只会打印最大的。

  1. reduce:> ('zhao_baidu.com', 10) # 当没有其他组合的数量超过·zhao_baidu.com·时,一直打印当前
  2. reduce:> ('zhao_baidu.com', 10)
  3. reduce:> ('wang_baidu.com', 11) #当·wang_baidu.com· 最多时就会打印该组合
  4. reduce:> ('wang_baidu.com', 11)
  5. reduce:> ('zhao_baidu.com', 11)
  6. reduce:> ('zhao_baidu.com', 11)
  7. reduce:> ('zhao_baidu.com', 11)
  8. reduce:> ('wang_baidu.com', 12)
  9. reduce:> ('wang_baidu.com', 12)
  10. reduce:> ('wang_baidu.com', 13)
  11. reduce:> ('wang_baidu.com', 13)
  12. reduce:> ('wang_baidu.com', 13)

物理分区(Physical Partitioning)

分区就是将数据进行重新分布,传递到不同的流分区去进行下一步处理。

前面介绍的key_by它就是按照键的哈希值来进行重新分区的操作,只不过这种分区操作只能保证把数据按key来分开,至于能不能分的均匀、每个key去哪个分区,这些都是无法控制的,所以常说key_by是一种逻辑分区操作,是一种"软分区"。

那下面我们介绍下什么是物理分区,也就是"硬分区"。物理分区是真正的分区策略,精准的调配数据,告诉每个数据该去哪个分区。

常见的物理分区策略有随机配( Random)、轮询分配( Round-Robin)、重缩放( Rescale和广播( Broadcast),下边我们分别来做了解。

1.随机分区

随机分区服从均匀分布,所以可以把流中的数据随机打乱,均匀的传递到下游任务分区,因为是随机的,所以就算数据一样,分区可能也可能不会相同。

  1. from pyflink.common import Types
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1) # 这里我们设施并行度是1 ,这里设置多少取决配置,我是本地执行那么就是cpu核数
  5. ds = env.from_collection(
  6. [('a', 'id=1', 1), ('a', 'id=2', 2), ('a', 'id=3', 3), ('b', 'home=1', 1), ('b', 'home=2', 2)],
  7. type_info=Types.ROW_NAMED(["key", "url", "value"], [Types.STRING(), Types.STRING(), Types.INT()]))
  8. ds.shuffle().print("shuffle:").set_parallelism(4) # 经过shuffle后我们设置并行度为4
  9. env.execute()

打印后我们可以看到,其中4条均匀分配到4个分区,多出一条随机到其中一个分区。

  1. shuffle::4> +I[a,id=3,3]
  2. shuffle::3> +I[a,id=2,2]
  3. shuffle::2> +I[a,id=1,1]
  4. shuffle::1> +I[b,home=1,1]
  5. shuffle::2> +I[b,home=2,2]
2.轮询分区(rebalance)

轮询也是一种常见的重分 区方式。简单来说就“发牌”,按照先后顺序将数据做依次分发。其实跟随机分区没啥太大的区别,只不过轮询使用的是Round-Robin负载均衡算法,比random更平均的分配到下游计算任务中。

  1. import random
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. name_list = ["wang", "zhao", "yu"]
  4. url_list = ["baidu.com", "taobao.com", "google.com", "yangshi.com"]
  5. result_list = []
  6. for num in range(100):
  7. result_tuple = (random.choice(name_list), random.choice(url_list))
  8. result_list.append(result_tuple)
  9. env = StreamExecutionEnvironment.get_execution_environment()
  10. env.set_parallelism(1) # 这里我们设施并行度是1 ,这里设置多少取决配置,我是本地执行那么就是cpu核数
  11. ds = env.from_collection(
  12. result_list
  13. )
  14. ds.rebalance().print("rebalance:").set_parallelism(4) # 经过rebalance后我们设置并行度为4
  15. env.execute()
  1. ......
  2. rebalance::3> ('wang', 'google.com')
  3. rebalance::2> ('yu', 'taobao.com')
  4. rebalance::1> ('wang', 'baidu.com')
  5. rebalance::2> ('zhao', 'taobao.com')
  6. rebalance::3> ('wang', 'google.com')
  7. rebalance::3> ('wang', 'google.com')
  8. rebalance::3> ('zhao', 'yangshi.com')
  9. rebalance::4> ('yu', 'baidu.com')
  10. rebalance::3> ('zhao', 'google.com')
3.重缩放分区 (rescale)

重缩放分区和轮询分区的算法都一样,都是使用Round-Robin负载均衡算法,但是跟轮询的差别是重缩放只会将数据发送到下游并行任务的一部分。如果发牌人有多个,那么轮询是每个发牌人都面向所有人发牌,而重缩放是分成小团体,发牌人只给自己的团体内的所有人轮流发牌。

什么时候该用重缩放?

1.当数据接收方的数量是数据发送方数量的整数倍时,rescale的效率就会更高。

2. 轮询分区和重缩放的连接机制是不同的,重缩放主要是针对每一个任务和校友对应的部分任务找时间建立通信,可以节省资源。而轮询因为是面向所有的数据接收方,当taskmanger数量较多时,这种跨节点的网络传输必然影响效率。

  1. import random
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. name_list = ["wang", "zhao", "yu"]
  4. url_list = ["baidu.com", "taobao.com", "google.com", "yangshi.com"]
  5. result_list = []
  6. for num in range(100):
  7. result_tuple = (random.choice(name_list), random.choice(url_list))
  8. result_list.append(result_tuple)
  9. env = StreamExecutionEnvironment.get_execution_environment()
  10. env.set_parallelism(1) # 这里我们设施并行度是1 ,这里设置多少取决配置,我是本地执行那么就是cpu核数
  11. ds = env.from_collection(
  12. result_list
  13. )
  14. ds.rescale().print("rescale:").set_parallelism(4) # 经过rescale后我们设置并行度为4
  15. env.execute()

结果跟轮询和随机都差不多,主要是效率问题。

4.广播(broadcast)

这种其实不应该说是重分区,因为经过广播之后,数据会在不同的分区都保留一份,可能进行重复处理,可以调用broadcast(),将输入数据复制并发送到下游算子所有并行任务中去。

我理解这个应该跟spark的广播变量是一个意思,就是输入数据不用每个分区都发了,将输入数据定义成广播变量,这样就不用每个分区都传递变量。 提高效率。

  1. import random
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. name_list = ["wang", "zhao", "yu"]
  4. url_list = ["baidu.com", "taobao.com", "google.com", "yangshi.com"]
  5. result_list = []
  6. for num in range(100):
  7. result_tuple = (random.choice(name_list), random.choice(url_list))
  8. result_list.append(result_tuple)
  9. env = StreamExecutionEnvironment.get_execution_environment()
  10. env.set_parallelism(1) # 这里我们设施并行度是1 ,这里设置多少取决配置,我是本地执行那么就是cpu核数
  11. ds = env.from_collection(
  12. result_list
  13. )
  14. ds.broadcast().print("broadcast:").set_parallelism(4) # 经过broadcast后我们设置并行度为4
  15. env.execute()
5.全局分区(global)

就是将所有数据传递到一个分区中,换句话说就是强制让分区数=1,这个太极端了,基本不常用。但是数据量少,或者是特殊情况可以用。

6.自定义分区(custom)

上面都满足不了你了,那么咱就自定义把。

通过partition_custom方法来自定义分区。需要传递两个参数;第一个是自定义分区器(partitioner)对象,第二个是应用分区器的字段,它的指定方式与keyby指定key的方式基本一样,可以通过字段名称指定,也可以通过字段位置的索引来指定。

  1. from typing import Any
  2. from pyflink.datastream import StreamExecutionEnvironment, Partitioner, KeySelector
  3. env = StreamExecutionEnvironment.get_execution_environment()
  4. env.set_parallelism(1)
  5. ##将奇偶数分区
  6. ds = env.from_collection(
  7. [1, 2, 3, 4, 5, 6, 7, 8]
  8. )
  9. class myPartitioner(Partitioner):
  10. def partition(self, key: Any, num_partitions: int) -> int:
  11. return key % 2
  12. class myKeySelector(KeySelector):
  13. def get_key(self, value):
  14. return value
  15. ds.partition_custom(myPartitioner(), myKeySelector()).print("custom:").set_parallelism(2)
  16. env.execute()

但是执行有错误。应该是提交作业失败,重新提交,但是我也没试明白。不知道咋回事,等后续在研究把。

  1. Caused by: org.apache.flink.runtime.JobException: Recovery is suppressed by NoRestartBackoffTimeStrategy
  2. at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.handleFailure(ExecutionFailureHandler.java:139)
  3. at org.apache.flink.runtime.executiongraph.failover.flip1.ExecutionFailureHandler.getFailureHandlingResult(ExecutionFailureHandler.java:83)
  4. at org.apache.flink.runtime.scheduler.DefaultScheduler.recordTaskFailure(DefaultScheduler.java:256)
  5. at org.apache.flink.runtime.scheduler.DefaultScheduler.handleTaskFailure(DefaultScheduler.java:247)
  6. at org.apache.flink.runtime.scheduler.DefaultScheduler.onTaskFailed(DefaultScheduler.java:240)
  7. at org.apache.flink.runtime.scheduler.SchedulerBase.onTaskExecutionStateUpdate(SchedulerBase.java:738)
  8. at org.apache.flink.runtime.scheduler.SchedulerBase.updateTaskExecutionState(SchedulerBase.java:715)

数据输出

当数据流经过一系列的转换后,需要将计算结果进行输出,那么负责输出结果的算子称为Sink。

1.sink_to

上面1.16版本kafka输入数据,下面新版本的输出,同样都是有问题的,还是建议用旧版把。有新的研究会补充上。

  1. from pyflink.common import SimpleStringSchema, WatermarkStrategy
  2. from pyflink.datastream import StreamExecutionEnvironment
  3. from pyflink.datastream.connectors import DeliveryGuarantee
  4. from pyflink.datastream.connectors.kafka import KafkaSource, KafkaOffsetsInitializer, KafkaSink, \
  5. KafkaRecordSerializationSchema
  6. sink = KafkaSink.builder() \
  7. .set_bootstrap_servers('localhost:9092') \
  8. .set_record_serializer(
  9. KafkaRecordSerializationSchema.builder()
  10. .set_topic('test_sink_topic')
  11. .set_value_serialization_schema(SimpleStringSchema())
  12. .build()
  13. ) \
  14. .set_delivery_guarantee(DeliveryGuarantee.AT_LEAST_ONCE) \
  15. .build()
  16. ds.sink_to(sink)
2.add_sink

也可以输出到jdbc中

  1. jdbc_options = JdbcConnectionOptions.JdbcConnectionOptionsBuilder() \
  2. .with_user_name("xxxxxx") \
  3. .with_password("xxxxxx") \
  4. .with_driver_name("com.mysql.cj.jdbc.Driver") \
  5. .with_url("jdbc:mysql://localhost:3306/test_db") \
  6. .build()
  7. ds.add_sink(JdbcSink.sink("insert test_table(id, message) VALUES(null, ?)",
  8. type_info=Types.ROW([Types.STRING()]),
  9. jdbc_connection_options=jdbc_options))

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号