基于FLINK + PAIMON + StarRocks 分层构建流式湖仓

基于Flink+Paimon构建流式湖仓

j结合Paimon与StarRocks构建流式湖仓-实时计算 Flink版-阿里云

参考:https://blog.youkuaiyun.com/MDZ_1122333322/article/details/145651839

FLINK1.20 + FLINK CDC + MINIO3 + PAIMON1.2 + StarRocks3.5.7 + JDK11 + streampark2.1 

Mysql SQL   FLINK CDC 同步到 PAIMON (基于 minio)
在streampark上 创建 dwd到dws层的Flink任务
通过starrocks查询PAIMON的数据,并构建异步物化视图

在mysql中创建名称为order_dw的数据库

create database order_dw;

建表并写入数据

CREATE TABLE `orders` (
  order_id bigint not null primary key,
  user_id varchar(50) not null,
  shop_id bigint not null,
  product_id bigint not null,
  buy_fee bigint not null,   
  create_time timestamp not null,
  update_time timestamp not null default now(),
  state int not null
);

CREATE TABLE `orders_pay` (
  pay_id bigint not null primary key,
  order_id bigint not null,
  pay_platform int not null, 
  create_time timestamp not null
);

CREATE TABLE `product_catalog` (
  product_id bigint not null primary key,
  catalog_name varchar(50) not null
);

-- 准备数据
INSERT INTO product_catalog VALUES(1, 'phone_aaa'),(2, 'phone_bbb'),(3, 'phone_ccc'),(4, 'phone_ddd'),(5, 'phone_eee');

INSERT INTO orders VALUES
(100001, 'user_001', 12345, 1, 5000, '2023-02-15 16:40:56', '2023-02-15 18:42:56', 1),
(100002, 'user_002', 12346, 2, 4000, '2023-02-15 15:40:56', '2023-02-15 18:42:56', 1),
(100003, 'user_003', 12347, 3, 3000, '2023-02-15 14:40:56', '2023-02-15 18:42:56', 1),
(100004, 'user_001', 12347, 4, 2000, '2023-02-15 13:40:56', '2023-02-15 18:42:56', 1),
(100005, 'user_002', 12348, 5, 1000, '2023-02-15 12:40:56', '2023-02-15 18:42:56', 1),
(100006, 'user_001', 12348, 1, 1000, '2023-02-15 11:40:56', '2023-02-15 18:42:56', 1),
(100007, 'user_003', 12347, 4, 2000, '2023-02-15 10:40:56', '2023-02-15 18:42:56', 1);

INSERT INTO orders_pay VALUES
(2001, 100001, 1, '2023-02-15 17:40:56'),
(2002, 100002, 1, '2023-02-15 17:40:56'),
(2003, 100003, 0, '2023-02-15 17:40:56'),
(2004, 100004, 0, '2023-02-15 17:40:56'),
(2005, 100005, 0, '2023-02-15 18:40:56'),
(2006, 100006, 0, '2023-02-15 18:40:56'),
(2007, 100007, 0, '2023-02-15 18:40:56');

下载:

Central Repository: org/apache/paimon/paimon-flink-action/1.2.0

wget https://repo1.maven.org/maven2/org/apache/paimon/paimon-flink-action/1.2.0/paimon-flink-action-1.2.0.jar .

下载  flink-sql-connector-mysql-cdc-3.1.x.jar

Maven Repository: org.apache.flink » flink-sql-connector-mysql-cdc » 3.1.1

增加的主要依赖包如下:

copy到flink/lib下 重启flink集群


参考:Mysql CDC | Apache Paimon
FLINK CDC 整体 同到 PAIMON,采用 paimon-flink-action-1.2.0.jar

方式一  在flink-1.20.0所在服务期 直接提供 flink 命令

