水善利万物而不争,处众人之所恶,故几于道
我们要对如下数据进行统计分析,其中每列分别代表的含义是用户id(userId)、行为(behavior)、来源渠道(channel)、时间戳(timestamp)
本案例主要是模拟各大App应用市场的下载、安装、更新、卸载的行为统计分析。我们对每个渠道下的各种行为进行统计,比如vivo应用市场的下载是15,oppo应用市场的更新是23 …
本案例中没有文本文件,而是用自定义数据源实现的(为了复习自定义数据源),我们将上面的数据封装成一个市场用户行为实体,来进行处理。这个实体的属性包括:用户id(userId)、行为(behavior)、来源渠道(channel)、时间戳(timestamp)属性。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MarketingUserBehavior {
private Long userId;
private String behavior;
private String channel;
private Long timestamp;
}
直接使用random随机生成一些用户行为数据,然后封装到一个对象中,最后收集起来就行了。注意来个睡眠,要不然顶不住。
public static class AppSource implements SourceFunction<MarketingUserBehavior> {
// 在这里把source读到的数据放入到流中
@Override
public void run(SourceContext<MarketingUserBehavior> ctx) throws Exception {
Random random = new Random();
String [] behaviors = {"download","install","update","uninstall"};
String [] channels = {"Appstore","huawei","vivo","oppo","xiaomi"};
while (true){
Long userId = (long)(random.nextInt(2000) + 1);
String behavior = behaviors[random.nextInt(behaviors.length)];
String channel = channels[random.nextInt(channels.length)];
Long timestame = System.currentTimeMillis();
ctx.collect(
new MarketingUserBehavior(
userId,
behavior,
channel,
timestame
));
// 1秒生成5条数据
Thread.sleep(200);
}
}
// 取消source读取数据
// 这个方法不会自动执行, 可以在外面条用这个方法
@Override
public void cancel() {
}
}
因为数据源我们返回的是市场用户行为对象,所以我们第一步将这个对象映射为元组类型,并标记,便于后期统计,然后通过keyBy分组,分组的key直接用元组的第一个元素,也就是来源和行为作为分组依据,然后进行sum求和,求出各种渠道下不同行为的的统计值。
// 统计不同的渠道不同的行为的个数
env
// 从自定义数据源读取数据
.addSource(new AppSource())
// 将市场用户行为映射为元组 (oppo_install,1)
.map(new MapFunction<MarketingUserBehavior, Tuple2<String,Long>>() {
@Override
public Tuple2<String, Long> map(MarketingUserBehavior value) throws Exception {
return Tuple2.of(value.getChannel()+"_"+value.getBehavior(),1L);
}
})
// 以渠道和行为进行分组
.keyBy(new KeySelector<Tuple2<String, Long>, String>() {
@Override
public String getKey(Tuple2<String, Long> value) throws Exception {
return value.f0;
}
})
// 求和
.sum(1)
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
public class Flink04_Project_App {
public static void main(String[] args) {
Configuration conf = new Configuration();
conf.setInteger("rest.port",1000);
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(conf);
env.setParallelism(2);
// 统计不同的渠道不同的行为的个数
env
// 从自定义数据源读取数据
.addSource(new AppSource())
// 将市场用户行为映射为元组 (oppo_install,1)
.map(new MapFunction<MarketingUserBehavior, Tuple2<String,Long>>() {
@Override
public Tuple2<String, Long> map(MarketingUserBehavior value) throws Exception {
return Tuple2.of(value.getChannel()+"_"+value.getBehavior(),1L);
}
})
// 以渠道和行为进行分组
.keyBy(new KeySelector<Tuple2<String, Long>, String>() {
@Override
public String getKey(Tuple2<String, Long> value) throws Exception {
return value.f0;
}
})
// 求和
.sum(1)
.print();
try {
env.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
public static class AppSource implements SourceFunction<MarketingUserBehavior> {
@Override
public void run(SourceContext<MarketingUserBehavior> ctx) throws Exception {
Random random = new Random();
String [] behaviors = {"download","install","update","uninstall"};
String [] channels = {"Appstore","huawei","vivo","oppo","xiaomi"};
while (true){
Long userId = (long)(random.nextInt(2000) + 1);
String behavior = behaviors[random.nextInt(behaviors.length)];
String channel = channels[random.nextInt(channels.length)];
Long timestame = System.currentTimeMillis();
ctx.collect(
new MarketingUserBehavior(
userId,
behavior,
channel,
timestame
));
Thread.sleep(200);
}
}
@Override
public void cancel() {
}
}
}
自定义数据源要实现SourceFunction
接口,然后实现接口中的两个抽象方法run
和cancel
,其中run方法是把source读到的数据放入到流中,他一般会有一个while循环去不断的读数据,而cancel方法是终止读数据的方法,他不会主动调用,可以在外面调用这个方法。
然后处理的逻辑比较简单,首先addSource(new AppSource())
就可以读到数据,读到数据后,因为我们的数据流中的数据是一个个的对象,所以我们直接来个map
映射,将对象映射为元组并打上计数标记,然后用keyBy
分组进行sum
求和就可以了