pyflinkf初试:table API与 datastream API

tableAPI没啥好说的,就是flinksql
datastreamAPI重点的有几个算子:
map:对数据进行函数操作,map传入一个方法,方法入参为整个数据流,但在方法内部以列表的形式处理
filter:该算子将按照条件对输入数据集进行筛选操作,将符合条件的数据集输出
最重要的就是map,因为map方法能做到一些flinksql做不到的事情:
例如:要判断数据大于100,则则发送消息,很显然,在sql里无法判断也无法发送。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from pyflink.table import TableEnvironment, EnvironmentSettings
from pyflink.table.udf import udf
from pyflink.table import DataTypes
from pyflink.datastream import StreamExecutionEnvironment
from pyflink.table import StreamTableEnvironment
from pyflink.common.typeinfo import Types
from pyflink.datastream.functions import RuntimeContext, MapFunction


def hello_world():
    """
    flink 学习:简单建立source  sink  udf  简单关联,简单输出,简单聚合
    :return:
    :rtype:
    """
    t_env = TableEnvironment.create(EnvironmentSettings.in_streaming_mode())  # 初始化入口类
    t_env.get_config().get_configuration().set_string('parallelism.default','1')  # 设置并发为1
    my_source_ddl = """
    create table source(
    id timestamp(3),
        data INT,
        WATERMARK FOR id AS id
        )with(
            'connector' = 'filesystem',
            'path' = 'file:///Users/duanzebing/Desktop/python_flink.csv',
            'format' = 'csv'
        )
    """
    my_sink_ddl = """
    create table sink(
    id timestamp(3),
    data_sum int
    ) with(
        'connector' = 'print'
        --'format' = 'canal-json',
        --'path' = '{}'
    )
    """
    my_source_ddl_1 = """
        create table source_1(
        id timestamp(3),
        data INT,
        WATERMARK FOR id AS id,
        PRIMARY KEY(id) NOT ENFORCED
        )with(
            'connector' = 'filesystem',
            'path' = 'file:///Users/duanzebing/Desktop/python_flink.csv',
            'format' = 'csv'
        )
        """
    # values.source.timestamp  源系统创建事件的时间戳。对应source.ts_ms 于 Debezium 记录中的字段。
    # https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/connectors/table/formats/debezium/#available-metadata
    """
     create table source_1(
        id int,
        data TINYINT,
        update_time TIMESTAMP(3) METADATA FROM `values.source.timestamp` VIRTUAL,
    WATERMARK FOR update_time AS update_time,
    PRIMARY KEY(currency) NOT ENFORCED
        )with(
            'connector' = 'filesystem',
            'path' = 'file:///Users/duanzebing/Desktop/python_flink.csv',
            'format' = 'csv'
        )
    """
    t_env.execute_sql(my_source_ddl)
    t_env.execute_sql(my_sink_ddl)
    t_env.execute_sql(my_source_ddl_1)
    # t = t_env.execute_sql('select id from source')
    @udf(result_type=DataTypes.INT())
    def add(i):
        return i * 100
    # t_env.execute_sql("create temporary function add as 'add' language python")
    # 上面这个不知道为什么一直报错
    t_env.create_temporary_function('add', add)
    t_env.execute_sql('''
       INSERT INTO sink
            select b.id ,a.data from source a 
           left join source_1 for SYSTEM_TIME as of a.id as b
           on a.id = b.id
       ''').wait()
    """
     INSERT INTO sink
            select a.id ,a.data_sum from (
           SELECT id, add(data) as data_sum from source) a 
           left join source_1 for SYSTEM_TIME as of PROCTIME() as b
           on a.id = b.id
    """
    # t_env.execute_sql('''
    # INSERT INTO sink
    #     SELECT id, sum(data) as data_sum FROM
    #         (SELECT id / 2 as id, data FROM source)
    #     WHERE id > 1
    #     GROUP BY id
    # ''').wait()

def flink_datastream():
	# 创建map方法,用来在map算子中执行数据处理操作
    class MyMapFunction(MapFunction):
        def map(self,value):
            if value[0] == 'Alice':
                value[0] = 'alice'
            if value[1] == 12:
                value[1] = 10
            return value[0],value[1]

    # create environments of both APIs
    # create environments of both APIs
    env = StreamExecutionEnvironment.get_execution_environment()  # datastream API
    t_env = StreamTableEnvironment.create(env)  # table API

    # create a DataStream 创建datastream
    ds = env.from_collection([("Alice", 12), ("Bob", 10), ("Alice", 100)],
                             type_info=Types.ROW_NAMED(
                                 ["a", "b"],
                                 [Types.STRING(), Types.INT()]))
	# 把datastream转成table 
    input_table = t_env.from_data_stream(ds).alias("name", "score")
    # register the Table object as a view and query it
    # the query contains an aggregation that produces updates
    # 创建临时视图
    t_env.create_temporary_view("InputTable", input_table)
    # 在这个视图上进行查询
    res_table = t_env.sql_query("SELECT name, score FROM InputTable ")
    # interpret the updating Table as a changelog DataStream
    # 将更新表解释为changelog数据流
    res_stream = t_env.to_changelog_stream(res_table)
    # add a printing sink and execute in DataStream API
    # map:对数据进行函数操作,map传入一个方法,方法入参为整个数据流,但在方法内部以列表的形式处理
    # map_stream = res_stream.map(MyMapFunction())
    # map_stream = res_stream.map(lambda x:(x[0],x[1]+1))
    # map_stream.print()
    # filter:该算子将按照条件对输入数据集进行筛选操作,将符合条件的数据集输出
    # filter_stream = res_stream.filter(lambda x:x if x[1]<100 else False)
    # filter_stream.print()
    # kyeby:按照传入的数据进行分组
    # keyby_stream = res_stream.key_by(lambda a: a[0])
    # keyby_stream.print()
    # res_stream.print()
    # 执行
    env.execute()


