Flink SQL + Kafka 实时统计部门人数

一、场景设定

我们设计一个非常经典的小案例:

  • 输入: Kafka Topic employee_information,格式 JSON

  • 字段:

    • emp_id:员工 ID(INT)
    • name:员工姓名(STRING)
    • dept_id:部门 ID(INT)
  • 需求:

    • 实时统计各个部门当前有多少员工;
    • 结果写回到另一个 Kafka Topic department_counts(Upsert 流),方便下游报表 / 大屏消费。

整体结构就是:
Kafka → Flink SQL → Kafka

二、环境假设

先假定你本地已经有:

  • Flink 1.13+(推荐 1.15+ 甚至 1.18+)
  • Kafka 单机(本地 9092)
  • Flink 已经把 flink-sql-connector-kafka-*.jar 放到 lib/ 目录(或 SQL Client 通过 -j 显式加载)。

然后先启动:

bash 复制代码
# 启动 Flink 集群
./bin/start-cluster.sh

# 另开一个终端:启动 SQL Client
./bin/sql-client.sh

Kafka 那边你可以先创建两个 Topic(示例命令):

bash 复制代码
# 员工信息 Topic
kafka-topics.sh --bootstrap-server localhost:9092 \
  --create --topic employee_information --partitions 1 --replication-factor 1

# 部门统计 Topic
kafka-topics.sh --bootstrap-server localhost:9092 \
  --create --topic department_counts --partitions 1 --replication-factor 1

我们用 JSON 作为消息格式,示例消息长这样:

json 复制代码
{"emp_id": 1, "name": "Alice", "dept_id": 1}
{"emp_id": 2, "name": "Bob",   "dept_id": 1}
{"emp_id": 3, "name": "Cindy", "dept_id": 2}

在 SQL Client 里执行下面 DDL,把这个 Topic 映射成一张动态表:

sql 复制代码
-- 源表:员工信息(Kafka JSON)
CREATE TABLE employee_information (
  emp_id  INT,
  name    STRING,
  dept_id INT,
  -- 可以顺带声明一个处理时间,后面如果要做窗口统计会用到
  proc_time AS PROCTIME()
) WITH (
  'connector' = 'kafka',
  'topic' = 'employee_information',
  'properties.bootstrap.servers' = 'localhost:9092',
  'properties.group.id' = 'flink-sql-employee-group',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'json',
  'json.ignore-parse-errors' = 'true'
);

验证一下表能不能读到数据,可以先手动塞几条:

bash 复制代码
kafka-console-producer.sh --broker-list localhost:9092 \
  --topic employee_information
# 然后输入几行 JSON,每行一条
{"emp_id":1,"name":"Alice","dept_id":1}
{"emp_id":2,"name":"Bob","dept_id":1}
{"emp_id":3,"name":"Cindy","dept_id":2}

SQL Client 中跑:

sql 复制代码
SELECT * FROM employee_information;

如果有数据流出来,就 OK 了。

四、定义结果表(Sink):部门人数统计(Upsert-Kafka)

部门人数是一个「按部门去重聚合」的结果,每个 dept_id 对应一个最新的 emp_count ,非常适合用 Upsert-Kafka

sql 复制代码
-- 结果表:部门人数统计(Upsert-Kafka JSON)
CREATE TABLE department_counts (
  dept_id    INT,
  emp_count  BIGINT,
  PRIMARY KEY (dept_id) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'department_counts',
  'properties.bootstrap.servers' = 'localhost:9092',
  'key.format' = 'json',
  'key.json.ignore-parse-errors' = 'true',
  'value.format' = 'json',
  'value.json.ignore-parse-errors' = 'true'
);

这里 PRIMARY KEY (dept_id) NOT ENFORCED 的含义是:

  • Flink 认为 dept_id 是唯一键,用它来生成 Upsert 流;
  • 但不会对数据做强约束检查(NOT ENFORCED),性能更好。

如果你只是想先本地看结果,也可以额外建一个 print 表调试:

sql 复制代码
CREATE TABLE department_counts_print (
  dept_id   INT,
  emp_count BIGINT
) WITH (
  'connector' = 'print'
);

五、实时统计 SQL:按部门人数聚合

核心逻辑其实就一条 SQL:

sql 复制代码
INSERT INTO department_counts
SELECT
  dept_id,
  COUNT(*) AS emp_count
FROM employee_information
GROUP BY dept_id;

