fink-DataStream(四)

4.fink-DataStream(四)

1.source
1.基于文件
readTextFile(path)
读取文本文件,文件遵循TextInputFormat读取规则,逐行读取并返回。
2.基于socket
socketTextStream从socker中读取数据,元素可以通过一个分隔符切开。
3.基于集合
fromCollection(Collection)
通过java 的collection集合创建一个数据流,集合中的所有元素必须是相同类型的。
4.自定义输入
addSource可以实现读取第三方数据源的数据
系统内置提供了一批connectors,连接器会提供对应的source支持【kafka】

以 kafka 消息队列的数据作为来源

1.添加pom依赖
<! – https://mvnrepository.com/artifact/org.apache.flink/flink - connector - kafka - 0.11 – >

org.apache.flink
flink-connector-kafka-0.11_2.11
1.7.2

val properties = new Properties() 
properties.setProperty("bootstrap.servers", "master:9092,slaver1:9092,slaver2:9092")
properties.setProperty("group.id", "consumer-group") 
properties.setProperty("key.deserializer", 
"org.apache.kafka.common.serialization.StringDeserializer") 
properties.setProperty("value.deserializer", 
"org.apache.kafka.common.serialization.StringDeserializer") 
properties.setProperty("auto.offset.reset", "latest") 
 
val stream3 = env.addSource(new FlinkKafkaConsumer011[String]("sensor", new 
SimpleStringSchema(), properties))
自定义 Source

val stream4 = env.addSource( new MySensorSource() )

class MySensorSource extends SourceFunction[SensorReading]{ 
 
// flag: 表示数据源是否还在正常运行 
var running: Boolean = true 
 
override def cancel(): Unit = { 
running = false 
} 
 //实现run方法
override def run(ctx: SourceFunction.SourceContext[SensorReading]): Unit 
= { }
 
} 
2.transformations
map:输入一个元素,然后返回一个元素,中间可以做一些清洗转换等操作
flatmap:输入一个元素,可以返回零个,一个或者多个元素(压平的效果)
filter:过滤函数,对传入的数据进行判断,符合条件的数据会被留下
keyBy:根据指定的key进行分组,相同key的数据会进入同一个分区
    两种典型用法:
    dataStream.keyBy("someKey") // 指定对象中的 "someKey"字段作为分组key
    dataStream.keyBy(0) //指定Tuple中的第一个元素作为分组key
    注意:以下类型是无法作为key的
    1:一个实体类对象,没有重写hashCode方法,并且依赖object的hashCode方法
    2:一个任意形式的数组类型
    3:基本数据类型,int,long
reduce:对数据进行聚合操作,结合当前元素和上一次reduce返回的值进行聚合操作,然后返回一个新的值
aggregations:sum(),min(),max()等
window:在后面单独详解
union:合并多个流,新的流会包含所有流中的数据,但是union是一个限制,就是所有合并的流类型必须是一致的。
connect:和union类似,但是只能连接两个流,两个流的数据类型可以不同,会对两个流中的数据应用不同的处理方法。
CoMap, CoFlatMap:在ConnectedStreams中需要使用这种函数,类似于map和flatmap
Split:根据规则把一个数据流切分为多个流
Select:和split配合使用,选择切分后的流

1.这些算子可以针对 KeyedStream 的每一个支流做聚合。 
 sum() 
 min() 
 max() 
 minBy() 
 maxBy() 
2.keyBy:DataStream → KeyedStream:逻辑地将一个流拆分成不相交的分区,每个分
区包含具有相同 key 的元素,在内部以 hash 的形式实现的。 
3.Reduce :KeyedStream → DataStream:一个分组数据流的聚合操作,合并当前的元素
和上次聚合的结果,产生一个新的值,返回的流中包含每一次聚合的结果,而不是
只返回最后一次聚合的最终结果。 

4. Split 和 Select :
- split:DataStream → SplitStream:根据某些特征把一个 DataStream 拆分成两个或者
多个 DataStream。 
- select :SplitStream→DataStream:从一个 SplitStream中获取一个或者多个 DataStream。

5.Connect 和 CoMap
- connect:DataStream,DataStream → ConnectedStreams:连接两个保持他们类型的数据
 流,两个数据流被 Connect 之后,只是被放在了一个同一个流中,内部依然保持各
 自的数据和形式不发生任何变化,两个流相互独立。 

