Lecture 7 Flink流处理-DataStream开发 ch 1

0 前言

        遇到了问题,一定不要在网上乱找答案,要看它第一次出现错误的位置(Cause by xxx),然后一层一层看。

1.1 初体验

1.1.1 程序一般流程

  1. 获取Flink流处理执行环境
  2. 构建source
  3. 数据处理
  4. 构建sink

1.1.2 示例

      需求:编写Flink程序,用来统计单词的数量。

      步骤:

  1. 获取Flink流处理运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
  2. 构建一个socket源(此时在node1上使用nc -lk 9999开启一个监听端口),没有nc的使用以下命令安装:
    yum install nc -y
    
  3. 使用flink操作进行单词统计
  4. 打印(和批处理里面不太一样)

完整代码如下:

val env = StreamExecutionEnvironment.getExecutionEnvironment
val text = env.socketTextStream("node1", 9999)

val counts = text.flatMap { _.toLowerCase.split("\\W+") filter { _.nonEmpty } }
   .map { (_, 1) }
   .keyBy(_._1)
   .window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
   .sum(1)
counts.print()
env.execute("Window Stream WordCount")

1.1.3 小结

与批处理对比:

1 运行环境对象不同,streamexecutionenviroment

2 有些算子是不同

3 程序是一直运行,除非我们手动停止。

需要注意的点:

1. socketTextStream本身就是一个addsource方法

        可通过调试进入查看

        还可以通过相应的Java jar包

1.2 DataSource基础

1.2.1 数据分类

        Flink在流处理上的source和在批处理上的source基本一致。大致有4大类

        基于本地集合的source(Collection-based-source)--->不常用,不现实

       基于文件的source(File-based-source)- 读取文本文件,即符合 TextInputFormat 规范的文件,并将其作为字符串返回

       基于网络套接字的source(Socket-based-source)- 从 socket 读取。元素可以用分隔符切分。

       自定义的source(Custom-source)

        Flink的stream程序都是通过addSource(sourcefunction)来添加数据源,我们可以自定义数据源,通过继承ParallelSourceFunction RichParallelSourceFunction 来实现自己的数据源。

1.2.2 基于本地集合source演示

// 1 获取流处理运行环境
    val senv = StreamExecutionEnvironment.getExecutionEnvironment
    //0.用element创建DataStream(fromElements)
    val ds0: DataStream[String] = senv.fromElements("spark", "flink")
    ds0.print()

    //1.用Tuple创建DataStream(fromElements)
    val ds1: DataStream[(Int, String)] = senv.fromElements((1, "spark"), (2, "flink"))
    ds1.print()

    //2.用Array创建DataStream
    val ds2: DataStream[String] = senv.fromCollection(Array("spark", "flink"))
    ds2.print()

    //3.用ArrayBuffer创建DataStream
    val ds3: DataStream[String] = senv.fromCollection(ArrayBuffer("spark", "flink"))
    ds3.print()

    //4.用List创建DataStream
    val ds4: DataStream[String] = senv.fromCollection(List("spark", "flink"))
    ds4.print()

    //5.用List创建DataStream
    val ds5: DataStream[String] = senv.fromCollection(ListBuffer("spark", "flink"))
    ds5.print()

    //6.用Vector创建DataStream
    val ds6: DataStream[String] = senv.fromCollection(Vector("spark", "flink"))
    ds6.print()

    //7.用Queue创建DataStream
    val ds7: DataStream[String] = senv.fromCollection(Queue("spark", "flink"))
    ds7.print()

    //8.用Stack创建DataStream
    val ds8: DataStream[String] = senv.fromCollection(Stack("spark", "flink"))
    ds8.print()

    //9.用Stream创建DataStream(Stream相当于lazy List,避免在中间过程中生成不必要的集合)
    val ds9: DataStream[String] = senv.fromCollection(Stream("spark", "flink"))
    ds9.print()

    //10.用Seq创建DataStream
    val ds10: DataStream[String] = senv.fromCollection(Seq("spark", "flink"))
    ds10.print()

    //11.用Set创建DataStream(不支持)
    //val ds11: DataStream[String] = senv.fromCollection(Set("spark", "flink"))
    //ds11.print()

    //12.用Iterable创建DataStream(不支持)
    //val ds12: DataStream[String] = senv.fromCollection(Iterable("spark", "flink"))
    //ds12.print()

    //13.用ArraySeq创建DataStream
    val ds13: DataStream[String] = senv.fromCollection(mutable.ArraySeq("spark", "flink"))
    ds13.print()

    //14.用ArrayStack创建DataStream
    val ds14: DataStream[String] = senv.fromCollection(mutable.ArrayStack("spark", "flink"))
    ds14.print()

    //15.用Map创建DataStream(不支持)
    //val ds15: DataStream[(Int, String)] = senv.fromCollection(Map(1 -> "spark", 2 -> "flink"))
    //ds15.print()

    //16.用Range创建DataStream
    val ds16: DataStream[Int] = senv.fromCollection(Range(1, 9))
    ds16.print()

    //17.用fromElements创建DataStream
    println("-------------------")
    val ds17: DataStream[Long] = senv.generateSequence(1, 9)
    ds17.print()


    // 执行
    senv.execute()