语义上是:

  • 随着 employee_information 不断有新员工数据写入;
  • Flink 实时维护每个 dept_idCOUNT(*)
  • 每次计数变化就以 Upsert 形式写入 department_counts Kafka Topic。

如果你也建了 print 表,可以临时这样跑来对比:

sql 复制代码
INSERT INTO department_counts_print
SELECT
  dept_id,
  COUNT(*) AS emp_count
FROM employee_information
GROUP BY dept_id;

六、下游消费:实时查看聚合结果

6.1 用 Kafka 控制台消费 Upsert 流

Upsert-Kafka 的 key 是 dept_id,value 里只有 emp_count

bash 复制代码
kafka-console-consumer.sh \
  --bootstrap-server localhost:9092 \
  --topic department_counts \
  --from-beginning \
  --property print.key=true \
  --property key.separator=:

当你继续往 employee_information 写入数据,比如:

bash 复制代码
kafka-console-producer.sh --broker-list localhost:9092 \
  --topic employee_information
{"emp_id":4,"name":"David","dept_id":1}
{"emp_id":5,"name":"Eric","dept_id":2}
{"emp_id":6,"name":"Frank","dept_id":2}

你会看到 department_countsdept_id=1dept_id=2emp_count 不断被更新。

6.2 用 print Sink 做本地调试

如果用的是 department_counts_print,SQL Client 会直接在控制台打印结果,类似:

text 复制代码
+I(1,2)
+I(2,1)
-U(1,2)
+U(1,3)
...

其中:

  • +I 表示 insert;
  • -U / +U 表示 update 前 / update 后(这是 Flink 内部 changelog 语义)。

七、完整 SQL 脚本(可直接复制到 SQL Client)

下面是一个可以直接丢进 SQL Client 的完整脚本,你可以按顺序执行:

sql 复制代码
-- =========================
-- 1. 源表:Kafka 员工信息
-- =========================
CREATE TABLE employee_information (
  emp_id  INT,
  name    STRING,
  dept_id INT,
  proc_time AS PROCTIME()
) WITH (
  'connector' = 'kafka',
  'topic' = 'employee_information',
  'properties.bootstrap.servers' = 'localhost:9092',
  'properties.group.id' = 'flink-sql-employee-group',
  'scan.startup.mode' = 'earliest-offset',
  'format' = 'json',
  'json.ignore-parse-errors' = 'true'
);

-- =========================
-- 2. 结果表:Upsert-Kafka 部门人数统计
-- =========================
CREATE TABLE department_counts (
  dept_id    INT,
  emp_count  BIGINT,
  PRIMARY KEY (dept_id) NOT ENFORCED
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'department_counts',
  'properties.bootstrap.servers' = 'localhost:9092',
  'key.format' = 'json',
  'key.json.ignore-parse-errors' = 'true',
  'value.format' = 'json',
  'value.json.ignore-parse-errors' = 'true'
);

-- (可选)调试用的 print Sink
CREATE TABLE department_counts_print (
  dept_id   INT,
  emp_count BIGINT
) WITH (
  'connector' = 'print'
);

-- =========================
-- 3. 实时部门人数统计作业:写入 Upsert-Kafka
-- =========================
INSERT INTO department_counts
SELECT
  dept_id,
  COUNT(*) AS emp_count
FROM employee_information
GROUP BY dept_id;

-- (可选)同时把结果打印出来看看
-- INSERT INTO department_counts_print
-- SELECT
--   dept_id,
--   COUNT(*) AS emp_count
-- FROM employee_information
-- GROUP BY dept_id;
相关推荐
♡喜欢做梦40 分钟前
MyBatis操作数据库(进阶):动态SQL
java·数据库·sql·java-ee·mybatis
copyer_xyf41 分钟前
SQL 语法速查手册:前端开发者的学习笔记
前端·数据库·sql
n***s9097 小时前
【MySQL基础篇】概述及SQL指令:DDL及DML
sql·mysql·oracle
jnrjian9 小时前
FRA中 keep的backup set 不保险
sql·oracle
h***047713 小时前
SpringBoot集成Flink-CDC,实现对数据库数据的监听
数据库·spring boot·flink
p***323514 小时前
一条sql 在MySQL中是如何执行的
数据库·sql·mysql
expect7g15 小时前
Paimon Branch --- 流批一体化之二
大数据·后端·flink
瀚高PG实验室15 小时前
Oracle或DM(达梦)时间戳之间的差值SQL迁移到瀚高数据库
数据库·sql·oracle·瀚高数据库
路边草随风16 小时前
starrocks compaction 进度问题定位
大数据·sql