# 提交 Flink 作业
/opt/flink-1.20.0/bin/flink run \
  -Dexecution.checkpointing.interval=10s \
  -Dstate.checkpoints.dir=file:///tmp/flink-checkpoints \
  /opt/flink-1.20.0/lib/paimon-flink-action-1.2.0.jar \
  mysql_sync_database \
  --warehouse s3://warehouse/wh \
  --database order_dw \
  --mysql_conf hostname=192.168.1.247 \
  --mysql_conf port=3306 \
  --mysql_conf username=root \
  --mysql_conf password=123456 \
  --mysql_conf database-name=order_dw \
  --catalog_conf s3.endpoint=http://192.168.1.243:9000 \
  --catalog_conf s3.access-key=minio \
  --catalog_conf s3.secret-key=minio@123 \
  --catalog_conf s3.region=us-east-1 \
  --catalog_conf s3.path.style.access=true \
  --table_conf bucket=1 \
  --table_conf changelog-producer=input \
  --table_conf sink.parallelism=1

方式二 :在streampark上提交任务
参考:在streampark运行paimon-flink-action-1.20.0.jar-优快云博客

 1、上传相关jar包到streampark所在服务的 /opt/flink-1.20.0/lib

 rm -rf flink-sql-connector-mysql-cdc-3.0.1.jar  (要用3.1)

2、在streampark上配置任务

上传

添加程序参数

mysql_sync_database
--warehouse s3://warehouse/wh
--database order_dw
--mysql_conf hostname=192.168.1.247
--mysql_conf port=3306
--mysql_conf username=root
--mysql_conf password=123456
--mysql_conf database-name=order_dw
--catalog_conf s3.endpoint=http://192.168.1.243:9000
--catalog_conf s3.access-key=minio
--catalog_conf s3.secret-key=minio@123
--catalog_conf s3.region=us-east-1
--catalog_conf s3.path.style.access=true
--table_conf bucket=1
--table_conf changelog-producer=input
--table_conf sink.parallelism=1

运行成功:

-------------------------------------------------------------

验证数据:

数据已经同步过来了

starrocks表已经有了

-----------------------------------------------

构建 DWD :dwd_orders

-- Step 2: ?? Paimon Catalog
CREATE CATALOG paimon_minio WITH (
  'type' = 'paimon',
  'warehouse' = 's3://warehouse/wh',
  's3.endpoint' = 'http://192.168.1.243:9000',
  's3.access-key' = 'minio',
  's3.secret-key' = 'minio@123',
  's3.region' = 'us-east-1',
  's3.path.style.access' = 'true'
);
-- ? ??? Paimon Catalog?????
USE CATALOG paimon_minio;
CREATE TABLE IF NOT EXISTS paimon_minio.order_dw.dwd_orders (
  order_id BIGINT,
  order_user_id STRING,
  order_shop_id BIGINT,
  order_product_id BIGINT,
  order_product_catalog_name STRING,
  order_fee BIGINT,
  order_create_time TIMESTAMP,
  order_update_time TIMESTAMP,
  order_state INT,
  pay_id BIGINT,
  pay_platform INT COMMENT 'platform 0: phone, 1: pc',
  pay_create_time TIMESTAMP,
  PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
  'merge-engine' = 'partial-update',
  -- ????????????????
  'partial-update.romove-record-on-delete' = 'true',
  -- ????
  'changelog-producer' = 'lookup' -- ??lookup??????????????????
);
SET
  'execution.checkpointing.max-concurrent-checkpoints' = '3';
SET
  'table.exec.sink.upsert-materialize' = 'NONE';
SET
  'execution.checkpointing.interval' = '10s';
SET
  'execution.checkpointing.min-pause' = '10s';
-- Paimon?????????????????INSERT???????????????UNION ALL?
INSERT INTO
  paimon_minio.order_dw.dwd_orders
SELECT
  o.order_id,
  o.user_id,
  o.shop_id,
  o.product_id,
  dim.catalog_name,
  o.buy_fee,
  o.create_time,
  o.update_time,
  o.state,
  CAST(NULL AS BIGINT) AS pay_id,
  CAST(NULL AS INT) AS pay_platform,
  CAST(NULL AS TIMESTAMP) AS pay_create_time