1.2.3 基于文件source演示

//TODO 2.基于文件的source(File-based-source)
//0.创建运行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
//TODO 1.读取本地文件
val text1 = env.readTextFile("data2.csv")
text1.print()
//TODO 2.读取hdfs文件
val text2 = env.readTextFile("hdfs://hadoop01:9000/input/flink/README.txt")
text2.print()
env.execute()

1.2.4 基于网络套接字source演示

        上面有,略。

1.2.5 基于自定义source演示

        Flink stream中我们可以通过实现sourcefunction(不并行的)或者实现parallesourcefunction(并行的)来定义我们自己的数据源方法,然后通过senv.addSource(自定义sourcefunction),就可以读取数据进行转换处理

Ctrl + Alt + B查看SourceFunction接口的所有实现类

步骤:

        1.首先通过实现SourceFunction或者它的子类RichSourceFunction来自定义Source;

        2.然后通过StreamExecutionEnvironment.addSource(sourceFunction)添加进来。

        比如读取Kafka数据的Source:因为用的比较多,Flink 直接提供了 FlinkKafkaConsumer011 等类可供你直接使用。所以,可以直接使用 addSource(new FlinkKafkaConsumer011<>(…)) 从 Apache Kafka 读取数据。当然你也可以自定义。

查看InputFormatSourceFunction中接口方法实现


        写一个非并行的SourceFunction,代码如下:

/*
演示自定义非并行数据源实现
 */
object MySourceNoParalle {
  def main(args: Array[String]): Unit = {
    //1 创建一个流处理的运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 2 添加自定义的数据源
    val myDs: DataStream[Long] = env.addSource(new MyNoParalleSourceFunction)//.setParallelism(3)
    //3 打印数据
    myDs.print()
    //启动
    env.execute()
  }
}

//SourceFunction泛型是我们自定义source的返回数据类型
class MyNoParalleSourceFunction extends SourceFunction[Long] {
  var ele: Long = 0
  var isRunning = true
  //发送数据,生产数据的方法
  override def run(ctx: SourceFunction.SourceContext[Long]): Unit = {
    while (isRunning) {
      ele += 1
      //通过上下文对象发送数据
      ctx.collect(ele)
      //降低发送速度
      Thread.sleep(1000)
    }
  }

  // 取消方法,取消是通过控制一个变量来影响run方法中的while循环
  override def cancel(): Unit = {
    isRunning = false //取消发送数据
  }
}

         说明:上述代码只能实现1个并行度,可以查看run和cancel方法的官方示例;如果需要实现多并行度,那就只能实现ParallelSourceFunction接口。

1.2.6 小结

        1 创建一个class实现sourcefunction接口;

        2 重写run方法,定义生产数据的业务逻辑,重写cancel方法(参考源码示例);

        3 senv.addSource()添加自定义的source;

        4 并行数据源:可以在source operator设置大于1的并行度;发送数据是重复;只需要将上面非并行自定义数据源实现的接口改为ParallelSourceFunction即可。

        5 富ParallelSourceFunction可以提供open,close等方法(可以通过ctrl+o快捷键生成定义;如果操作数据库可以实现在open或者close打开关闭连接)。




        非并行数据源:sourceFunction, source不能设置大于1的并行度,效率会比较低;

        并行数据源:ParallelSourceFunction,source可以设置大于1的并行度,效率会更高;

        富有的并行数据源:RichParallelSourceFunction,source可以设置大于1的并行度,此外还提供了open,close等高效的方法,效率会更高。

1.3 基础代码练习

自定义数据源, 每1秒钟随机生成一条订单信息(订单ID、用户ID、订单金额、时间戳)
要求: 
- 随机生成订单ID(UUID)
- 随机生成用户ID(0-2)
- 随机生成订单金额(0-100)
- 时间戳为当前系统时间
  
开发步骤:
1. 创建订单样例类
2. 获取流处理环境
3. 创建自定义数据源
   - 循环1000次
   - 随机构建订单信息
   - 上下文收集数据
   - 每隔一秒执行一次循环
4. 打印数据
5. 执行任务 

