Flink基础

flink基础
官网:https://nightlies.apache.org/flink/flink-docs-release-1.17/zh/docs/
问题假设
1)在我们app操作的用户行为日志怎么能够实时处理,以便于进行实时分析,做出反馈?
2)在618或者双11数据激增时候怎么保证数据的高吞吐,低延迟的实时消费?
3)数据消费的时候怎么保证上下游的数据一致性,可靠性,容错性?
4)怎么做好上下游链路数据的监控,以便于出现问题的时候即使反馈或者程序容错?
简介
在数据量激增的时代,各种业务场景都有大量的业务数据产生,对于这些不断产生的数据应该如何进行有效的处理,成为当下大多数公司所面临的问题。目前比较流行的大数据处理引擎 Apache Spark,基本上已经取代了MapReduce 成为当前大数据处理的标准。但对实时数据处理来说,Apache Spark 的 Spark-Streaming 还有性能改进的空间。Spark-Streaming 的流计算本质上还是批(微批)计算,Apache Flink 就是近年来在开源社区不断发展的技术中的能够同时支持高吞吐、低延迟、高性能的纯实时的分布式处理框架。
Flink(Apache Flink)是一个流式处理和批处理的开源分布式计算框架,它被设计用于在大规模数据集上进行高性能、低延迟的数据处理。Flink 提供了丰富的流式处理和批处理功能,可以应用于多种数据处理场景,如实时数据分析、流式ETL(Extract, Transform, Load)、数据流处理、复杂事件处理(CEP)、机器学习等。
与其他流框架对比
计算引擎的发展经历了几个过程,从第 1 代的 MapReduce,到第 2 代基于有向无环图的 Tez,第 3 代基于内存计算的 Spark,再到第 4 代的 Flink,各框架对比如下:
[图片]
1)模型: Storm 和 Flink 是真正的一条一条处理数据;而 Trident(Storm 的封装框架)和 Spark Streaming 其实都是小批处理,一次处理一批数据(小批量)。
2)API : Storm 和 Trident 都使用基础 API 进行开发,操作相对复杂;而 Spark Streaming 和 Flink 中都提供封装后的高阶函数,可以直接拿来使用,这样就比较方便了。
3)保证次数: 在数据处理方面,Storm 可以实现至少处理一次,但不能保证仅处理一次,这样就会导致数据重复处理问题,所以针对计数类的需求,可能会产生一些误差;Trident 通过事务可以保证对数据实现仅一次的处理,Spark Streaming 和 Flink 也是如此。
4)容错机制: Storm和Trident可以通过ACK机制实现数据的容错机制,而Spark Streaming和 Flink 可以通过 CheckPoint 机制实现容错。
5)状态管理: Storm 中没有实现状态管理,Spark Streaming 实现了基于 DStream 的状态管理,而 Trident 和 Flink 实现了基于操作的状态管理。
6)延时: 表示数据处理的延时情况,因为 Storm 和 Flink 接收到一条数据就处理一条数据,其数据处理的延时性是很低的;而 Trident 和 Spark Streaming 都是小型批处理,它们数据处理的延时性相对会偏高。
7)吞吐量: Storm 的吞吐量其实也不低,只是相对于其他几个框架而言较低;Trident 属于中等;而 Spark Streaming 和 Flink 的吞吐量是比较高的。
Flink四大基石
事件时间(Event Time)
事件时间是数据实际发生的时间。在流式数据处理中,数据可能是乱序的,即事件时间的顺序可能与数据到达 Flink 的顺序不一致。事件时间通常由数据中的时间戳字段表示。Flink 使用事件时间来进行窗口操作、处理延迟数据以及处理乱序数据等。
处理时间(Processing Time)
处理时间是 Flink 接收数据的时间。当数据进入 Flink 时,Flink 使用处理时间作为数据的时间戳,因此处理时间是相对于 Flink 所在的机器的本地时间。处理时间操作通常是最简单和最高效的,但在处理延迟或乱序数据时可能导致结果不准确
水位线(Watermark)
水位线是 Flink 中用于处理事件时间乱序性的重要机制。水位线表示事件时间的进度,Flink 根据水位线决定何时触发窗口计算和处理迟到的数据。水位线的正确设置对于保证事件时间处理的准确性和效率至关重要。
窗口操作
Flink 提供了丰富的窗口操作,如滚动窗口、滑动窗口和会话窗口。通过窗口操作,可以对数据流进行分组和分割,使得数据按照时间或其他属性进行聚合和计算。
窗口(Window)是处理无界流的关键所在。窗口可以将数据流装入大小有限的“桶”中,再对每个“桶”加以处理。 本文的重心将放在 Flink 如何进行窗口操作以及开发者如何尽可能地利用 Flink 所提供的功能。
状态管理
在流式处理中,需要跨事件保持状态以便进行计算和统计。Flink 提供了可扩展的状态管理机制,可以高效地处理大规模状态,并支持容错机制以确保状态数据不丢失。
容错性
Flink 具有高度的容错性,能够自动恢复节点故障,保证数据处理的可靠性。
API
Flink 提供了丰富的API,包括 Java、Scala 和 Python API,使得开发人员可以用自己熟悉的编程语言来开发应用程序。
丰富的库
集成多种数据源
Flink 支持多种数据源,包括文件系统、消息队列(如Kafka、RabbitMQ)、数据库等,可以自定义source和sink请查看官网