FROM
  (
    SELECT
      *,
      PROCTIME() AS proctime
    from
      paimon_minio.order_dw.orders
  ) o
  LEFT JOIN paimon_minio.order_dw.product_catalog FOR SYSTEM_TIME AS OF o.proctime AS dim ON o.product_id = dim.product_id
UNION ALL
SELECT
  order_id,
  CAST(NULL AS STRING) AS user_id,
  CAST(NULL AS BIGINT) AS shop_id,
  CAST(NULL AS BIGINT) AS product_id,
  CAST(NULL AS STRING) AS order_product_catalog_name,
  CAST(NULL AS BIGINT) AS order_fee,
  CAST(NULL AS TIMESTAMP) AS order_create_time,
  CAST(NULL AS TIMESTAMP) AS order_update_time,
  CAST(NULL AS INT) AS order_state,
  pay_id,
  pay_platform,
  create_time
FROM
  paimon_minio.order_dw.orders_pay;

--------------------------------------

DWS 

CREATE CATALOG paimon_minio WITH (
  'type' = 'paimon',
  'warehouse' = 's3://warehouse/wh',
  's3.endpoint' = 'http://192.168.1.243:9000',
  's3.access-key' = 'minio',
  's3.secret-key' = 'minio@123',
  's3.region' = 'us-east-1',
  's3.path.style.access' = 'true'
);
USE CATALOG paimon_minio;

SET 'execution.checkpointing.max-concurrent-checkpoints' = '3';
SET 'table.exec.sink.upsert-materialize' = 'NONE';
SET 'execution.checkpointing.interval' = '10s';
SET 'execution.checkpointing.min-pause' = '10s';

-- 为了同时计算用户视角的聚合表以及商户视角的聚合表,另外创建一个以用户 + 商户为主键的中间表。
CREATE TABLE IF NOT EXISTS paimon_minio.order_dw.dwm_users_shops (
    user_id STRING,
    shop_id BIGINT,
    ds STRING,
    payed_buy_fee_sum BIGINT COMMENT '当日用户在商户完成支付的总金额',
    pv BIGINT COMMENT '当日用户在商户购买的次数',
    PRIMARY KEY (user_id, shop_id, ds) NOT ENFORCED
) WITH (
    'merge-engine' = 'aggregation', -- 使用预聚合数据合并机制产生聚合表
    'fields.payed_buy_fee_sum.aggregate-function' = 'sum', -- 对 payed_buy_fee_sum 的数据求和产生聚合结果
    'fields.pv.aggregate-function' = 'sum', -- 对 pv 的数据求和产生聚合结果
    'changelog-producer' = 'lookup', -- 使用lookup增量数据产生机制以低延时产出变更数据
    -- dwm层的中间表一般不直接提供上层应用查询,因此可以针对写入性能进行优化。
    'file.format' = 'avro', -- 使用avro行存格式的写入性能更加高效。
    'metadata.stats-mode' = 'none' -- 放弃统计信息会增加OLAP查询代价(对持续的流处理无影响),但会让写入性能更加高效。
);

INSERT INTO paimon_minio.order_dw.dwm_users_shops
SELECT
    order_user_id,
    order_shop_id,
    DATE_FORMAT (pay_create_time, 'yyyyMMdd') as ds,
    order_fee,
    1 -- 一条输入记录代表一次消费
FROM paimon_minio.order_dw.dwd_orders
WHERE pay_id IS NOT NULL AND order_fee IS NOT NULL;

------------------------------------------------------------------------

创建 dws_users 和 dws_shops

