有一些转换(如join、coGroup、keyBy、groupBy)要求在元素集合上定义一个key。还有一些转换(如reduce、groupReduce、aggregate、windows)可以应用在按key分组的数据上。
![ebb9d41cc34e3af43c48ef4e08e1d5be.png](https://img-blog.csdnimg.cn/img_convert/ebb9d41cc34e3af43c48ef4e08e1d5be.png)
Flink的数据模型不是基于key-value对的。因此,不需要将数据集类型物理打包为键和值。key是“虚拟的”:它们被定义为指导分组操作符的实际数据上的函数。
按元组的元素位置分组
最简单的情况是对元组的一个或多个字段进行分组。请看下面的示例代码:
package com.xueai8.ch03;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;
/**
* Created by www.xueai8.com
*
* keyBy转换
*/
public class TransformerKeyBy {
public static void main(String[] args) throws Exception {
// 设置流执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 加载数据源,并执行flatMap转换
DataStream<String> ds = env.fromElements("good good study","day day up")
.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out)
throws Exception {
for(String word: value.split("W+")){
out.collect(word);
}
}
});
// 通过map转换,将事件流中事件的数据类型变换为(word,1)元组形式
DataStream<Tuple2<String,Integer>> ds_map =
ds.map(new MapFunction<String, Tuple2<String,Integer>>() {
@Override
public Tuple2<String, Integer> map(String s) throws Exception {
return new Tuple2<>(s,1);
}
});
// keyBy转换,按key重分区
KeyedStream<Tuple2<String,Integer>,Tuple> ds_keyed = ds_map.keyBy(0);
// 输出
ds_keyed.print();
// 执行
env.execute("flink keyBy transformatiion");
}
}
执行以上代码,输出结果如下:
(up,1)
(study,1)
(day,1)
(day,1)
(good,1)
(good,1)
按字段表达式来定义key
也可以使用字段表达式来定义key。可以使用基于字符串的字段表达式来引用嵌套的字段,并为分组、排序、连接或联合分组定义key。字段表达式使得在(嵌套的)复合类型(如Tuple和POJO类型)中选择字段变得非常容易。例如,在下面的代码中,我们按成员性别分区。
package com.xueai8.ch03;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* Created by www.xueai8.com
*/
public class TransformerKeyBy2 {
// POJO类,用来表示事件流中的事件
public static class Person {
public String name; // 姓名
public String gender; // 性别
public Person() {}
public Person(String name, String gender) {
this.name = name;
this.gender = gender;
}
@Override
public String toString() {
return this.name + ": 性别 " + this.gender;
}
}
public static void main(String[] args) throws Exception {
// 设置流执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 加载数据源
DataStream<Person> personDS = env.fromElements(
new Person("张三", "男"),
new Person("李四", "女"),
new Person("小多米", "男"));
// 执行keyBy转换,按性别(gender)分区
personDS.keyBy("gender").print();
// 执行
env.execute("flink keyBy transformatiion");
}
}
执行程序,输出结果如下:
6> 李四: 性别 女
1> 张三: 性别 男
1> 小多米: 性别 男
注意到,相同的key在同一分区内被计算。另外要特别注意的是,对于要充当key的POJO类,必须满足以下条件:
- 字段名必须声明为public的;
- 必须有默认的无参构造器;
- 所有构造器必须声明为public的。
使用“key selector”函数定义key
定义key的另一种方法是“key selector”函数。一个key selector函数接受单个元素作为输入,并返回该元素的key。返回的key可以是任何类型的,可以从确定性计算中得到。例如,在下面的示例中,我们根据成员的年龄,分为两组:成年人和未成年人。
package com.xueai8.ch03;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* Created by www.xueai8.com
*
* keyBy转换,使用 key selector
*/
public class TransformerKeyBy3 {
// POJO类,用来表示事件流中的事件
public static class Person {
public String name; // 姓名
public Integer age; // 年龄
public Person() {}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name + ": 年龄 " + this.age;
}
}
public static void main(String[] args) throws Exception {
// 设置流执行环境
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 加载数据源
DataStream<Person> personDS = env.fromElements(
new Person("张三", 16),
new Person("李四", 18),
new Person("王老五", 35),
new Person("赵小六", 23),
new Person("小多米", 12));
// 执行keyBy转换,按年龄分区(成年人和未成年人)
KeyedStream<Person,String> keyed = personDS.keyBy(new KeySelector<Person, String>() {
@Override
public String getKey(Person person) throws Exception {
return person.age>=18 ? "adult" : "young";
}
});
// 输出结果流
keyed.print();
// 执行
env.execute("flink keyBy transformatiion");
}
}
执行以下代码,输出结果如下:
6> 李四: 年龄 18
1> 张三: 年龄 16
6> 王老五: 年龄 35
1> 小多米: 年龄 12
6> 赵小六: 年龄 23