代码如下:

package com.sjxy.flink.stream.source.customsource

import java.util.UUID
import java.util.concurrent.TimeUnit

import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

import scala.util.Random

/*
自定义数据源,练习 生成订单数据
 */

//订单信息(订单ID、用户ID、订单金额、时间戳)
case class Order(id: String, userId: Int, money: Long, createTime: Long)

object OrderCustomSource {
  def main(args: Array[String]): Unit = {
    /*
    1. 创建订单样例类
    2. 获取流处理环境
    3. 创建自定义数据源
       - 循环1000次
       - 随机构建订单信息
       - 上下文收集数据
       - 每隔一秒执行一次循环
    4. 打印数据
    5. 执行任务
     */
    //1  获取流处理环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 2 加载自定义的order数据源,RichParallelSourceFunction泛型是  生产的数据类型,order
    val orderDs: DataStream[Order] = env.addSource(new RichParallelSourceFunction[Order] {
      var isRunning = true

      //2.1生成订单数据方法
      override def run(ctx: SourceFunction.SourceContext[Order]): Unit = {
        //2.1.1 生成订单 业务逻辑
        while (isRunning) {
          //orderid
          val orderId = UUID.randomUUID().toString
          //userid
          val userId = Random.nextInt(3)
          //money
          val money = Random.nextInt(101)
          //createTime
          val createTime = System.currentTimeMillis()
          ctx.collect(Order(orderId, userId, money, createTime))
          //每隔一秒中执行一次
          TimeUnit.SECONDS.sleep(1)
        }
      }

      //2.2 取消数据的生成方法
      override def cancel(): Unit = {
        isRunning = false
      }
    }).setParallelism(1)
    //3 打印数据
    orderDs.print()
    // 4 启动
    env.execute()


  }
}

1.4 DataSource进阶

写一个从 MySQL 中读取数据的 Source

1. 打开DataGrip,连接集群里的MySQL数据库(file->new->datasource->mysql);

2. 生成一张测试的表,添加一些基本的数据;脚本如下:

CREATE TABLE `t_student` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(255) DEFAULT NULL,
    `age` int(11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

    INSERT INTO `t_student` VALUES ('1', 'jack', '18');
    INSERT INTO `t_student` VALUES ('2', 'tom', '19');
    INSERT INTO `t_student` VALUES ('3', 'rose', '20');
    INSERT INTO `t_student` VALUES ('4', 'tom', '19');
    INSERT INTO `t_student` VALUES ('5', 'jack', '18');
    INSERT INTO `t_student` VALUES ('6', 'rose', '20');

3. 增加pom依赖,编写代码;

        pom依赖如下:

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.38</version>
</dependency>

        代码如下:

package com.sjxy.flink.stream.source.customsource

import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet}
import java.util.concurrent.TimeUnit
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

/*
演示自定义并行数据源读取mysql
 */

//定义student 样例类
case class Student(id: Int, name: String, age: Int)

object MysqlRichParallelSource {
  def main(args: Array[String]): Unit = {
    //1 创建一个流处理的运行环境
    val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    // 2 添加自定义的数据源
    val stuDs: DataStream[Student] = env.addSource(new MysqlRichParalleleSource).setParallelism(1)
    //3 打印数据
    stuDs.print()
    //4 启动
    env.execute()
  }
}

// 2自定义mysql并行数据源
class MysqlRichParalleleSource extends RichParallelSourceFunction[Student] {
  var ps: PreparedStatement = null
  var connection: Connection = null

  //2.1 开启mysql连接
  override def open(parameters: Configuration): Unit = {
    //驱动方式
    connection = DriverManager.getConnection("jdbc:mysql://node1:3306/ke", "root", "123456")
    //准备sql语句查询表中全部数据
    var sql = "select id ,name,age from t_student";
    //准备执行语句对象
    ps = connection.prepareStatement(sql)
  }

  //2.3 释放资源,关闭连接
  override def close(): Unit = {
    if (connection != null) {
      connection.close()
    }
    if (ps != null) ps.close()
  }


  var isRunning = true

  // 2.2 读取mysql数据
  override def run(ctx: SourceFunction.SourceContext[Student]): Unit = {
    while (isRunning) {
      //读取mysql中的数据
      val result: ResultSet = ps.executeQuery()
      while (result.next()) {
        val userId = result.getInt("id")
        val name = result.getString("name")
        val age = result.getInt("age")
        //收集并发送
        ctx.collect(Student(userId, name, age))

      }

      //休眠5s,执行一次
      TimeUnit.SECONDS.sleep(5)
    }
  }

  //取消方法
  override def cancel(): Unit = {
    isRunning = false
  }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值