-- Step 2: ?? Paimon Catalog
CREATE CATALOG paimon_minio WITH (
  'type' = 'paimon',
  'warehouse' = 's3://warehouse/wh',
  's3.endpoint' = 'http://192.168.1.243:9000',
  's3.access-key' = 'minio',
  's3.secret-key' = 'minio@123',
  's3.region' = 'us-east-1',
  's3.path.style.access' = 'true'
);
USE CATALOG paimon_minio;
-- 用户维度聚合指标表。
CREATE TABLE IF NOT EXISTS paimon_minio.order_dw.dws_users (
  user_id STRING,
  ds STRING,
  payed_buy_fee_sum BIGINT COMMENT '当日完成支付的总金额',
  PRIMARY KEY (user_id, ds) NOT ENFORCED
) WITH (
  'merge-engine' = 'aggregation',
  -- 使用预聚合数据合并机制产生聚合表
  'fields.payed_buy_fee_sum.aggregate-function' = 'sum' -- 对 payed_buy_fee_sum 的数据求和产生聚合结果
  -- 由于dws_users表不再被下游流式消费,因此无需指定增量数据产生机制
);
-- 商户维度聚合指标表。
CREATE TABLE IF NOT EXISTS paimon_minio.order_dw.dws_shops (
  shop_id BIGINT,
  ds STRING,
  payed_buy_fee_sum BIGINT COMMENT '当日完成支付总金额',
  uv BIGINT COMMENT '当日不同购买用户总人数',
  pv BIGINT COMMENT '当日购买用户总人次',
  PRIMARY KEY (shop_id, ds) NOT ENFORCED
) WITH (
  'merge-engine' = 'aggregation',
  -- 使用预聚合数据合并机制产生聚合表
  'fields.payed_buy_fee_sum.aggregate-function' = 'sum',
  -- 对 payed_buy_fee_sum 的数据求和产生聚合结果
  'fields.uv.aggregate-function' = 'sum',
  -- 对 uv 的数据求和产生聚合结果
  'fields.pv.aggregate-function' = 'sum' -- 对 pv 的数据求和产生聚合结果
  -- 由于dws_shops表不再被下游流式消费,因此无需指定增量数据产生机制
);
SET
  'execution.checkpointing.max-concurrent-checkpoints' = '3';
SET
  'table.exec.sink.upsert-materialize' = 'NONE';
SET
  'execution.checkpointing.interval' = '10s';
SET
  'execution.checkpointing.min-pause' = '10s';
INSERT INTO
  paimon_minio.order_dw.dws_users
SELECT
  user_id,
  ds,
  payed_buy_fee_sum
FROM
  paimon_minio.order_dw.dwm_users_shops;
-- 以商户为主键,部分热门商户的数据量可能远高于其他商户。
-- 因此使用local merge在写入Paimon之前先在内存中进行预聚合,缓解数据倾斜问题。
INSERT INTO
  paimon_minio.order_dw.dws_shops
  /*+ OPTIONS('local-merge-buffer-size' = '64mb') */
SELECT
  shop_id,
  ds,
  payed_buy_fee_sum,
  1,
  -- 一条输入记录代表一名用户在该商户的所有消费
  pv
FROM
  paimon_minio.order_dw.dwm_users_shops;

ADS层 构建物化视图

starrocks 切换会 默认的catalog;

构建基于PAIMON的异步物化视图

查询结果:

相关语句如下:

CREATE MATERIALIZED VIEW ads_users_by_date
REFRESH ASYNC EVERY(INTERVAL 2 MINUTE)
PROPERTIES ("replication_num" = "1")
AS
select * from paimon_minio_catalog.order_dw.dws_users;
 

-------------------------------------

-- 查看 MV 状态
SHOW MATERIALIZED VIEWS LIKE 'ads_users_by_date';
 
-- 手动刷新(可选)
REFRESH MATERIALIZED VIEW ads_users_by_date;
 
-- 查询结果
SELECT * FROM ads_users_by_date;

1、同步任务 为 在命令行中提交的flink任务

2、分层建设任务 为在streampark上提供的3个任务

-----------------------------------------------------------------------------------------------

验证新数据的录入情况

捕捉业务数据库的变化

前面已完成了流式湖仓的构建,下面将测试流式湖仓捕捉业务数据库变化的能力。

