Flink进阶(一)

文章目录

  • Flink进阶和代码演练(一)
    • 1、Flink API的抽象级别
    • 2、DataStream
      • 2.1、DataSource
      • 2.2、自定义Source代码
      • 2.3、内置Connectors
      • 2.4、Source 容错性保证
      • 2.5、Transformations
      • 2.6、自定义分区
      • 2.7、自定义Sink写到redis
    • 3、Redis
      • 3.1、String类型
      • 3.2、Hash类型
      • 3.3、其他命令
      • 3.4、List类型
      • 3.5、Set类型
      • 3.6、SortedSet类型zset
      • 3.7、Keys命令
    • 4、DataSet
      • 4.1 DataSet-DataSource
      • 4.2 DataSet-Transformations
      • 4.3 Sink
    • 5、Broadcast
      • 5.1 DataStreaming的Broadcast
      • 5.2 Flink Broadcast
    • 6、Accumulator
      • 6.1 案例代码
    • 7、分布式缓存

Flink进阶和代码演练(一)

1、Flink API的抽象级别

Flink进阶(一)_第1张图片

2、DataStream

2.1、DataSource

基于文件
readTextFile(path)
读取文本文件,文件遵循TextInputFormat读取规则,逐行读取并返回。

基于socket
socketTextStream
从socker中读取数据,元素可以通过一个分隔符切开。

基于集合
fromCollection(Collection)
通过java 的collection集合创建一个数据流,集合中的所有元素必须是相同类型的。

addSource 可以实现读取第三方数据源的数据
系统内置提供了一批connectors,连接器会提供对应的source支持 [kafka]

自定义Source
source是程序的数据源输入,通过StreamExecutionEnvironment.addSource(sourceFunction)来为你的程序添加一个source。
flink提供了大量的已经实现好的source方法。
可以自定义source
通过实现sourceFunction接口来自定义无并行度的source,
通过实现ParallelSourceFunction接口or继承RichParallelSourceFunction来自定义有并行度的source。

2.2、自定义Source代码

package Streaming.CustormSource

import org.apache.flink.streaming.api.functions.source.SourceFunction

/****
  * 自定义实现并行度为1的source
  * 指定数据类型,否则出错
  */
class MySource extends SourceFunction[Int]{
  /***
    *主要的方法
    * 启动一个source
    * 大部分情况下,都需要在这个run方法中实现一个循环
    * 就能循环生产数据
    */
  var isRunning = true
  var count = 0
  override def run(ctx: SourceFunction.SourceContext[Int]): Unit = {
    while(isRunning){
      ctx.collect(count)
      count+=1
      Thread.sleep(1000)
    }
  }
  /***
    * 取消一个cancel时候会调用的方法
    */
  override def cancel(): Unit = {
    isRunning = false
    print("结束啦~")
  }
}
package Streaming.CustormSource
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.source.{RichParallelSourceFunction, SourceFunction}

/**
  *自定义实现一个支持并行度source
  * RichParallelSourceFunction 可以去添加其他的数据来源open,close方法
  */
class MyRichSource extends RichParallelSourceFunction[Int]{
  /***
    *主要的方法
    * 启动一个source
    * 大部分情况下,都需要在这个run方法中实现一个循环
    * 就能循环生产数据
    */
  var isRunning = true
  var count = 1
  override def run(sourceContext: SourceFunction.SourceContext[Int]): Unit = {
    while(isRunning){
      sourceContext.collect(count)
      count+=1
      Thread.sleep(1000)
    }
  }
  override def cancel(): Unit = {
    isRunning = false
    println("结束啦~")
  }
  override def open(parameters: Configuration): Unit = {
    println("数据库连接了")
  }
  override def close(): Unit = {
    println("数据库关闭了")
  }
}
package Streaming.CustormSource
import org.apache.flink.streaming.api.scala._
import org.apache.flink.streaming.api.windowing.time.Time

object MySourceDemo{
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val data = env.addSource(new MyRichSource)
    // 每两秒对产生的数据进行聚合
    //有八个线程都在执行相同的代码,所以数据是0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1,相加是8
    val res = data.timeWindowAll(Time.seconds(2)).sum(0)
    env.execute()
  }
}