Demo

//FlinkKafkaConsumer创建一个 Kafka数据源。然后使用flatMap函数处理从 Kafka 获取到的数据,并使用collector处理后的数据到下游。最后我们将处理后的数据存储到HDFS 中
//导入必要的 Flink 库:
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.filesystem.TextOutputFormat;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import java.util.Properties;

//设置 Flink 环境并创建 Kafka 数据源:
public class KafkaToHDFSDemo {
    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "localhost:9092");
        properties.setProperty("group.id", "flink-consumer-group");

        String topic = "your-kafka-topic";
        FlinkKafkaConsumer<String> kafkaConsumer = new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), properties);
        DataStream<String> kafkaStream = env.addSource(kafkaConsumer);

//使用 flatMap 函数处理数据并将结果存储到 HDFS:
        String hdfsPath = "hdfs://localhost:9000/path/to/output/folder";
        TextOutputFormat<String> outputFormat = new TextOutputFormat<>(new org.apache.flink.core.fs.Path(hdfsPath));
        DataStream<String> dataStream = kafkaStream.flatMap((String data, Collector<String> out) -> {
//处理这里传入的数据,并向收集器发出单个字符串
            String[] words = data.split("\\s+");
            for (String word : words) {
                out.collect(word);
            }
        });

        dataStream.writeUsingOutputFormat(outputFormat).setParallelism(1);

        env.execute("Kafka to HDFS");
    }
}
//使用FlinkTable Api
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.core.fs.FileSystem;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;

import java.util.Properties;

public class KafkaToHDFSDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        EnvironmentSettings settings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
        StreamTableEnvironment tEnv = StreamTableEnvironment.create(env, settings);

        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "localhost:9092");
        properties.setProperty("group.id", "flink-consumer-group");

        String topic = "your-kafka-topic";
        FlinkKafkaConsumer<String> kafkaConsumer = new FlinkKafkaConsumer<>(topic, new SimpleStringSchema(), properties);
        DataStream<String> kafkaStream = env.addSource(kafkaConsumer);

        // 定义kafka的schema
        String schema = "message STRING";

        // Kafka流创建一个临时表
        tEnv.createTemporaryView("kafka_table", kafkaStream, schema);

        // 插入HDFS sink表的查询
        String hdfsSinkPath = "hdfs://localhost:9000/path/to/output/folder";
        String insertQuery = "INSERT INTO hdfs_sink SELECT message FROM kafka_table";

        // 查询将数据从Kafka同步到HDFS
        tEnv.executeSql("CREATE TABLE hdfs_sink (message STRING) " +
                "PARTITIONED BY (message) " +
                "WITH ('connector' = 'filesystem', 'path' = '" + hdfsSinkPath + "', 'format' = 'csv')");
        tEnv.executeSql(insertQuery);
    }
}

//自定义UDF
import org.apache.flink.table.functions.ScalarFunction;

public class UpperCaseUDF extends ScalarFunction {

    //字符串转大些
    public String eval(String input) {
        if (input == null) {
            return null;
        }
        return input.toUpperCase();
    }
}

//使用 KeyedState 在 DataStream 中对特定键的状态进行管理和维护
import org.apache.flink.api.common.functions.RichFlatMapFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

public class KeyedStateExample {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 数据源
        DataStream<Tuple2<String, Integer>> dataStream = env.fromElements(
                Tuple2.of("key1", 1),
                Tuple2.of("key2", 2),
                Tuple2.of("key1", 3),
                Tuple2.of("key2", 4),
                Tuple2.of("key1", 5)
        );

        // 对key进行聚合
        DataStream<Tuple2<String, Integer>> resultStream = dataStream
                .keyBy(tuple -> tuple.f0)
                .flatMap(new SumFunction());
        //输出
        resultStream.print();
       //执行
        env.execute("Keyed State Example");
    }

    public static class SumFunction extends RichFlatMapFunction<Tuple2<String, Integer>, Tuple2<String, Integer>> {
        
        private transient ValueState<Integer> sumState;

        @Override
        public void open(Configuration parameters) throws Exception {
            // 使用ValueStateDescriptor初始化ValueState
            ValueStateDescriptor<Integer> descriptor =
                    new ValueStateDescriptor<>("sumState", Integer.class, 0);

            sumState = getRuntimeContext().getState(descriptor);
        }

        @Override
        public void flatMap(Tuple2<String, Integer> value, Collector<Tuple2<String, Integer>> out) throws Exception {
            // 从ValueState中检索当前值
            Integer currentSum = sumState.value();

            // 将当前值与总和相加
            currentSum += value.f1;

            // 更新ValueState中的总和
            sumState.update(currentSum);

            // 发出更新后的总和作为输出
            out.collect(Tuple2.of(value.f0, currentSum));
        }
    }
}

