TopN ?SQL就能搞定!


流计算 Oceanus 简介

流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发、无缝连接、亚秒延时、低廉成本、安全稳定等特点的企业级实时大数据分析平台。流计算 Oceanus 以实现企业数据价值最大化为目标,加速企业实时化数字化的建设进程。本文将会介绍如何使用 Flink 实现常见的 TopN 统计需求。首先使用 Python 脚本模拟生成商品购买数据(每秒钟发送一条)并发送到 CKafka,随后在 Oceanus 平台创建 Flink SQL 作业实时读取 CKafka 中的商品数据,经过滚动窗口(基于事件时间)统计每分钟内商品购买种类的前三名(Top3),最后将结果存储于 PostgreSQL。

前置准备

创建流计算 Oceanus 集群

进入 Oceanus 控制台 [1],点击左侧【集群管理】,点击左上方【创建集群】,具体可参考 Oceanus 官方文档 创建独享集群 [2]。

创建消息队列 CKafka

进入 CKafka 控制台 [3],点击左上角【新建】,创建 CKafka 实例,具体可参考 CKafka 创建实例 [4]。随后点击进入实例,单击【topic 管理】>【新建】,即可完成 Topic 的创建,具体可参考 CKafka 创建 Topic [5]。

数据准备

本示例使用 Python 脚本向 Topic 发送模拟数据,前提条件需要网络互通。这里我们选择的是与 CKafka 同 VPC 的 CVM 进入,并且安装 Python 环境。如若网络不通,可在 CKafka 实例里面【基本信息】>【接入方式】>【添加路由策略】>【路由类型】里面选择 VPC 网络公网域名接入 的方式打通网络,具体可参考 CKafka 官网 入门流程指引 [6]。

#!/usr/bin/python3
# 首次使用该脚本,需 "pip3 install kafka" 安装kafka模块
import json
import random
import time
from kafka import KafkaProducer

broker_lists = ['10.0.0.29:9092']
kafka_topic_oceanus = 'oceanus_advanced4_input'

producer = KafkaProducer(bootstrap_servers=broker_lists,
                         value_serializer=lambda m: json.dumps(m).encode('ascii'))

def send_data(topic):
    user_id = random.randint(1,50)
    item_id = random.randint(1,1000)
    category_id = random.randint(1,20)
    user_behaviors = ['pv','buy','cart','fav']
    current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    msg = {
        'user_id':user_id,
        'item_id':item_id,
        'category_id':category_id,
        'user_behavior':user_behaviors[random.randint(0,len(user_behaviors)-1)],
        'time_stamp':current_time
    }
    producer.send(topic, msg)
    print(msg)
    producer.flush()

if __name__ == '__main__':
    count = 1
    while True:
        # 每秒发送一条数据
        time.sleep(1)
        send_data(kafka_topic_oceanus)

更多接入方式请参考 CKafka 收发消息 [7]

创建 PostgreSQL 实例

进入 PostgreSQL 控制台 [8],点击左上角【新建】创建实例,具体参考 创建 PostgreSQL 实例 [9]。进入实例数据库,创建 oceanus_advanced4_output 表,用于接收数据。

-- 建表语句
create table public.oceanus_advanced4_output (
win_start     TIMESTAMP,
category_id   INT,
buy_count     INT,
PRIMARY KEY(win_start,category_id)  );

笔者这里使用 DBeaver 进行外网连接,更多连接方式参考官网文档 连接 PostgreSQL 实例 [10]

流计算 Oceanus 作业

1. 创建 Source

CREATE TABLE `kafka_json_source_table` (
  user_id        INT,
  item_id        INT,
  category_id    INT,
  user_behavior  VARCHAR,
  time_stamp     TIMESTAMP(3),
  WATERMARK FOR time_stamp AS time_stamp - INTERVAL '3' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'oceanus_advanced4_input',    -- 替换为您要消费的 Topic
  'scan.startup.mode' = 'latest-offset', -- 可以是 latest-offset / earliest-offset / specific-offsets / group-offsets / timestamp 的任何一种
  'properties.bootstrap.servers' = '10.0.0.29:9092',  -- 替换为您的 Kafka 连接地址
  'properties.group.id' = 'testGroup',     -- 必选参数, 一定要指定 Group ID
  'format' = 'json',
  'json.fail-on-missing-field' = 'false',  -- 如果设置为 false, 则遇到缺失字段不会报错。
  'json.ignore-parse-errors' = 'true'      -- 如果设置为 true,则忽略任何解析报错。
);

2. 创建 Sink

CREATE TABLE `jdbc_upsert_sink_table` (
    win_start     TIMESTAMP(3),
    category_id   INT,
    buy_count     INT,
    PRIMARY KEY (win_start,category_id) NOT ENFORCED
) WITH (
    'connector' = 'jdbc',
    'url' = 'jdbc:postgresql://10.0.0.236:5432/postgres?currentSchema=public&reWriteBatchedInserts=true',              -- 请替换为您的实际 MySQL 连接参数
    'table-name' = 'oceanus_advanced4_output', -- 需要写入的数据表
    'username' = 'root',                      -- 数据库访问的用户名(需要提供 INSERT 权限)
    'password' = 'Tencent123$',               -- 数据库访问的密码
    'sink.buffer-flush.max-rows' = '200',     -- 批量输出的条数
    'sink.buffer-flush.interval' = '2s'       -- 批量输出的间隔
);

3. 编写业务 SQL

-- 创建临时视图,用于将原始数据过滤、窗口聚合
CREATE VIEW `kafka_json_source_view` AS
SELECT
  TUMBLE_START(time_stamp,INTERVAL '1' MINUTE) AS win_start,
  category_id,
  COUNT(1) AS buy_count
FROM `kafka_json_source_table`
WHERE user_behavior = 'buy'
GROUP BY TUMBLE(time_stamp,INTERVAL '1' MINUTE),category_id;
-- 统计每分钟 Top3 购买种类
INSERT INTO `jdbc_upsert_sink_table`
SELECT
b.win_start,
b.category_id,
CAST(b.buy_count AS INT) AS buy_count
FROM (SELECT *
          ,ROW_NUMBER() OVER (PARTITION BY win_start ORDER BY buy_count DESC) AS rn
      FROM `kafka_json_source_view`
      ) b
WHERE b.rn <= 3;

总结

本文使用 TUMBLE WINDOW 配合 ROW_NUMBER 函数,统计分析了每分钟内购买量前三的商品种类,用户可根据实际需求选择相应的窗口函数统计对应的 TopN。更多窗口函数的使用参考 时间窗口函数 [11]。

作者在落表时将 rn 字段和 win_end 字段裁剪后写入(即无排名优化写入),在使用无 rn 的场景下,需对结果表主键的特别小心,如果定义有误会直接导致 TopN 的结果不准确。