2.3、内置Connectors

  • Apache Kafka (source/sink)
  • Apache Cassandra (sink)
  • Elasticsearch (sink)
  • Hadoop FileSystem (sink)
  • RabbitMQ (source/sink)
  • Apache ActiveMQ (source/sink)
  • Redis (sink)

2.4、Source 容错性保证

Source 语义保证 备注
kafka exactly once(仅一次) 建议使用0.10及以上
Collections exactly once
Files exactly once
Socktes at most once

2.5、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()等
union:合并多个流,新的流会包含所有流中的数据,所有合并的流类型必须是一致的。
Connect:和union类似,只能连接两个流,两个流的数据类型可以不同,对两个流中的数据应用不同的处理方法。
CoMap, CoFlatMap:在ConnectedStreams中需要使用这种函数,类似于map和flatmap
Split:根据规则把一个数据流切分为多个流
Select:和split配合使用,选择切分后的流
Random partitioning:随机分区
dataStream.shuffle()
Rebalancing:对数据集进行再平衡,重分区,消除数据倾斜,轮询的去发送一条数据
dataStream.rebalance() 全量重新
Rescaling:
dataStream.rescale() 按比例分区
Custom partitioning:自定义分区
自定义分区需要实现Partitioner接口
dataStream.partitionCustom(partitioner, "someKey")
或者dataStream.partitionCustom(partitioner, 0);
Broadcasting:在后面单独详解

2.6、自定义分区

package Streaming.Partition

import org.apache.flink.api.common.functions.Partitioner
class MyPartition extends Partitioner[Int]{
  override def partition(k: Int, i: Int): Int = {
      if(k%2==0){
        1
      }else{
        0
      }
  }
}
package Streaming.Partition

import Streaming.CustormSource.MyRichSource

object ParitionTest {
  def main(args: Array[String]): Unit = {
    import org.apache.flink.streaming.api.scala._
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    val text = env.addSource(new MyRichSource).setParallelism(1)
    val partitionData = text.map(line=>{
      Tuple1(line)
    }).partitionCustom(new MyPartition,0)
    partitionData.map(line=>{
      println(Thread.currentThread().getId)
      line
    }).print().setParallelism(1)
    env.execute("ParitionTest")
  }
}

2.7、自定义Sink写到redis

package Streaming.Sink

import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, _}
import org.apache.flink.streaming.connectors.redis.RedisSink
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig
import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
/**
  * 将数据写入到redis中
  */
object DataToRedis {
  def main(args: Array[String]): Unit = {
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //获取数据
    val text = env.socketTextStream("mini01",8888,'\n')
    //lpush l_words word

    //对数据进行组装
    val l_wordData = text.map(("l_words",_))
    val conf = new FlinkJedisPoolConfig.Builder().setHost("mini01").setPort(6379).build()
    l_wordData.addSink(new RedisSink[(String, String)](conf,new MyRedisMapper))
    env.execute("data2redis")
  }
}
class MyRedisMapper extends RedisMapper[(String,String)]{
  override def getCommandDescription: RedisCommandDescription = {
     new RedisCommandDescription(RedisCommand.LPUSH)
  }

  override def getKeyFromData(t:(String,String)): String = {
     t._1
  }

  override def getValueFromData(t: (String,String)): String = {
     t._2
  }
}

3、Redis

3.1、String类型

Set
129.0.0.1:6379> set str1 angelababy
OK

Get
129.0.0.1:6379> get str1
"angelababy"

Del
129.0.0.1:6379> del str1
(integer) 1

自增
必须value为数字类型
129.0.0.1:6379> set s1 1
OK
129.0.0.1:6379> incr s1
(integer) 2
129.0.0.1:6379> incr s1
(integer) 3

自减
初始值5
129.0.0.1:6379> decr s1
(integer) 4
129.0.0.1:6379> decr s1
(integer) 3
129.0.0.1:6379> decr s1
(integer) 2
129.0.0.1:6379> decr s1

自增自减指定数值
初始值0
129.0.0.1:6379> incrby s1 100
(integer) 100
129.0.0.1:6379> decrby s1 10
(integer) 90