- coMap/coFlatMap:作用于 ConnectedStreams 上,功能与 map
	和 flatMap 一样,对 ConnectedStreams 中的每一个 Stream 分别进行 map 和 flatMap处理

6.Union :DataStream → DataStream:对两个或者两个以上的 DataStream 进行 union 操
作,产生一个包含所有 DataStream 元素的新 DataStream。

7。Connect 与 Union 区别: 
- Union 之前两个流的类型必须是一样,Connect 可以不一样,在之后的 coMap
中再去调整成为一样的。 
- Connect 只能操作两个流,Union 可以操作多个。
3.sink
writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取
print()/printToErr():打印每个元素的toString()方法的值到标准输出或者标准错误输出流中

自定义输出addSink:Flink支持多种数据源的输出
	比如下面内置的Connectors:
        Apache Kafka (source/sink)
        Apache Cassandra (sink)
        Elasticsearch (sink)
        Hadoop FileSystem (sink)
        RabbitMQ (source/sink)
        Apache ActiveMQ (source/sink)
        Redis (sink)
        

1.kafka-sink

pom

<! -- https://mvnrepository.com/artifact/org.apache.flink/flink - connector - kafka - 0.11 -- > 
<dependency>     
    <groupId>org.apache.flink</groupId>     
    <artifactId>flink-connector-kafka-0.11_2.11</artifactId>    
    <version>1.7.2</version> 