//生产案例
//需求:   abtest平台数据支持
//实现步骤:
//1)与后端商定好前台操作行为json传入到kafka
//2)根据规则解析json生成sql并去查找到结果集
//3)把返回结果集写入到mysql中间表库,并在写好通知程序
//4)监控flink程序异常行为(kafka数据挤压,ads层输出数据是否异常等等)
//主代码如下(涉及到一些方法代码请忽略)

public class Parse {
    // 创建Logger对象
    private static final Logger log = LoggerFactory.getLogger(Parse.class);

    public static void main(String[] args) throws Exception {
        //配置信息
        Properties conf = getProperties("/data/source/data_warehouse/ga/jar/abtestbackground/conf/conf.properties");
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.enableCheckpointing(300000);
        env.getCheckpointConfig().setCheckpointTimeout(300000);
        env.setRestartStrategy(RestartStrategies.fixedDelayRestart(10, Time.seconds(60)));
        //kafka
        String bootstrap_server = conf.getProperty("***");
        String kafka_topic = conf.getProperty("***");
        String group_id = conf.getProperty("***");
        String offset_reset = conf.getProperty("***");
        //接口url
        String msg_url = conf.getProperty("***");
        KafkaSource<String> source = KafkaSource.<String>builder()
                .setBootstrapServers(bootstrap_server)
                .setTopics(kafka_topic)
                .setGroupId(group_id)
                .setStartingOffsets(offset_reset.equals("earliest") ? OffsetsInitializer.earliest() : OffsetsInitializer.latest())
                .setValueOnlyDeserializer(new SimpleStringSchema())
                .build();
        env.setParallelism(2);
        DataStreamSource<String> ABTest_Source = env.fromSource(source, WatermarkStrategy.noWatermarks(), "ABTest_Source");
        SingleOutputStreamOperator<String> flatMap = ABTest_Source.flatMap(new RichFlatMapFunction<String, String>() {
            @Override
            public void flatMap(String s, Collector<String> collector) throws Exception {
                String str = "";
                String pid = "";
                try {
                    str = s;
                    JSONObject jsonObject = JSON.parseObject(str);
                    pid = jsonObject.getString("pid");
                    String is_only_performance_data = jsonObject.getString("is_only_performance_data");

                    String result = "";
                    //"is_only_performance_data":"0",//是否只要效果数据(0:返回全部数据;1:只返回实验效果详细数据)
                    //全部数据:is_only_performance_data=0
                    if (is_only_performance_data.equals("0")) {
                        //实验效果基础数据
                        ArrayList<Object> arrayList = create_Base(str, conf);
                        //实验效果效果详细数据(单天)--,含覆盖用户数(已废弃)
                        ArrayList detail_data = create_Detail(str, conf);
                        arrayList.addAll(detail_data);
                        //用户覆盖数
                        ArrayList user_covered = create_User_Covered(str, conf);
                        arrayList.addAll(user_covered);
                        Return_Data return_data = new Return_Data();
                        return_data.setPid(pid);
                        return_data.setResults(arrayList);
                        result = JSON.toJSON(return_data).toString();
                        System.out.println(result);
                    }
                    if (is_only_performance_data.equals("1")) {
                        //实验效果基础数据
                        ArrayList<Object> arrayList = create_Base(str, conf);
                        Return_Data return_data = new Return_Data();
                        return_data.setPid(pid);
                        return_data.setResults(arrayList);
                        result = JSON.toJSON(return_data).toString();
                        System.out.println(result);
                        /*//实验效果效果详细数据(多天),不含覆盖用户数
                        ArrayList detail_data = create_Detail(str, conf);
                        Return_Data return_data = new Return_Data();
                        ArrayList<Object> arrayList = new ArrayList<>();
                        arrayList.addAll(detail_data);
                        return_data.setPid(pid);
                        return_data.setResults(arrayList);
                        result = JSON.toJSON(return_data).toString();
                        System.out.println(result);*/
                    }

                    to_MySQL(str, result, conf);
                    //查询完成,调用接口通知
                    //线上
                    //String msgUrl = getMsgUrl(msg_url, "pid=" + pid + "&status=1&msg=写入MySQL成功");
                    //测试
                    getTokent_Url(msg_url, "pid=" + pid + "&status=1&msg=写入MySQL成功", "");
                } catch (Exception e) {
                    log.error("JSON 解析失败: " + str);
                    //任务失败,调用接口通知
                    //线上
                    String msgUrl = getMsgUrl(msg_url, "pid=" + pid + "&status=2&msg=JSON 解析失败: " + str);
                    //测试
                    getTokent_Url(msg_url, "pid=" + pid + "&status=2&msg=JSON 解析失败: " + str, "");
                    e.printStackTrace();
                    System.out.println("JSON 解析失败: " + str);
                }
            }
        });

        flatMap.print();
        env.execute("ABTest");
    }
}

问题解答
1 使用flinkapi实时消费神策上报kafka埋点数据处理
2 618和双11的时候,会提前对数据激增flink程序进行资源调整和并发调整
3 合理使用flink的checkpoint,水位线加窗口等等特性实现容错
4 flink提供一套restapi或者自己写一些代码,当发生异常时报警或自动处理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值