基于文件
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。
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()
}
}
Source | 语义保证 | 备注 |
---|---|---|
kafka | exactly once(仅一次) | 建议使用0.10及以上 |
Collections | exactly once | |
Files | exactly once | |
Socktes | at most once |
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:在后面单独详解
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")
}
}
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
}
}
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"
增加
hset m1 k1 v1
查看
hget m1 k1
删除
hdel m1 k1
批量操作:
增加:
hmset m1 k1 v1 k2 v2
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"
获取全部字段名字和内容
从左边存值(堆栈)
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
添加元素
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
添加元素
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
查看所有的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
基于文件
readTextFile(path)
基于集合
fromCollection(Collection)
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)
writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取
writeAsCsv():将元组以逗号分隔写入文件中,行及字段之间的分隔是可配置的。每个字段的值来自对象的toString()方法
print():打印每个元素的toString()方法的值到标准输出或者标准错误输出流中
将一个数据从一个DataStream广播到其他的DataStream
data.setParallelism(1).broadcast.map(line=>println("接收到的数据:"+line))
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()
}
}
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)
}
}
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()
}
}