</dependency
  //设置kafka参数
    var properties = new Properties()
    properties.setProperty("bootstrap.servers", "master:9092,slaver1:9092,slaver2:9092")
    properties.setProperty("group.id", "consumer-group")
    properties.setProperty("key.deserializer",
      "org.apache.kafka.common.serialization.StringDeserializer")
    properties.setProperty("value.deserializer",
      "org.apache.kafka.common.serialization.StringDeserializer")
    properties.setProperty("auto.offset.reset", "latest")


    //获取 kafka source ->dataStream
    val stream = env.addSource(new FlinkKafkaConsumer011[String(
        "flink_source_of_kafka_test",//topic name
        new SimpleStringSchema(),//固定写法
        properties//kafka 参数
    ))


//transform
val dataStream: DataStream[String] = stream.map(data => {
      val split: Array[String] = data.split(",")
      SensorReading(split(0).trim, split(1).trim.toLong, split(2).trim.toDouble).toString
    })
//sink
    dataStream.addSink(new FlinkKafkaProducer011[String](
      "master:9092,slaver1:9092,slaver2:9092",//kakfa params
      "sink",//topic name
      new SimpleStringSchema()//固定写法
    ))
2.redis-sink

pom

<! -- https://mvnrepository.com/artifact/org.apache.bahir/flink - connector - redis -- > 
<dependency>     
    <groupId>org.apache.bahir</groupId>     
    <artifactId>flink-connector-redis_2.11</artifactId>    
    <version>1.0</version> 
</dependency> 

redis集群模式

//source
    val stream = env.addSource(new FlinkKafkaConsumer011[String]("flink_source_of_kafka_test", new SimpleStringSchema(), properties))


    //transform
    val dataStream= stream.map(data => {
      val split: Array[String] = data.split(",")
      SensorReading(split(0).trim, split(1).trim.toLong, split(2).trim.toDouble)
    })

    //redis conf
	//redis集群模式
    val nodes = new java.util.HashSet[InetSocketAddress]()
    nodes.add(new InetSocketAddress("master", 7001))
    nodes.add(new InetSocketAddress("slaver1", 7002))
    nodes.add(new InetSocketAddress("slaver2", 7003))
    nodes.add(new InetSocketAddress("slaver1", 7004))
    nodes.add(new InetSocketAddress("slaver2", 7005))
    nodes.add(new InetSocketAddress("master", 7006))
    val redisConf = new FlinkJedisClusterConfig
    .Builder()
      .setNodes(nodes)
      .build()

    //sink
    dataStream.addSink(new RedisSink(redisConf,new MyRedisMapper))

redis-单节点模式核心代码

//redis配置
val conf = new FlinkJedisPoolConfig
.Builder()
.setHost("localhost")
.setPort(6379)
.build() 

//redis-sink 
//使用flink自带的redissink
//也可以自定义redis sink 实现RichSinkFunction

dataStream.addSink( new RedisSink[SensorReading](conf, new MyRedisMapper) )

//自定义
//Person为自定义的bean
class MyRedisMapper extends RedisMapper[Person]{ 
  override def getCommandDescription: RedisCommandDescription = { 
    new RedisCommandDescription(RedisCommand.HSET, "sensor_temperature") 
  } 
  override def getValueFromData(t: Person): String = 
t.temperature.toString 
 
  override def getKeyFromData(t: Person): String = t.id 
} 
 
3.jdbc-sink
// source
    val stream = env.readTextFile("D:\\code\\flink_first\\src\\main\\scala\\sensor.txt")


    val dataStream: DataStream[SensorReading] = stream.map(data => {
      val split: Array[String] = data.split(",")
      SensorReading(split(0).trim, split(1).trim.toLong, split(2).trim.toDouble)

    })

    //sink
    dataStream.addSink( new MyJdbcSink() )//传入自定义的jdbc sink 
    dataStream.print()
    env.execute("kafka sink test")


//自定义function
class MyJdbcSink() extends RichSinkFunction[SensorReading]{ 
  var conn: Connection = _ 
  var insertStmt: PreparedStatement = _ 
  var updateStmt: PreparedStatement = _ 
 
  // open 主要是创建连接
 
  override def open(parameters: Configuration): Unit = { 
    super.open(parameters) 
 
    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", 
"root", "123456") 
    insertStmt = conn.prepareStatement("INSERT INTO temperatures (sensor, 
temp) VALUES (?, ?)") 
    updateStmt = conn.prepareStatement("UPDATE temperatures SET temp = ? WHERE 
sensor = ?") 
  } 
  // 调用连接,执行 sql 
  override def invoke(value: SensorReading, context: 
SinkFunction.Context[_]): Unit = { 
     
	updateStmt.setDouble(1, value.temperature) 
    updateStmt.setString(2, value.id) 
    updateStmt.execute() 
 
    if (updateStmt.getUpdateCount == 0) { 
      insertStmt.setString(1, value.id) 
      insertStmt.setDouble(2, value.temperature) 
      insertStmt.execute() 
    } 
  } 
 
  override def close(): Unit = { 
    insertStmt.close() 
    updateStmt.close() 
    conn.close() 
  } 
}
4.Elasticsearch

pom

<dependency>     
    <groupId>org.apache.flink</groupId>     
    <artifactId>flink-connector-elasticsearch6_2.11</artifactId>
    <version>1.7.2</version> 
</dependency> 
//相当于发送http请求
val httpHosts = new util.ArrayList[HttpHost]() 
httpHosts.add(new HttpHost("localhost", 9200)) 
 
//flink自带的es sink
val esSinkBuilder = new ElasticsearchSink.Builder[SensorReading]( httpHosts, 
new ElasticsearchSinkFunction[SensorReading] { 
  override def process(t: SensorReading, runtimeContext: RuntimeContext, 
requestIndexer: RequestIndexer): Unit = { 
    println("saving data: " + t) 
    val json = new util.HashMap[String, String]() 
    json.put("data", t.toString) 
    val indexRequest = 
Requests.indexRequest().index("sensor").`type`("readingData").source(json) 
    requestIndexer.add(indexRequest) 
    println("saved successfully") 
  } 
} ) 
dataStream.addSink( esSinkBuilder.build() ) 
自定义sin:Java-redis-sink

实现SinkFunction接口或者继承RichSinkFunction

  public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> text = env.socketTextStream("node01", 9999, "\n"); // 连接netcat

        //对数据进行组装,把string转化为tuple2<String,String>
        DataStream<Tuple2<String, String>> l_wordsData = text.map(new MapFunction<String, Tuple2<String, String>>() {
            @Override
            public Tuple2<String, String> map(String value) throws Exception {
                return new Tuple2<>("l_words", value);
            }
        });

        //创建redis的配置
        FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder().setHost("node02").setPort(6379).build();
        //创建redissink
        RedisSink<Tuple2<String, String>> redisSink = new RedisSink<>(conf, new MyRedisMapper());

        l_wordsData.addSink(redisSink);

        env.execute("StreamingDemoToRedis");
    }

    public static class MyRedisMapper implements RedisMapper<Tuple2<String, String>>{
        // 表示从接收的数据中获取需要操作的redis key
        @Override
        public String getKeyFromData(Tuple2<String, String> data) {
            return data.f0;
        }
        // 表示从接收的数据中获取需要操作的redis value
        @Override
        public String getValueFromData(Tuple2<String, String> data) {
            return data.f1;
        }
        // 持久化方式
        @Override
        public RedisCommandDescription getCommandDescription() {
            return new RedisCommandDescription(RedisCommand.LPUSH);
        }
    }