PAIMON 中已有数据:

同步任务已启动。

在mysql中添加数据:
 

INSERT INTO orders VALUES
(100008, 'user_001', 12345, 3, 3000, '2023-02-15 17:40:56', '2023-02-15 18:42:56', 1),
(100009, 'user_002', 12348, 4, 1000, '2023-02-15 18:40:56', '2023-02-15 19:42:56', 1),
(100010, 'user_003', 12348, 2, 2000, '2023-02-15 19:40:56', '2023-02-15 20:42:56', 1);



INSERT INTO orders_pay VALUES
(2008, 100008, 1, '2023-02-15 18:40:56'),
(2009, 100009, 1, '2023-02-15 19:40:56'),
(2010, 100010, 0, '2023-02-15 20:40:56');

产看PAIMON中的数据

查看 dwd_orders, 数据已同步:

查看 dwm_users_shops, 数据已有变化:

返回 查看starrocks 内部表

原有数据:

数据已更新为:

<think>我们讨论的是在Flink中使用Paimon(一个流式数据存储)的场景。问题:如果流采用Flink Paimon,那么原始数据是否需要存储,如何存储? 回答: 在Flink Paimon架构中,原始数据通常需要被存储。Paimon是一个流式数据存储,它支持实时数据流的摄入,同时提供批处理和流处理的能力。Paimon的设计目标之一就是作为流批一体的存储层,因此它会将原始数据以某种格式(如Apache Parquet、ORC等)存储在分布式文件系统(如HDFS、S3等)或对象存储中。 存储方式: 1. **表格式**:Paimon使用表格式(类似于Delta Lake、Apache Iceberg)来管理存储在分布式文件系统中的数据文件。它维护了表的元数据(包括schema、分区信息、数据文件列表等),并支持ACID事务。 2. **存储结构**:数据被划分为多个文件(通常是小文件),并且会自动合并小文件以优化读取性能。同时,为了支持流式读取,Paimon还维护了一个变更日志(changelog),这样流处理作业可以实时消费这些变更。 3. **存储位置**:用户需要指定一个根目录(例如,HDFS路径或S3桶路径),Paimon会在该目录下创建数据库和表目录,并在表目录下存储数据文件和元数据。 在Flink中使用Paimon,通常需要以下步骤: 1. 在Flink作业中,使用Paimon作为sink,将数据写入Paimon表。 2. 配置Paimon catalog,指定存储位置和元数据管理方式。 示例代码(Flink SQL): 假设我们有一个Kafka数据源,我们将其写入Paimon表。 首先,创建Kafka表(源表): ```sql CREATE TABLE KafkaTable ( user_id STRING, item_id STRING, behavior STRING, ts TIMESTAMP(3), WATERMARK FOR ts AS ts - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'user_behavior', 'properties.bootstrap.servers' = 'localhost:9092', 'properties.group.id' = 'testGroup', 'format' = 'json', 'scan.startup.mode' = 'earliest-offset' ); ``` 然后,创建Paimon表(目标表): ```sql CREATE TABLE PaimonTable ( user_id STRING, item_id STRING, behavior STRING, ts TIMESTAMP(3), PRIMARY KEY (user_id, item_id) NOT ENFORCED ) WITH ( 'connector' = 'paimon', 'path' = 'hdfs://path/to/paimon/default.db/PaimonTable', -- 或者 's3://bucket/path/to/table' 'auto-create' = 'true' ); ``` 最后,将数据从Kafka表插入到Paimon表: ```sql INSERT INTO PaimonTable SELECT user_id, item_id, behavior, ts FROM KafkaTable; ``` 解释: - 在上述SQL中,我们定义了一个Paimon表,并指定了存储路径(path)。Paimon会自动在该路径下创建表,并存储数据。 - 我们指定了主键(PRIMARY KEY),这样Paimon会以主键来管理记录(更新或删除)。如果不指定主键,则默认为追加模式。 - 设置`'auto-create' = 'true'`使得在表不存在时自动创建表。 因此,原始数据会被存储到指定的文件系统路径中,并且以Paimon的表格式进行组织。 关于原始数据存储的补充说明: - **存储原始数据**:Paimon会存储写入的每一条记录(即原始数据),同时为了支持高效的查询和流读,它会将数据以列式格式(如Parquet)存储,并维护多个版本(通过快照管理)。 - **存储成本**:用户需要考虑存储成本,因为随着数据不断写入,存储量会增长。Paimon提供了快照过期和文件压缩(compaction)机制来管理存储。 §§相关问题§§: 1. Paimon表如何支持流式读取? 2. 在Paimon中如何处理小文件问题? 3. Paimon与Apache Hudi、Delta Lake和Iceberg相比有什么优势? 4. 如何配置Paimon表的压缩策略?</think>### Flink Paimon 原始数据存储方案 在 Flink Paimon流式数据存储)架构中,**原始数据需要持久化存储**。Paimon 的核心设计是作为流批一体的存储层,其存储机制与传统数据类似,但针对流处理进行了优化。以下是详细方案: #### 存储方式与原理 1. **分层存储结构**: - **数据文件**:原始数据以列式格式(Parquet/ORC)存储在分布式文件系统(HDFS/S3) - **日志文件**:增量变更以 Avro 格式存储(类似 Kafka 日志) - **元数据**:通过 `_metadata` 目录管理快照、schema 和分区信息 2. **写流程优化**: ```mermaid graph LR A[Flink Source] --> B[实时数据流] B --> C{Paimon Sink} C --> D[内存Buffer] D --> E[提交检查点] E --> F[生成数据文件] F --> G[更新元数据] ``` #### 存储配置示例(Flink SQL) ```sql -- 创建Paimon表(自动管理存储) CREATE TABLE user_behavior ( user_id STRING, item_id STRING, behavior STRING, event_time TIMESTAMP(3), WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND ) PARTITIONED BY (dt) WITH ( 'connector' = 'paimon', 'path' = 's3://my-bucket/paimon/user_behavior', -- 存储位置 'auto-create' = 'true', 'bucket' = '4', -- 分桶数 'changelog-producer' = 'input', -- 生成变更日志 'merge-engine' = 'deduplicate', -- 主键去重 'snapshot.time-retained' = '1h' -- 快照保留 ); -- 写入数据 INSERT INTO user_behavior SELECT user_id, item_id, behavior, event_time, DATE_FORMAT(event_time, 'yyyy-MM-dd') AS dt -- 动态分区 FROM kafka_source; ``` #### 关键配置说明: 1. **存储位置**:`path` 参数指定分布式存储路径(支持 HDFS/S3/OSS) 2. **分区策略**: - 显式分区:`PARTITIONED BY (dt)` - 动态分区:写入时自动创建分区目录 3. **数据管理**: - `changelog-producer`:生成 CDC 日志供流式计算消费 - `merge-engine`:主键更新策略(deduplicate/partial-update/aggregation) 4. **存储优化**: - `bucket`:控制文件粒度,避免小文件 - `compaction':后台合并小文件(需开启异步压缩) #### 原始数据存储特点: 1. **持久化保证**: - Checkpoint 完成时数据刷写到持久存储 - 支持 Exactly-Once 语义 2. **存储格式**: - 数据文件:列式存储(Parquet) - 变更日志:行式存储(Avro) 3. **生命周期**: - 快照自动清理(`snapshot.time-retained`) - 支持时间旅行查询(Time Travel) #### 运维建议: 1. **存储成本优化**: ```sql ALTER TABLE user_behavior SET ('snapshot.expire.limit' = '50'); -- 保留50个快照 ALTER TABLE user_behavior SET ('compaction.max.file-num' = '10'); -- 合并小文件 ``` 2. **流读优化**: ```sql SELECT * FROM user_behavior /*+ OPTIONS('scan.mode'='latest-full') */; -- 全量+增量读取 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值