设置或者获取多个key/value
129.0.0.1:6379> mset s1 v1 s2 v2 
OK
129.0.0.1:6379> mget s1 s2
1) "v1"
2) "v2"

3.2、Hash类型

增加
hset m1 k1 v1
查看
hget m1 k1
删除
hdel m1 k1

批量操作:
增加:
hmset m1 k1 v1 k2 v2

3.3、其他命令

hexists user age		查看user中是否有age字段

hsetnx user age 30      如果user中没有age字段则设置age值为30,否则不做任何操作

只获取字段名或字段值
129.0.0.1:6379> hmset user age 20 name lisi 
OK
129.0.0.1:6379> hkeys user
1) "age"
2) "name"
129.0.0.1:6379> hvals user
1) "20"
2) "lisi"

获取字段数量
hlen user

HMSET items:1001 id 3 name apple price 999.9

192.168.101.3:7003> HGETALL items:1001
1) "id"
2) "3"
3) "name"
4) "apple"
5) "price"
6) "999.9"
获取全部字段名字和内容

3.4、List类型

从左边存值(堆栈)
129.0.0.1:6379> lpush list1 1 2 3 4 5 6
(integer) 6

从右边存值(队列)
129.0.0.1:6379> rpush list1 a b c d
(integer) 10

查看List值 跟的数是下标
lrange list1 0 3
查看全部
lrange list1 0 -1

从两端弹出值
弹出之后就数据就不会存在
lpop list1 
rpop list1

获取列表的长度
llen list1

获得/设置指定索引的元素值
lindex list1 2
lset list1 2 a

只保留列表指定片段
ltrim l:list1 0 2

向列表中插入元素,在元素后面
linsert list after a 4

将元素从一个列表转移到另一个列表中 移动一个
rpoplpush list1 newlist 

3.5、Set类型

添加元素
sadd set1 1 2 3 3 4 5 5
删除元素
srem set1 3
查看元素
smembers set1
判断元素是否存在
sismember set1 6

差集运算
129.0.0.1:6379> sadd set3 2 3 4
(integer) 3
129.0.0.1:6379> sadd set4 1 2 3
(integer) 3
129.0.0.1:6379> sdiff set4 set3
1) "1"
交集
129.0.0.1:6379> sinter set3 set4
1) "2"
2) "3"

并集运算
129.0.0.1:6379> sunion set3 set4
1) "1"
2) "2"
3) "3"
4) "4"

个数
scard set3 

从集合中弹出一个元素
spop set3

3.6、SortedSet类型zset

添加元素
zadd zset1 1 haha 2 hehe 0 heihei

获得排名在某个范围的元素列表
129.0.0.1:6379> zrange zset1 0 3
1) "heihei"
2) "hehe"
129.0.0.1:6379> zrevrange zset1 0 3
1) "hehe"
2) "heihei"
129.0.0.1:6379> zrevrange zset1 0 3 withscores
1) "hehe"
2) "2"
3) "heihei"
4) "0"

获得指定分数范围的元素
ZRANGEBYSCORE zset1 0 97 WITHSCORES

增加某个元素的分数,返回值是更改后的分数。
ZINCRBY zset1  4 hehe

获得集合中元素的数量
ZCARD

获得指定分数范围内的元素个数
ZCOUNT zset1 0 90

按照排名范围删除元素
ZREMRANGEBYRANK zset1 0 1

按照分数范围删除元素
ZREMRANGEBYSCORE zset1 80 100

获取元素的排名
ZRANK zset1 heihei 

从大到小
ZREVRANK scoreboard zhangsan heihei

3.7、Keys命令

查看所有的key
keys *

设置Key的生存时间
设置test的值为1
set test 1

获取test的值
get test
"1"

设置test的生存时间为5秒
EXPIRE test 5

查看test的生成时间,还有1秒删除
TTL test
(integer) 1
TTL test
(integer) -2

获取test的值,已经删除
get test
(nil)

确认一个key 是否存在
exists HongWan

删除一个key
del str1
rename rename age age_new

返回值的类型
type
type addr

4、DataSet

4.1 DataSet-DataSource

基于文件
readTextFile(path)
基于集合
fromCollection(Collection)