4.重分区
可以事先思考一个问题:让我们自己设计重分区可以有哪几种规则?
Random partitioning:随机分区
	实现方法:dataStream.shuffle()
	查看源码,selectChannels方法使用random.nextInt实现随机分布数据到某个分区中
Rebalancing:对数据集进行再平衡,重分区,消除数据倾斜
	实现方法:dataStream.rebalance() 
	查看源码,selectChannels方法使用“++”的方式使得数据均匀的分布到各个分区中
Rescaling:用在上下游并发不等的时候,决定一对多或多对一的分发策略
	实现方法:dataStream.rescale()
	举个例子:
	如果上游操作有2个并发,而下游操作有4个并发,那么上游的一个并发结果分配给下游的两个并发操作,另外的一个并发结果分配给了下游的另外两个并发操作。另一方面,下游有两个并发操作而上游又4个并发操作,那么上游的其中两个操作的结果分配给下游的一个并发操作,而另外两个并发操作的结果则分配给另外一个并发操作。
    注意:在不同并行度不是彼此的倍数的情况下,一个或多个下游 算子操作将具有来自上游 算子操作的不同数量的输入。由于这种方式仅发生在一个单一的节点,因此没有跨网络的数据传输。
	Rescaling与Rebalancing的区别:
	Rebalancing会产生全量重分区,而Rescaling不会。
	关于Rescaling与Rebalancing的分区策略,如下图。

Custom partitioning:自定义分区
自定义分区需要实现Partitioner接口
	dataStream.partitionCustom(partitioner, "someKey")
	或者dataStream.partitionCustom(partitioner, 0);
	实现demo如下


分区函数:
1、global:上游的分区的数据全部分发到下游的第一个分区中
2、shuffle:上游的每个分区的数据随机分发到下游的分区中
3、rebalance:上游的第一个记录分发到下游的第一个分区,第二条记录分发到下游的第二个分区
4、rescale:(用在上下游并发不等的时候,决定一对多或多对一的分发策略)
5、partitionCustom:自定义分区,实现类需要继承Partitioner接口
6、broadcast:广播分区将上游数据集输出到下游Operator的每个实例中。适合于大数据集Join小数据集的场景。
7、forward:将记录输出到下游本地的operator实例。ForwardPartitioner分区器要求上下游算子并行度一样。上下游Operator同属一个SubTasks。
8、keyby:将记录按Key的Hash值输出到下游Operator实例。
UDF 函数 更细粒度的控制流

Flink 暴露了所有 udf 函数的接口(实现方式为接口或者抽象类)。例如
MapFunction, FilterFunction, ProcessFunction、RichMapFunction、RichFilterFunction 等等

富函数(Rich Functions

“富函数”是 DataStream API 提供的一个函数类的接口,所有 Flink 函数类都
有其 Rich 版本。它与常规函数的不同在于,可以获取运行环境的上下文,并拥有一
些生命周期方法,所以可以实现更复杂的功能。

  • RichMapFunction
  • RichFlatMapFunction
  • RichFilterFunction
  • Rich Function 有一个生命周期的概念。典型的生命周期方法有:
    • open()方法是 rich function 的初始化方法,当一个算子例如 map 或者 filter
      被调用之前 open()会被调用。
    • lose()方法是生命周期中的最后一个调用的方法,做一些清理工作。
    • getRuntimeContext()方法提供了函数的 RuntimeContext 的一些信息,例如函
      数执行的并行度,任务的名字,以及 state 状态
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值