Flink 之 TopN 以及窗口TopN

一、概念

        在Flink中,TopN计算是指从数据流中选取前N个元素,通常根据某个指标进行排序。TopN计算可以在全局范围内进行,也可以在窗口内进行。窗口TopN计算是指在特定的时间窗口或计数窗口内进行TopN计算。这些操作在实时数据分析、监控和推荐系统中非常常见。

二、案例

2.1 topN

        魔法部在英国各地追踪巫师施展的每一个法术,并想知道每个巫师最喜欢的两个法术。 Flink SQL可用于计算连续聚合值,因此如果我们知道巫师施展的每个法术,就可以计算出他们施展该法术的总次数,此结果可用在OVER窗口上以计算Top-N。先对上一步计算的结果按wizard列进行分区,然后再根据施法次数(times_cast DESC)降序排序。最后只保留row_num <= 2的行,从而找到每个巫师最喜欢的2个法术。

代码:

CREATE TEMPORARY TABLE spells_cast (
    wizard STRING,
    spell  STRING
) WITH (
  'connector' = 'faker', 
  'fields.wizard.expression' = '#{harry_potter.characters}',
  'fields.spell.expression' = '#{harry_potter.spells}'
);

-- 找出每个巫师最喜欢的两个法术
SELECT wizard, spell, times_cast
FROM (
    SELECT *,
    ROW_NUMBER() OVER (PARTITION BY wizard ORDER BY times_cast DESC) AS row_num -- 按法术次数降序排序
    FROM (SELECT wizard, spell, COUNT(*) AS times_cast FROM spells_cast GROUP BY wizard, spell) -- 计算每个巫师施展的各种法术的次数
)
WHERE row_num <= 2;

结果:

2.2 窗口topN

        Flink有:滚动窗口、滑动窗口、会话窗口这几种,在此以滚动窗口为例进行示范,有一张订单表 orders,表中包含:下单时间、金额、商品、供货商,要求按照供货商分组每2分钟统计一次汇总的销售金额,并取出每个窗口下汇总销售价格排行前三的供货商名单以及对应销售金额。

代码如下:

CREATE TEMPORARY TABLE orders ( 
    bidtime TIMESTAMP(3),
    price DOUBLE, 
    item STRING,
    supplier STRING,
    WATERMARK FOR bidtime AS bidtime - INTERVAL '5' SECONDS  -- 定义watermark
) WITH (
  'connector' = 'faker',    -- Faker 连接器仅在 VVR-4.0.12 及以上支持
  'fields.bidtime.expression' = '#{date.past ''30'',''SECONDS''}',
  'fields.price.expression' = '#{Number.randomDouble ''2'',''1'',''150''}',
  'fields.item.expression' = '#{Commerce.productName}',
  'fields.supplier.expression' = '#{regexify ''(Alice|Bob|Carol|Alex|Joe|James|Jane|Jack)''}',
  'rows-per-second' = '100'
);

-- 取出销售排名前三的供应商
SELECT *
    FROM (
        -- 按窗口时间分区,按价格降序排序
        SELECT *
              , ROW_NUMBER() OVER (PARTITION BY window_start, window_end ORDER BY price DESC) as rownum
        FROM (
            -- 计算每个窗口内各个供应商的销售额
            SELECT 
                       window_start
                     , window_end
                     , supplier
                     , SUM(price) as price
                     , COUNT(*) as cnt
              FROM TABLE(TUMBLE(TABLE orders, DESCRIPTOR(bidtime), INTERVAL '2' MINUTES))
             GROUP BY window_start, window_end, supplier
        )
    ) WHERE rownum <= 3;

结果:

Flink可以使用ProcessFunction实现TopN操作。下面是一个示例代码,用于计算点击量排名前3名的用户: ```java DataStream<UserBehavior> userBehaviorStream = ...; DataStream<UserViewCount> windowedData = userBehaviorStream .filter(new FilterFunction<UserBehavior>() { @Override public boolean filter(UserBehavior userBehavior) throws Exception { return userBehavior.getBehavior().equals("pv"); } }) .keyBy(new KeySelector<UserBehavior, Long>() { @Override public Long getKey(UserBehavior userBehavior) throws Exception { return userBehavior.getItemId(); } }) .timeWindow(Time.hours(1), Time.minutes(5)) .aggregate(new CountAgg(), new WindowResultFunction()); DataStream<String> topItems = windowedData .keyBy("windowEnd") .process(new TopNHotUsers(3)) .map(new MapFunction<Tuple2<Long, String>, String>() { @Override public String map(Tuple2<Long, String> value) throws Exception { return "窗口结束时间: " + new Timestamp(value.f0) + "\n" + value.f1; } }); topItems.print(); ``` 其中,TopNHotUsers是一个自定义的ProcessFunction,用于计算排名前N的用户。具体实现可以参考以下代码: ```java public class TopNHotUsers extends KeyedProcessFunction<Long, UserViewCount, Tuple2<Long, String>> { private final int topSize; public TopNHotUsers(int topSize) { this.topSize = topSize; } private ListState<UserViewCount> itemState; @Override public void open(Configuration parameters) throws Exception { super.open(parameters); ListStateDescriptor<UserViewCount> itemsStateDesc = new ListStateDescriptor<>("itemState-state", UserViewCount.class); itemState = getRuntimeContext().getListState(itemsStateDesc); } @Override public void processElement(UserViewCount userViewCount, Context context, Collector<Tuple2<Long, String>> collector) throws Exception { itemState.add(userViewCount); context.timerService().registerEventTimeTimer(userViewCount.getWindowEnd() + 1); } @Override public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple2<Long, String>> out) throws Exception { List<UserViewCount> allItems = new ArrayList<>(); for (UserViewCount item : itemState.get()) { allItems.add(item); } itemState.clear(); allItems.sort(new Comparator<UserViewCount>() { @Override public int compare(UserViewCount o1, UserViewCount o2) { return (int) (o2.getViewCount() - o1.getViewCount()); } }); StringBuilder result = new StringBuilder(); result.append("====================================\n"); result.append("时间: ").append(new Timestamp(timestamp - 1)).append("\n"); for (int i = 0; i < Math.min(topSize, allItems.size()); i++) { UserViewCount currentItem = allItems.get(i); result.append("No").append(i + 1).append(":") .append(" 商品ID=").append(currentItem.getItemId()) .append(" 浏览量=").append(currentItem.getViewCount()) .append("\n"); } result.append("====================================\n\n"); out.collect(Tuple2.of(timestamp - 1, result.toString())); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值