if __name__ == '__main__':
    # hello_world()
    flink_datastream()

2022-09-08更新

首先上代码

from pyflink.common import WatermarkStrategy, Types
from pyflink.datastream import StreamExecutionEnvironment, RuntimeExecutionMode, FlatMapFunction, RuntimeContext
from pyflink.datastream.connectors import FileSource, StreamFormat
from pyflink.datastream.state import ValueStateDescriptor


# DataStream API 应用程序首先需要声明一个执行环境(StreamExecutionEnvironment),这是流式程序执行的上下文。
# 你将通过它来设置作业的属性(例如默认并发度、重启策略等)、创建源、并最终触发作业的执行。


def hello_world():
    env = StreamExecutionEnvironment.get_execution_environment()
    env.set_runtime_mode(RuntimeExecutionMode.BATCH)  # 处理批数据时使用,即将最终的结果进行输出,而不是获取一条记录后就输出一条结果
    env.set_parallelism(1)  # 设置并行度
    ds = env.from_source(source=FileSource.for_record_stream_format(StreamFormat.text_line_format(),
                                                                 r'flink_test.txt').process_static_file_set().build(),
                         watermark_strategy=WatermarkStrategy.for_monotonous_timestamps(),
                         source_name='source_text'
                         )

    # ds.flat_map(flink_flat_map).print()
    # ds.flat_map(flink_flat_map).key_by(lambda a: a).flat_map(ValueSum()).print()
    ds.flat_map(flink_flat_map) \
        .map(lambda i: (i, 1), output_type=Types.TUPLE([Types.STRING(),Types.INT()])) \
        .key_by(lambda i: i[0]) \
        .reduce(lambda i, j: (i[0],i[1]+j[1])).print()
    env.execute()


def flink_map(row):
    """
    map 算子,和spark  pandas的类似 一进一出
    :param row:
    :return:
    """
    row = row.split()
    if len(row) >= 3:
        return (row[0] + '0', row[1] + '1', row[2] + '2')
    else:
        return (row[0], row[1], 'xxx')


def flink_flat_map(row):
    """
    flat_map 算子,和spark的类似,功能就是一进多出
    :param row:
    :return:
    """
    yield from row.split()


class ValueSum(FlatMapFunction):
    """
    状态(state)类 ,实现的功能很简单,就是一个普通的计数

    """
    def __init__(self):
        self.sum = None

    def open(self, runtime_context: RuntimeContext):
        descriptor = ValueStateDescriptor('this_sum', Types.INT())
        self.sum = runtime_context.get_state(descriptor)

    def flat_map(self, value):
        current_sum = self.sum.value()
        if current_sum is None:
            current_sum = 0
        current_sum += 1
        # print(self.sum)

        self.sum.update(current_sum)
        yield value, current_sum


if __name__ == '__main__':
    hello_world()

需要注意的地方有三个
第一:注意普通的flat_map算子和状态state的flat_map写法上的区别,state的需要继承FlatMapFunction,并且先再open方法中注册状态
第二: env.set_runtime_mode(RuntimeExecutionMode.BATCH) 这个语句的使用,再批处理时尤其好用,下面是实测区别
数据源为:
apple flink flink
hello es flink
study flink

使用 env.set_runtime_mode(RuntimeExecutionMode.BATCH) 输出为:

(es,1)
(apple,1)
(flink,4)
(hello,1)
(study,1)

不使用输出为:

(apple,1)
(flink,1)
(flink,2)
(hello,1)
(es,1)
(flink,3)
(study,1)
(flink,4)

可以看出来,BATCH确实是吧数据作为一批来使用

第三:reduce的使用

  • keyBy算子之后的reduce,其实计算的是历史以来所有数据的和,每过来一条数据,就输出一次结果。类似与python自带的reduce
from functools import reduce

以本次示例来说:
ds.flat_map(flink_flat_map)
.map(lambda i: (i, 1), output_type=Types.TUPLE([Types.STRING(),Types.INT()]))
.key_by(lambda i: i[0])
.reduce(lambda i, j: (i[0],i[1]+j[1])).print()
第一个flat_map 把从文件读取到的一行按照空格输出,即一个单词为一条,然后的map把一个字符串的单词转成一个元组,元组第二个值始终为1以便后续计算,然后key_by 以元组的第一个元素即单词为key,然后reduce输出key和 上一次的value和本次的value的和,来达到计数的效果

  • window算子之后的reduce,其实计算的是window窗口内的数据和,每次窗口触发的时候,才会输出一次结果。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值