4.2 DataSet-Transformations

map:输入一个元素,然后返回一个元素,中间可以做一些清洗转换等操作
    val data1 = env.fromCollection(Array(1,2,3,4,5,6))
    data1.map(_+1).print()


flatMap:输入一个元素,可以返回零个,一个或者多个元素
    val data = env.fromCollection(Array("hello hello","hello java","hello tom"))
    data.flatMap(_.split(" ")).print()


mapPartition:类似map,一次处理一个分区的数据【如果在进行map处理的时候需要获取第三方资源链接,建议使用mapPartition】
    val data1 = env.fromCollection(Array(1,2,3,4,5,6))
    data1.mapPartition(iter=>{
      iter.map(println(_))
    })


filter:过滤函数,对传入的数据进行判断,符合条件的数据会被留下
    val data1 = env.fromCollection(Array(1,2,3,4,5,6))
    data1.filter(_>3).print()
    
    
reduce:对数据进行聚合操作,结合当前元素和上一次reduce返回的值进行聚合操作,然后返回一个新的值
    val data1 = env.fromCollection(Array(1,2,3,4,5,6))
    data1.reduce(_+_).print()


Aggregate:sum、max、min等
    val data1 = env.fromCollection(Array(1,2,3,4,5,6)).map((1,_))
    data1.aggregate(Aggregations.SUM,1).map(_._2).print()
    data1.aggregate(Aggregations.MAX,1).map(_._2)print()
    data1.aggregate(Aggregations.MIN,1).map(_._2)print()


Distinct:返回一个数据集中去重之后的元素,data.distinct()
    val data1 = env.fromCollection(Array(1,2,3,4,5,6,6))
    data1.distinct().print()


Join:内连接
    val data1 = env.fromCollection(Array(("tom",20),("jack",20)))
    val data2 = env.fromCollection(Array(("tom",21),("lucy",20)))
    data1.join(data2).where(0).equalTo(0).map(line=>{
      (line._1._1,line._1._2,line._2._2)
    }).print()
OuterJoin:外链接
    data1.leftOuterJoin(data2).where(0).equalTo(0)
      .apply((x,y)=>{
        if(y==null){
          (x._1,x._2,null)
        }else{
          (x._1,x._2,y._2)
        }
      }).print()

    data1.rightOuterJoin(data2).where(0).equalTo(0)
      .apply((x,y)=>{
        if(x==null){
          (y._1,y._2,null)
        }else{
          (x._1,x._2,y._2)
        }
      }).print()
    data1.fullOuterJoin(data2).where(0).equalTo(0)
      .apply((x,y)=>{
        if(x==null){
          (y._1,y._2,null)
        }else if(y==null){
          (x._1,x._2,null)
        }else {
          (x._1,x._2,y._2)
        }
      }).print()


Cross:获取两个数据集的笛卡尔积
    data1.cross(data2).print()
    
    
Union:返回两个数据集的总和,数据类型需要一致
    data1.union(data2).print()
    

first:获取集合中的前N个元素
    data1.first(1).print()


SortPartition:在本地对数据集的所有分区进行排序,通过sortPartition()的链接调用来完成对多个字段的排序
(分区里有序)
    data2.sortPartition(1,Order.DESCENDING).print()

groupBy和sortGroup 分组和组内排序
    data.flatMap(_.split(" ")).map((_,Random.nextInt(100))).setParallelism(2)
      .groupBy(0)
      .sortGroup(1,Order.DESCENDING)
      .first(2)
      .setParallelism(1)
Rebalance:对数据集进行再平衡,重分区,消除数据倾斜
Hash-Partition:根据指定key的哈希值对数据集进行分区
partitionByHash()
Range-Partition:根据指定的key对数据集进行范围分区
partitionByRange()
    val data = env.readTextFile("C:\\Users\\eRRRchou\\Desktop\\Demo.txt")
    val data1 =  data.map((1,_)).partitionByHash(0).map(line=>{
      println(line+":"+Thread.currentThread().getId)
      line
    })
    data1.rebalance().map(line=>{
      println(line+":"+Thread.currentThread().getId)
    }).print()
    data1.partitionByRange(1).map(line=>{
      println(line+":"+Thread.currentThread().getId)
    }).print()



Custom Partitioning:自定义分区规则
自定义分区需要实现Partitioner接口
partitionCustom(partitioner, "someKey")
或者partitionCustom(partitioner, 0)

4.3 Sink

writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取
writeAsCsv():将元组以逗号分隔写入文件中,行及字段之间的分隔是可配置的。每个字段的值来自对象的toString()方法
print():打印每个元素的toString()方法的值到标准输出或者标准错误输出流中

5、Broadcast

5.1 DataStreaming的Broadcast

将一个数据从一个DataStream广播到其他的DataStream

data.setParallelism(1).broadcast.map(line=>println("接收到的数据:"+line))

5.2 Flink Broadcast

package Streaming.Broadcast

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration

import scala.collection.immutable.HashMap

object BroadcastTest {
  def main(args: Array[String]): Unit = {

    val env = ExecutionEnvironment.getExecutionEnvironment
    val broadcastDate = env.fromCollection(Array(("zs",20),("ls",19),("ww",26)))
    val toBroadcast = broadcastDate.map(line=>{
      Map(line._1->line._2)
    })
    val data = env.fromElements("zs","ls","ww")

    data.map( new RichMapFunction[String,String] {
        var broadcast:java.util.List[Map[String,Integer]] = null
        var allMap = new HashMap[String,Integer]()
      override def open(parameters: Configuration): Unit = {
        broadcast = getRuntimeContext.getBroadcastVariable[Map[String,Integer]]("broadcastName")
        for(i <- 0 until  broadcast.size()){
          allMap++=broadcast.get(i)
        }
      }

      override def map(in: String):String = {
          val age = allMap.get(in)
          in+","+age.getOrElse(0)
      }
    }).withBroadcastSet(toBroadcast,"broadcastName")
      .print()
  }
}

6、Accumulator

6.1 案例代码

package Streaming.Counter

import org.apache.flink.api.common.accumulators.IntCounter
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.{ExecutionEnvironment, _}
import org.apache.flink.configuration.Configuration
object CounterTest {
  def main(args: Array[String]): Unit = {

    val env = ExecutionEnvironment.getExecutionEnvironment

    val data = env.fromElements("a","b","c","d")
    var res = data.map(new RichMapFunction[String,String]{
      var count = new IntCounter()
      override def open(parameters: Configuration): Unit = {
        getRuntimeContext.addAccumulator("num-lines",this.count)
      }

      override def map(in: String): String = {
        count.add(1)
        in
      }
    })
    //res.print()
    //需要提交job又不能使用print
    res.writeAsText("D:/out.txt").setParallelism(1)
    //需要泛型来强转
      val jobRes = env.execute("counter").getAccumulatorResult[Int]("num-lines")
    println(jobRes)
  }
}

7、分布式缓存

Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件
此缓存的工作机你制如下:程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它
用法
1:注册一个文件
env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")  
2:访问数据
File myFile = getRuntimeContext().getDistributedCache().getFile("hdfsFile");

需求:

将分布式集群中的需要用的文件copy到taskmanager的本地

代码:

import java.util
import org.apache.commons.io.FileUtils
import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.{ExecutionEnvironment, _}
import org.apache.flink.configuration.Configuration

object DistributeCache {
  def main(args: Array[String]): Unit = {
    //获取运行环境
    val env = ExecutionEnvironment.getExecutionEnvironment
    //将文件cache到taskmanager本地目录,并启用别名
    env.registerCachedFile("d://out.txt","out")
    val data = env.fromElements("a","b","c","d")
    data.map(new RichMapFunction[String,String] {
      val dataList:util.ArrayList[String] = new util.ArrayList[String]()
      override def open(parameters: Configuration): Unit = {
        val file = getRuntimeContext.getDistributedCache.getFile("out")
        val lines = FileUtils.readLines(file)
        for(i <- 0 until  lines.size()){
          dataList.add(lines.get(i))
          println(lines.get(i))
        }
      }
      override def map(in: String): String = {
        in
      }
    }).print()
  }
}

你可能感兴趣的:(Flink进阶(一))