万字长文详解|蚂蚁数据湖深度探索与业务应用实践
作者介绍:黄超傑,蚂蚁数据智能部实时数据团队数据研发工程师,致力于数据湖技术在蚂蚁落地应用、蚂蚁广告实时数仓建设、蚂蚁数据成本治理等工作。
在开始之前
如果:
- 你想要提升数据时效,能够接受分钟级延迟
- 你好奇准实时相较离线、实时数据研发究竟能节约多少成本?开发方式有什么不同?
- 你希望数据流批一体、进一步降低成本、降低实时开发难度
- 你不知怎么去设计湖表、搭建湖仓链路
- 你准备探索学习一个新的数据存储引擎/技术栈
这篇文章很有可能会帮到你!
目录
第一章 引言
第二章 湖表设计探索
第三章 湖表应用探索
第四章 湖表运维方案
第五章 准实时、实时、离线方案成本对比
第六章 Paimon建表范式
在本文,你会收获到:
- Paimon建表范式导图(可跳转至⭐️第六章 ⭐️)
- 实时、离线、准实时成本比较方案(可跳转至第五章)
- 涵盖明细/维度/汇总表、指标计算、湖表应用、运维、成本的数据湖探索实践记录
🌟在文章开始之前,你可能会需要的一些前置知识:🌟
本文核心围绕Paimon和Flink这两个技术栈,若你了解对应的基本概念,将会更有助于理解。此处附上两个浅显易懂的文档,读者可按需阅读:
- Paimon基本概念
- Flink基本概念
此外,有部分内容会涉及多个实时、离线、准实时技术引擎,如表所示,读者可对标阿里或开源的相关技术进行理解。
类型 | 蚂蚁 | 阿里 | 开源 |
计算-Stream | AntFlink | VVR/Flink | ApacheFlink |
计算-Batch | ODPS | ODPS | ApacheHive |
存储-OLAP | Explorer | ADB,Hologres | Prosto,ClickHouse |
存储-NoSQL | Lindorm | Lindorm | ApacheHBase |
存储-DFS | Pangu | Pangu | ApacheHadoopHDFS |
消息 | SofaMQ | MetaQ | ApacheRocketMQ,ApacheKafka |
消息 | SLS | SLS,TT | ApacheRocketMQ,ApacheKafka |
数据可视化 | DeepInsight | FBI | Apache Superset |
第一章 引言
1.1 背景
互联网业务高速发展的当今,离线数仓虽然有着成熟的加工链路和相对低廉的成本,但面临数据时效低的问题,而实时计算任务虽然有着秒级延迟,但是任务开发运维复杂、涉及大数据量聚合的flink状态在内存的存储以及实时链路的消息中间件存储成本均比较昂贵。此外,在传统lambda架构下,实时离线各自独立,面对兼具离线、实时诉求的需求时,需要离线和实时技术人员按两条技术链路分别进行加工,中间还涉及大量核对口径、核对数据的“人力成本”,使得相关实时化诉求无法快速满足。
现今数据湖技术趋近成熟,有Apache Iceberg 、Apache Hudi、Apache Paimon这些开源数据湖技术。在各类技术选型和业界信息输入下,笔者了解到Paimon具备数据聚合处理、部分列更新能力,同时天然和flink集成,能结合在线表的CDC信息实现在线表->湖表的准实时同步,并且有相当数量的阿里、蚂蚁同学作为committer投入在Paimon的社区中。
综上,Paimon当前已明确作为蚂蚁数据湖方案选型。笔者希望结合上述实时、离线数据研发痛点和当下Paimon的特性,以某个场景为例,提供一个低门槛、低成本、分钟级延迟、流批一体化的数据研发方案或范式,供业内数据研发参考,也供湖仓技术的引擎侧和平台侧了解当下数据研发所关注的Paimon特性与研发痛点。同时,为了方案的透明化与易用性,探索与分析过程将在本文章作详细分享,供读者查阅与讨论。
1.2 业务场景
本技术探索中,笔者选取了蚂蚁广告业务作为探索场景,涵盖产品、报表、样本、特征场景的探索分析,并挑选了广告投放智能诊断场景的数据研发作为具体应用。
广告主在支付宝投放各类广告,业务运营同学需要结合广告的曝光、点击、转化情况与广告主的各类状态(如账户余额、投放计划等等)对广告投放策略进行调整(如调整素材、充值金额、投放时间段等),蚂蚁广告投放智能诊断产品将投放效果的数据分析思路 沉淀为一个个诊断问题,每个问题对应有若干个指标和规则。诊断产品能够让用户对广告投放情况进行全面诊断(当天或历史),查看广告在对应时间下命中哪些诊断问题,并进一步执行优化操作;诊断报表将各维度诊断指标通过OLAP引擎集成在宽表中,能够支持用户对诊断指标进行自定义多维度分析;诊断告警能及时对命中重要诊断问题的广告进行监控,并汇总推送给用户,用户及时调整投放策略。
蚂蚁广告投放智能诊断场景的数据服务示意图
上述业务形态需要兼具历史和实时数据的展示分析能力,实时时效性可接受分钟级延迟;大量指标的数据来源为广告业务主要流量数据与主要实体数据(如下图),对应了离线/实时重点基础层、中间层数据资产,在数量级和复杂度上均具备一定代表性。
广告主要流量行为
第二章 湖表设计探索
2.1 明细表设计探索
2.1.1 现状和痛点
广告曝光、点击、转化(后简称“展点转”)是广告业务的重要基础流量数据,服务于广告投放效果诊断、漏斗分析、广告模型样本等多个重要领域。
业务层面,面向分析报表/策略调控,有相当一部分场景需要串联展点转数据,以便于在一行数据中得到广告的曝光、点击、转化等信息,从不同维度/口径进行上卷聚合,进行分析/调控;面向算法样本,算法检索日志需要关联每个检索的广告是否有曝光/点击/转化,进而获取算法样本,给模型学习迭代。这两个广告业务场景
技术层面,从实时消费角度来看,当前实时中间层的广告曝光流量SLS由于量非常大,下游在消费时常出现热点(除非上高保集群),但在对内的许多报表或产品场景,指标的更新的延迟性能要求不高,分钟级可接受;从数据质量角度来看,广告日志数据会有重复下发的情况,而部分场景涉及资金计费,需要严格对数据去重,当前实时链路是采用 SLS+下游多个flink任务去重来解决,成本较高。
2.1.2 探索思路
在传统实时或离线的明细加工中,实时侧是把流量行为各自用SLS承接,并在下游针对特定业务去做对应口径下的行为关联操作,离线侧是直接把相关行为串联拼接沉淀至中间层明细资产,供各个业务使用,两条链路互相独立,互不干扰。
理想情况下,数据湖下的流量明细表能够同时服务于准实时消费(分钟级延迟)和离线消费(天级延迟),即一份资产,多种用途。
基于Paimon的partial update特性,可以尝试将广告重点流量行为数据写入一张Paimon宽表,一行数据记录【某次检索在某个展位的某个广告的投放的曝光、点击、转化信息】,把三种相关的行为在一张表中实现关联,下游不再需要在flink任务中去做join;若不对三种行为预先关联,也可基于Paimon主键表的first-row特性,使用Paimon表实现去重,下游也不再需要做去重。
因此,我们可抽象为三个方案探索:
- 方案一:所有明细流直接入湖,使用Paimon主键表能够构造宽表的特性,实现去重+关联,构成行为明细宽表
- 方案二:按行为区分明细流入湖,使用Paimon主键表的first-row特性,实现去重
- 方案三:按行为区分明细流入湖,使用Paimon apppend表承接数据,不去重,对标实时sls/离线ods表
明细数据处理涉及大量数据的etl,会消耗较多资源,这也是技术选型需要关注的重要方面,因此在下文中笔者还将围绕资源和技术复杂度进行比较。
2.1.3 方案探索与讨论
方案一 主键表(行为关联+去重 )
探究问题:
- 分区:小时分区 还是 天分区?如果前者,单个桶的存储大小较少,下游消费性能会较好,但是写入会因为有迟到数据导致writer数量多,影响性能;如果后者,单个桶的存储大小较大,下游消费性能可能会受影响,写入性能较好(因为最多只有两个分区在写)。
- 合并机制:partial-update 还是 aggregation?常识而言,是要采用partial-update,但是在广告场景中,三种流量数据存在大量公共字段存储,在设计之初,笔者希望这部分公共字段在写入时不进行重复更新,于是想到aggregation的first_value聚合函数,能够让字段值维持为第一个写入的值,“看似”可以避免重复更新。
表设计:
表 | 广告曝光、点击、转化明细宽表 | |
表类型 | 主键表 | |
变更机制 | lookup | lookup |
合并机制 | partial-update | aggregation |
写入设计 | 以曝光数据更新大部分公共字段(从业务含义上,大部分情况都是曝光先到,点击和转化后到),点击和转化只更新行为特有字段,避免大量重复字段的重复更新 | 每个行为任务写所有公共字段+行为特有字段,涉及到其他行为特有字段时候,默认写入空值。 聚合策略: first_not_null_value :公共字段、特有字段的写入聚合方式,取第一个非空值。 listagg:公共行为变动字段,采用 “行为打标+字段值”,进行聚合拼接,逗号分隔 |
参数设计 | 分区类型:天分区 生命周期:3天 分桶设计:固定分桶(因为有多个任务同时写入,不能使用动态分桶) 分桶数量:800 checkpoint间隔 = 1分钟 changelog-producer.row-deduplicate = true:避免重复数据下发,使得下游任务不用去重 | 分区类型:小时分区 /天分区 生命周期:3天 分桶设计:固定分桶(因为有多个任务同时写入,不能使用动态分桶) checkpoint间隔 = 1分钟 changelog-producer.row-deduplicate = true:避免重复数据下发,使得下游任务不用去重 |
评价 | 优:
缺:
| 优:
缺:
|
方案二 主键表(去重)
探究问题:
- 分桶:固定分桶 还是 动态分桶?
表名 | 广告曝光明细表 | 广告点击明细表 | 广告转化明细表 |
表类型 | 主键表 | ||
变更机制 | lookup | ||
合并机制 | first-row(去重) | ||
写入设计/字段设计 | 仅带入曝光行为 | 仅带入点击行为 | 仅带入转化行为 |
基本参数设计 | 分区类型:分区表(天分区) 生命周期:1天,按更新时间 分桶:动态分桶/固定分桶 checkpoint间隔 = 1分钟 |
方案三 append表
探索问题:
- Flink写入表的方案有两种,用哪种比较合适?
- 明细写入时在Flink任务去重
- 明细写入时不去重,在下游聚合时去重
表设计:
表 | 广告曝光、点击、转化表 |
表类型 | append表 |
基本参数设计 | 分区类型:分区表 生命周期:1天,按更新时间 分桶:动态分桶 checkpoint间隔 = 1分钟 |
表高级参数设计
明细表的高级参数,主要针对表的写入和消费进行优化,此处列举我们考虑的参数如下
表高级参数设计 | ||
参数名 | 参数值 | 备注 |
partition.expiration-strategy | udpate-time | 按数据更新时间对分区进行生命周期管理,和odps生命周期的逻辑保持一致。(默认是按分区字段的时间) |
partition.expiration-check-interval | 1h | 分区过期检查时间 |
dynamic-bucket.target-row-num | 1000000 | 考虑到流量明细一行内容较大,所以需要参数减少一个分桶容纳的数据条数(默认为200w),进而减少分桶小文件数,优化性能 |
snapshot.time-retained | 1d | snapshot保存一天,对于流读来说,代表回刷时间最多为1天 |
write-buffer-spillable | true | 避免小文件过多的参数 |
write-buffer-for-append | true | |
snapshot.expire.execution-mode | async | paimon 默认会同步删除过期快照。当需要删除的文件过多时,可能无法快速删除,出现反压。为了避免这种情况,用户可以通过设置使用异步过期(async) |
lookup-wait | false | 异步lookup,优化写入性能 |
2.1.4 资源比较
写入任务
方案 | 表设计 |
方案一 宽表 | aggregation宽表(小时分区) |
aggregation宽表(天分区) | |
partial udpate宽表(天分区) | |
方案二 first_row主键表 | 动态分桶-曝光 |
动态分桶-点击 | |
动态分桶-转化 | |
固定分桶-曝光、点击、转化明细写入一张表 | |
方案三 append表 | flink不去重,写入点击、曝光、转化append表 |
flink去重,写入点击、曝光、转化append表 |
结论:
- 整体资源消耗方面,方案三(不去重)<方案三(去重)<方案二(固定分桶)<方案二(动态分桶)<=方案一(天分区, partial update)<=方案一(天分区, aggregation),宽表>单一主键表>非主键表
- 如果数据延迟较大且数据量大,使用天分区比较合适
- 用Paimon去重数据时,数据量较大的情况下,使用固定分桶的资源消耗较少,这是因为动态分桶会不断加大分桶数,导致写入资源消耗无限制扩大。
- 明细数据是否去重写入append表,大致会带来两倍的资源消耗差距,所以在实际场景中,需要根据下游消费任务的数量是否足够多,数量越多,节约的下游去重资源就越多,这部分资源节约可能>在明细去重的资源消耗。
- 方案一、二消耗几乎一致,但是宽表方案在数据的存储上减少了冗余字段的存储,额外有节约存储资源。
消费任务
消费任务选择【广告创意曝光次数】汇总场景的资源消耗情况进行对比,用Paimon进行聚合计算。
方案 |
方案一 aggregation宽表(天分区),下游需要去重 |
方案一 partial update宽表(天分区),下游不需要去重 |
方案二,固定分桶表,下游不去重 |
方案三,写入时不去重,在下游去重 |
实验结论:
- 涉及到下游去重的资源消耗几乎是 下游不去重的两倍。
2.1.5 技术复杂度比较
写入任务
方案一 > 方案二 > 方案三
- 方案一:开发调试需要花费较多的时间,因为涉及多个任务的同时写入更新,需要预估分桶数,写入宽表的任务调试花了不少时间
- 方案二:一个任务直接写入三个主键表
- 方案三:一个任务写入一个表,append表参数也是最少的
消费任务
方案一>方案三>方案二
- 方案一:消费较为复杂,由于多种行为被关联了,需要判断不同行为对应的字段是否为空来判断是否为目标行为,涉及到aggregation表还需要对数据进行去重
- 方案二:直接消费明细
- 方案三:
- flink去重:直接消费明细
- 没有去重:下游消费时,需要在flink任务中去重
2.1.6 小结
结合上述资源比较和开发复杂度比较,笔者认为,在广告流量场景,选取 flink去重写入append表 方案资源消耗和开发复杂度是最优的。
2.2 维度表设计探索
2.2.1 现状与痛点
广告场景有众多实体关系(如下图),针对维度表的探索,我们聚焦在广告投放诊断产品场景,该产品需要针对“单元”实体的各类维度数据进行诊断,具体来说,需要将 创意、单元、计划、委托人、广告主维度表入湖,将多个维度的指标作为“状态指标”映射到单元上,以进行进一步的“诊断逻辑”判断。
当前该产品的数据加工链路中,实时使用Lindorm(类Hbase的存储引擎)作为维度宽表存储最新状态数据,离线用odps表存储历史状态数据,针对这两部分数据各自设计产品模块供用户使用。在数据湖中,怎么以实时离线一体化的形式将大量维度数据存储和映射?维度数据的历史存储问题怎么解决?这都是我们在数据湖维度表设计中遇到的问题。
2.2.2 探索思路
- 基础维度表入湖及其schema设计探索
- 维度宽表schema设计探索
- 尝试实现将离线和实时数据在同一张湖表中存储,统一写入与消费方式
2.2.3 基础维表设计
表类型 | 适用场景 | 构建方案 |
非分区表 | 不需要留存历史数据 | 批任务初始化存量+流任务增量同步 |
分区表 | 需要留存历史的维度数据,实现流批一体 | 分区有两类:
写入方式:(1个初始化批任务,1个流任务,1个日调度批任务)
|
分区维度表-【增全量合并】写入方案示意⬇️
2.2.4 维度宽表设计
打宽方式
要创建一张维度宽表,首先需要确定如何进行打宽操作。Paimon主键表支持非主键关联(1:N)的扩展,因此可以使用多个实体的基础维表进行互相关联,以实现宽表(通过父维度left join子维度实现扩展)。打宽任务可以分为两个步骤:
- 批任务初始化
- 流任务增量数据写入
需要特别注意的是,如果直接采用这种方式进行打宽,当“父子维度对象均为新建(insert)且子维度数据比父维度数据晚到”时,父维度在关联子维度时将无法找到对应的子维度对象,从而导致这部分数据丢失。为避免这种情况,写入任务可以采用双流join、延迟关联或批任务回补等方式。
非分区表
适用场景:不需要留存历史数据
方案:
- 批任务初始化存量,有多少个上游数据来源,对应多少个批任务
- 流任务增量同步,有多少个上游数据来源,对应多少个流任务
分区表
【讨论-使用标签留存历史快照】
维度表设置分区,主要是为了存储历史维度数据,而历史状态存储主要需要面对数据漂移的问题,而维度宽表的写入任务可能非常多,如果全都按照前述基础维表方案进行流批一体设计,复杂度会比较高,因此,如果用户能够容忍宽表的部分数据漂移,可以考虑结合Paimon的标签、分区机制,较简单地留存维度宽表历史数据,因此笔者在此处对标签的使用进行了一系列讨论
Paimon标签机制
围绕标签机制,有如下讨论:
1.报表端或者工程侧当前是否可以通过标签机制消费历史数据?
a.报表侧需要适配
b.工程侧可通过sql直接查询
2.按分区查询是否比按标签查询更快?如果前者比后者快,把标签数据导入到分区中,带来的额外存储资源消耗是否<<直接查询标签提效的收益?
- 标签本质化是把snapshot复制了一份,放在了tag文件夹里,查询的效果就是查snapshot。如果是把数据导入分区,也是在分区里生成一份snapshot,也是查snapshot,查询无差异,存储也无差异。
3.维表Join能否使用标签?
- 不能,一旦使用标签,就是直接查表,不是维表join的逻辑。
4.数据按照标签快照机制的存储是和业务含义是否相符?
- 如果按机器时间,要等待迟到数据的话,也会把没有迟到的数据错误打上标签导致数据漂移,消费方不得不在消费时再进行过滤; 若是状态变化的数据,状态就丢失了,数据错误。
- 如果按watermark的话可能丢数据,而且写入代码会更复杂。那么标签能否按某个字段来生成?--经查阅paimon源代码发现,不行
结合以上讨论,笔者整理Paimon标签创建方案可能存在的问题如下所示↓
【方案设计】
在上述讨论中,我们了解到标签机制只适用于能容忍一定数据误差的场景,因此,还需要考虑更周全的设计方案,更完整准确地留存数据。在下表的维度宽表方案设计中,笔者针对不同方案的任务写入、消费的方式进行详细描述,比较各个方案的优劣和适用场景。
方案描述中的“N”含义:假设维度宽表的诸多维度字段来自 N 个维度表,则对应有N个打宽的任务
方案一 TAG存储历史数据(有漂移) | |
方案描述 | 表设计:
写入设计:
消费设计:
|
优点 | 开发方案最简单 |
缺点 |
|
适用场景 |
|
方案二 TAG导入历史分区存储 (有漂移) | |
方案描述 | 表设计:
写入设计:
消费设计:
|
优点 |
|
缺点 |
|
适用场景 |
|
方案三 历史分区双写 (无漂移) | |
方案描述 | 表设计:
写入设计:
消费设计:
|
优点 |
|
缺点 | 方案复杂,需要运维(N+1 )个flink流任务+1个批任务 |
适用场景 |
|
方案四 同步上游全量写历史分区(无漂移) | |
方案描述 | 前提-要求 上游维表本身已按数据更新时间进行天分区存储,无漂移 如上游维表为业务工程表,则可按照前述【基础维表设计】的内容,将业务工程表做成Paimon分区表,并且每天严格按gmt_modified进行分区存储 写入:
|
优点 |
|
缺点 |
|
适用场景 |
|
附:历史分区双写方案 数据链路与代码示意↓
insert into table_name
select col1,col2,col1_col2_gmt_modified,col1_col2_gmt_modified as dt
from table_name
where dt ='latest'
;insert into table_name
select col3,col3_gmt_modified,col3_gmt_modified as dt
from table_name
where dt ='latest'
insert into table_name
select col1,col2,col1_col2_gmt_modified,'${bizdate}' as dt
from table_name
where dt ='latest' and col1_col2_gmt_modified < concat('${today}','00:00:00')
;insert into table_name
select col3,col3_gmt_modified,'${bizdate}' as dt
from table_name
where dt ='latest' and col1_col2_gmt_modified < concat('${today}','00:00:00')
维表join性能
维表join性能考量方面,笔者在广告投放诊断的指标计算join场景、广告算法样本join曝光场景,使用Lindorm维表与Paimon维表,在不同的静态存储数据量(10g,100g,1TB)和join tps(<100,1000,1万)下,对维表join性能进行探查,得出以下结论:
维表引擎 | Lindorm | Paimon |
数据时效性 | 秒级 | 分钟级 |
数据量 | 大规模(>1TB) | 中小规模(<1TB) |
流式Join 性能 (TPS) | 高 TPS 的实时join (>=1万tps) | 中等TPS (<1000 tps基本稳定运行;1000-1w tps有一定性能瓶颈,反压风险,需针对性优化) |
开发复杂度 | 较高 (Lindorm的理解成本较高,运维也较复杂) | 较低 (Paimon理解成本较低,并天然与flink集成) |
硬件成本 | 高 | 低 |
2.3 指标计算(汇总表)探索
2.3.1 现状与痛点
广告投放诊断产品涉及数百个汇总指标或状态指标计算,结合Paimon的技术特点,一方面,可充分利用Paimon自带的聚合能力计算,其相较实时flink聚合更节省资源,因为状态从Flink内存(Rocksdb)直接放到了Paimon的存储(DFS)中,后者比前者成本更低,还能以更简易的方式支持长周期指标计算;另一方面,Paimon表支持流/批消费,所有指标在湖表中存储,能提供统一的消费方式,规避原来实时/离线两条链路两套口径的方式,可进一步降低开发成本和消费理解成本。
2.3.2 探索思路
计算方面,笔者均使用Paimon的内置聚合函数进行指标计算,不再采用传统的flink聚合计算的方式。
存储方面,无论实时还是离线,一般都是用一张汇总表承接多个指标,也即宽表,因此指标计算探索设计主要围绕Paimon宽表展开:
- Paimon宽表结构可分为横表(partial update表)和竖表(aggregation表),可进行分析讨论
- 当前场景涉及多个父子维度指标,因此可考虑使用在子维度指标预聚合基础上,再聚合至父维度,与直接聚合至父维度的方案进行比较,预聚合也可降低明细层的读取压力/资源消耗。
2.3.3 方案探索与讨论
横表 vs 竖表
竖表schema示例
字段 | 聚合参数 |
对象id(主键) | 无 |
统计时间窗口(主键) | 无 |
指标名 | 无 |
最新指标值 | last_not_null_value |
求和指标值 | sum |
计数指标值 | count |
平均指标值 | avg |
实际数据示例⬇️
ad_id | metric_name | cnt(count) |
a | expo | 10 |
b | expo | 20 |
c | expo | 5 |
c | clk | 5 |
横表schema示例
字段 | 聚合参数 |
对象id(主键) | 无 |
统计时间窗口(主键) | 无 |
聚合指标1 | count |
聚合指标2 | last_not_null_value |
聚合指标3 | sum |
实际数据示例⬇️
ad_id | expo_cnt | clk_cnt |
a | 10 | 0 |
b | 20 | 0 |
c | 5 | 5 |
【方案讨论】
应用场景 | 评价维度 | Paimon汇总表设计方案 | |
横表(partial_udpate+aggregation) | 竖表(aggregation) | ||
作中间表 (需要触发变更,供下游计算) | 适用场景 |
|
|
缺陷讨论 | 如果采用partial-update+ agg+ lookup的形式写入计算多个指标,任意一个指标变动,整行都会下发(非相关指标的默认为空),所以可能会有以下几个问题:
|
| |
作维表/作结果表 (不需要设置表变更机制) | 适用场景 | 作维表:维度大宽表 作结果表:报表OLAP查询,产品查询 | 作结果表:指标列存,读取速度快,适合单指标消费场景; |
缺陷讨论 |
| 作结果表:报表场景下,往往涉及多个指标消费,可能还需后置打宽,泛用性较低。 作维表:竖表天然不适合作维表,一行信息量很少。 |
【关于横表的下游消费问题】
围绕上述讨论中,有关指标横表的【缺陷讨论】,笔者做了实际的实验验证如下
实验数据链路⬇️
当单元曝光汇总链路上游执行回刷操作,向partial update表回刷数据时,曝光汇总的任务运行情况如下↓
此时partial update表的下游消费【消耗指标】的任务↓运行状态出现变化,即-在曝光指标的上游回刷时,消耗指标的消费任务输入TPS也会有大幅增长,两个直观看似不想关的指标在此场景下互相影响了。
基于该现象,笔者将【消费消耗指标的任务】读取的source数据打印输出,分别统计【曝光回刷数据期间】和【常态运行期间】情况下的输出数据情况(消耗为空的数据行数,消耗不为空的数据行数),经比较发现:
- 无论是在回刷还是常态运行,曝光的数据更新会给下游消耗的消费任务带来额外的资源消耗(额外的数据为“消耗为空的数据”)
- 横表下发的数据中,消耗为空的占了1/3,也就是有1/3的数据都必须在下游任务中通过 消耗指标is not null 进行过滤,否则可能会引发一连串问题(如下游还有join逻辑,就会有很多无效的join发生)
【结论】
综上所述:
- 若计算指标需要沉淀在中间层,优先采用竖表形式,不宜在准实时中间层使用partial update横表(会有额外的资源浪费、耦合风险);
- 若涉及同个维度多个指标的同时消费,如大量OLAP报表场景,此时下游不再需要流读消费,可以采用partial update横表的形式,一次查询查出多个指标。
结合广告投放诊断产品的应用场景,笔者在该场景汇总层采用 先使用竖表进行指标计算,再打横至指标横表的方式进行研发。
预聚合 vs 直接聚合
在分钟级延迟即可满足需求的场景中,Paimon 的聚合性能相比 Flink 聚合具有更低的资源消耗和更简单的代码。因此,在处理聚合 PV、金额等累加场景时,使用 Paimon 完成数据聚合是一个高效的选择。
在实际业务场景中,可能需要在数据湖中围绕某个指标进行多维度的聚合,这些维度之间存在父子关联关系。在这种情况下,可以选择将明细数据写入不同维度的主键表中完成聚合,但这会导致多次重复读取明细数据,资源消耗较大(如图所示,方案一)。
为了进一步节省资源,考虑到 Paimon 的聚合特性,是否可以一次写入明细数据进行预聚合,然后再进行多次上卷聚合(如图所示,方案二),这是一个值得深入探讨的问题。
在 Paimon 的聚合特性中,SUM、COUNT 和 PRODUCT 等聚合函数支持回撤消息。因此,理论上,在完成第一次预聚合后,Paimon 的变更日志(Changelog)会产生 +U 和 -U 的数据。这些变更日志可以写入下游的 Paimon 表中,进行二次聚合,从而实现高效的多维度预聚合。
Paimon lookup机制图示
【实验结果】
方案 | 延迟 | 资源消耗 |
一次聚合 | 二次聚合比一次聚合延迟 1-2min | 二次聚合的资源消耗约为一次聚合的三分之一 |
二次聚合 |
【结论】
二次聚合优于一次聚合,更新延迟在分钟级可接受范围内,因此,在相关指标能进行上卷聚合时,优先进行二次聚合加工。
2.3.4 表设计
竖表-指标计算中间表设计
表基本信息 | ||
表类型 | 主键表 | |
写入模式(合并机制) | aggregation (写入时聚合) | |
分桶模式 | 固定分桶 | |
分桶键 | id+统计时间窗口+统计时间+指标名称 | |
主要字段组成 | 聚合逻辑 | 备注 |
对象id(主键) | 默认 | |
统计时间窗口(主键, 分区) | 默认 | 1D:天粒度 1H:小时粒度 latest: 最新状态值(涉及维度指标计算的延伸指标) ts: 长周期累积指标 |
统计时间(主键, 分区) | 默认 | 天粒度格式为yyyyMMdd 小时粒度格式为yyyyMMddHH 状态指标可为固定值,如20990101000000 累积指标(长周期指标)可选为累积起始时间+ts,如 '20240101ts' |
指标名称(主键, 分区) | 默认 | |
非主键维度值 | 默认 | 根据实际需要填写,常使用当前维度的父维度对象id,用于后续指标上卷计算 |
聚合求和值 | sum | |
聚合计数值 | sum | 注:当前Paimon 0.9的agg功能不再支持count,因此count_value字段采取的agg策略是sum,相关后续相关任务想要做计数聚合时,可采用 sum(1)的方式,直接往count_value字段写入1值,和count效果等同 |
聚合最新非空值 | last_non_null_value | |
聚合乘积值 | product | |
gmt_modified | last_non_null_value | 用于记录写入该表的数据更新时间,可由写入任务生成current timestamp |
max_upload_time | max | 最大行为上报时间(工程日志的上报时间),记录最新一条明细上报的时间,用以分析数据延迟 |
max_upload_time | max | 最大行为事件时间(用户行为发生时间),记录最新一条用户行为发生的时间,用以分析数据延迟 |
...(根据聚合函数,按需添加) | ... | ... |
参数设置 | ||
错误参数:⬇️ source.ignore-delete:true #下游需要二次聚合时,该参数不能等于true |
#下游需要流读该表时,需打开该参数。(此处下游需要将指标打横,所以配置该参数)
changelog-producer:lookup
#有多个任务写入时,必须打开该参数,同时加上压缩任务
write-only = true
#按分区数据更新时间进行分区生命周期管理
partition.expiration-strategy:update-time
#分区检查间隔时间
partition.expiration-check-interval:1d
#使得下游最多可回刷历史1天数据,根据实际业务需求填写
snapshot.time-retained: 1d
#若上游有回撤流(如有去重、聚合),除了sum、product 和 count 聚合函数字段外,其它字段均需要设置回撤
fields.<field-name>.ignore-retract = true
横表-指标结果表设计
表基本信息 | ||
表类型 | 主键表 | |
写入模式(合并机制) | partial-update (部分字段更新) | |
分桶模式 | 固定分桶 | |
分桶键 | id+统计时间窗口+统计时间 | |
字段组成 | 聚合逻辑 | 备注 |
对象id(主键) | 默认 | |
统计时间窗口(主键, 分区) | 默认 | 1D:天粒度 1H:小时粒度 latest: 最新状态值(涉及维度指标计算的延伸指标) ts: 长周期累积指标 |
统计时间(主键, 分区) | 默认 | 天粒度格式为yyyyMMdd 小时粒度格式为yyyyMMddHH 状态指标可为固定值,如20990101000000 累积指标(长周期指标)可选为累积起始时间+ts,如 '20240101ts' |
非主键维度值 | 默认 | 根据实际需要填写,常使用当前维度的父维度对象id,用于后续指标上卷计算 |
A来源指标 | 默认 | |
A来源指标更新时间 | 默认 | 可选,用来查验该来源的指标的更新时间 |
B来源指标 | 默认 | |
B来源指标更新时间 | 默认 | 可选,用来查验该来源的指标的更新时间 |
C来源指标 | 默认 | |
... | ||
参数设置 |
#可选,若该表不作流任务的source则不用设置(横表作为结果表,下游一般不再流消费了)
changelog-producer:lookup
#有多个任务写入时,必须打开该参数,同时加上压缩任务
write-only = true
#按分区数据更新时间进行分区生命周期管理
partition.expiration-strategy:update-time
#分区检查间隔时间
partition.expiration-check-interval:1d
#使得下游最多可回刷历史1天数据,根据实际业务需求填写
snapshot.time-retained: 1d
-
此处对多个来源的指标聚合逻辑采用默认(last_not_null_value),是考虑到需要回刷/稳定性要求较强的场景,所有指标在上游(指标竖表)做好计算,在结果表/维表处使用默认的last_not_null_value的聚合方式,这样就能实现【部分列回刷】,避免单个聚合指标的口径更新/回刷导致整个分区的数据都要重刷。
-
在指标横表中,笔者建议根据不同的【统计时间窗口】创建不同的表,而不是所有时间窗口的指标都往一个表写,否则会存在“某些指标字段在某些时间窗口总为空值”的情况,不易理解。
第三章 湖表应用探索
明细表、维度表、指标计算(汇总表)的开发,都还只是一个个“点”的问题,最终做这些点都需要串联成线,提供给业务应用(如产品、报表)。因此,笔者需要针对蚂蚁广告投放诊断的应用场景,结合当前湖表的消费方式和性能考量,设计对应的湖数据资产的整体架构设计和供给形式。
应用场景描述:
- 场景1:工程后端查数消费,供产品使用(涉及对客产品),需要支持快速点查,查询延迟敏感。
- 场景2:报表消费(涉及OLAP能力),内部运营人员使用,需要支持批量查询,并且后置进行复杂计算,查询延迟敏感度较低。
备注:笔者没有选择将应用层湖表同步写入其它OLAP引擎中(如蚂蚁的Explorer引擎)进行分析与比较,仅针对OLAP场景下,使用Paimon直接查询的性能进行讨论,其它OLAP引擎的性能比较不在本篇文章的范畴。
3.1 消费方式考量
经内部调研,Paimon表的消费可通过内部数据研发平台的API接口进行查询,也可通过该平台的手动查询功能进行查询,这些查询本质都是生成flink批任务sql对表进行查询,因此既支持点查,也支持批查。
面对这样的消费方式,数据应用层设计则需要综合考量以下几点问题来进行:
- 稳定性:查询的稳定性是否足够匹配应用场景的保障等级?
- 性能:查询的并发承载力是否足够?
- 安全:查询是否会存在数据安全问题?
如果Paimon的消费无法满足诉求,则需要考虑将Paimon数据同步至其它OLAP引擎中使用。
3.1 数据架构方案选型
基于【消费方式的分析与讨论】,结合前述明细表、维度表、指标计算探索,无论是指标横表、指标竖表、维度宽表实际都可以直接进行查询消费,在业务口径可以允许后置计算的情况下,甚至可以尝试直接从明细表/基础维度表来进行消费。因此,整体可分为四种方案范式(如下图):
各方案在各维度上的对比如下表所示:
比较模块 | 影响因素 | 方案一 | 方案二 | 方案三 (括号里为不做中间层的情况) | 方案四 (括号里为不做中间层的情况) |
时效 | 数据时效 (数字越大,时效越新) | 5 | 4 | 3(4) | 2(5) |
查询速度 (数字越大,查询越快) | 1-?(数据量过大可能直接查不出来) | 2 | 3 | 5 | |
开发 | 开发复杂度 (数字越大,越容易) | 5 | 3 | 4(3) | 1(4) |
运维 | 问题排查难易度 (数字越大,越容易) | 4 (大量逻辑在报表里,可能不好排查问题) | 3 | 2(3) | 4(1) |
运维节点数量 (影响主备链路搭建、链路稳定性,数字越大,节点数量越多) | 1 | 2 | 4(3) | 5(2) | |
数据回刷/更新复杂度 (数字越大,越简单) | 1(etl逻辑主要维护在报表里,基本在明细层比较少涉及回刷) | 4 (指标竖表回刷+维度横表回刷) | 2(3) (一层维度+指标横表回刷) | 1(2) (两层横表都要回刷) | |
延伸价值 | 泛用性 (数字越大,越泛用) | 0 | 3 | 4 | 4 |
成本 | 资源消耗 (数字越大,越节省资源) | 2(后置计算的flink批任务资源消耗也不低,甚至直接跑不动) | 3 | 2 | 1 最高,所有指标均需前置计算好 |
3.3 方案应用与实验
经实验,方案一在广告流量场景不适用,无法及时完成明细汇总的后置计算,故不在此作比较。工程和报表消费Paimon表数据是通过API生成Flink批任务查询,因此,在方案落地应用部分,笔者针对方案二、三、四的表生成Flink批任务查询,重点实验对比查询性能和资源成本消耗。
指标竖表+维度宽表
在报表场景中,指标竖表常需要打横,以进行多个指标的展示,打横代码示意如下:
SELECT 对象id(唯一识别id),SUM(COALESCE(last_non_null_value, 0)) FILTER (WHERE metric_name = 'ad_expo_pv') AS ad_expo_pv,SUM(COALESCE(last_non_null_value, 0)) FILTER (WHERE metric_name = 'ad_clk_pv') AS ad_clk_pv,SUM(COALESCE(last_non_null_value, 0)) FILTER (WHERE metric_name = 'ad_conv_cnt_chg') AS ad_conv_cnt_chg,SUM(COALESCE(last_non_null_value, 0)) FILTER (WHERE metric_name = 'ad_cost_amt') AS ad_cost_amtFROM 表名WHERE metric_name IN ('ad_expo_pv', 'ad_conv_cnt_chg', 'ad_cost_amt', 'ad_clk_pv')and stat_time = '查询日期'and group_id = 'xxxxx' --指定要查询的对象GROUP BY 对象id(唯一识别id)
指标横表+维度宽表
指标横表通过Paimon的partial update机制建成,指标会被提前打横,一行包含多个指标,可同时满足报表/工程查询的多样诉求。
【指标+维度】宽表
构建大宽表同时将汇总指标和维度值维护在同一张表中,前置通过flink任务和Paimon的partial update机制,往一张大宽表同步数据。
需要注意的是,维度值由于涉及大量状态数据(有大量状态在一次更新后就不会再更新下发),所以还需要每天使用批任务更新初始化当天的维度值。
查询时效与成本对比
方案 | 点查速度 (主键) | 点查速度 (非主键) | 批查速度 | 消费复杂度 | 资源成本/开发成本 |
方案二 指标竖表+维度宽表 | 1秒+ | 2秒+ | 6+秒 |
| 最低 |
方案三 指标横表+维度宽表 | 1秒+ | 2秒+ | 6+秒 |
| 中等,需将汇总指标打横 |
方案四 指标+维度大宽表 | 1秒内 | 1秒内 | 2+秒 | 查询最简单,一次查询可以把所有汇总指标和状态指标都带出来 | 最高,需将汇总指标打横,然后再将维度宽表和指标宽表使用partial-update拼接 |
3.4 广告智能投放诊断报表的全链路设计
综上,笔者在广告智能投放诊断场景,最终决定采取 汇总指标横表+维度宽表 方案提供数据报表服务,即指标宽表 和维度宽表通过后置计算方式进行join消费,所有聚合计算前置完成,此外,也可选地使用用指标中间表提供部分不需要打横的特殊指标(如账户余额)。
报表中,会汇集所有广告投放的诊断情况汇总以及对应的诊断明细,报表分钟级延迟更新,全链路最高延迟<=5分钟。
第四章 湖表运维方案
以上几章集中阐述了湖表的开发与应用方案,准实时场景下的运维也非常重要,事关数据的质量与稳定,本章将针对这部分进行一定补充。
4.1 湖表数据回刷/更新
首先,建议非当日分区的数据回刷,都直接使用flink批任务处理
【涉及aggregation字段的回刷】:
aggregation是将聚合状态存储在表中累加,除了“last_value"/"last_non_null_value" 指标回刷/更新外,其余类型指标均不能直接重置任务来进行指标更新/回刷,否则会重复累加计算。若要进行数据回刷/更新,需要按照以下步骤进行⬇️
情况1:回刷时间在上游表的snapshot留存时间范围内
- 若涉及回刷最新时间分区,需先暂停当前写入任务
- 清空分区
- 重置任务为回刷起始位点
情况2: 上游snapshot已经过期,无法支持实时回刷
- 按情况1的方案,回刷最近分区
- 更久以前的数据回刷,使用批任务,指定上游分区数据,写入下游
【不涉及aggregation字段的回刷】
非aggregation字段其实默认是用Paimon的“last_value"/"last_non_null_value" 聚合参数,任务可直接进行重启回刷,指标将按最新值进行更新。
4.2 主备链路
- Flink任务:在两个高保集群构造主备任务
- Paimon表:在不同的dfs挂载点建主备表
4.3 数据准确性核对
- 与离线链路核对:若有离线数据链路核对,则可创建Paimon外部表,查询两表同一统计周期的指标进行批量核对ODPS任务读Paimon实验
- Paimon表DQC核对:可配置flink批任务,定期查询Paimon表进行DQC查验
4.4 表/任务监控
需要关注以下三点:
- Flink任务监控:数据加工任务+压缩任务
- Paimon表小文件数:小文件数过多,会导致底层存储引擎dfs出问题
- 分桶数(动态分桶):分桶数过多时,会影响表消费的查询速度,尤其是批读消费(涉及报表或产品消费)
第五章 准实时、实时、离线方案成本对比
5.1 对比思路
笔者所选择的广告投放诊断场景,同时具备离线和实时数据链路,现已针对该场景使用Paimon+Flink开发了对应准实时链路,前述重点探究了准实时链路的研发技术方案,如果能从成本角度对比 实时、离线、准实时的差异,将会更全面的评估准实时链路的价值。
当前实时的计算/存储引擎成本计费需要考量写入消耗+静态存储消耗+读取消耗,而本探索项目中,准实时链路并未完全对业务使用,读取消耗难以对齐评估,因此,笔者选择从预算提报的角度对成本资源进行评估(这也符合“打算使用数据湖开发,需要评估并申请预算”的常见场景逻辑)。
预算提报角度下,实时引擎只考虑 写入消耗 和静态存储,能与准实时链路对齐比较。如下图示,我们结合各引擎的资源消耗,按照各引擎的预算评估方式,将预算按照年初100%交付提报进行估算,并转换为每日成本金额,进而实现三种链路的成本统一度量。
5.2 实现路径
具体实现方面,笔者与Flink、Lindorm、SLS、ODPS、Paimon这几个引擎对应的开发团队、DRE、财务沟通对齐明确对应的成本估算逻辑,计算出引擎按预算提报的单价,并拉齐该业务场景下的三条链路的任务、表资源,进行多种维度比较和分析。
5.3 成本对比结果
具体成本数值不便在本文中公开透露,基于此,我们将下述比较中最少成本的设为基准值1,其他成本情况将以该基准值进行相对赋值(括号中标识)。
- 总成本:离线(1)<准实时(2)<实时(5)
- 明细层:准实时(1)<<离线(3)<实时(10)
- 指标计算:准实时比实时节约近64%成本,流量越大的任务节约越多
- 存储引擎:准实时(1)<离线(10)<<实时(200)
总体而言,实时数据服务(秒级延迟)与离线数据服务(T+1天延迟)相比,其提供的数据服务的成本是准实时数据服务(分钟级延迟)的六倍。准实时数据服务能够提供更灵活的数据形式,如湖表相较于实时Lindorm可直接进行SQL查询、支持批量查询,便于排查问题和进行OLAP消费。准实时数据服务还提供更统一的消费方式,即湖表可以同时进行流读和批读,同时存储最新和历史数据,从而进一步节约了人力成本。
在企业内部应用场景中(如内部运营、产品、数据分析的数据查询分析),用户通常能够接受分钟级的延迟。基于Paimon的准实时方案在资源成本和研发复杂度方面相较于纯实时方案具有显著优势,特别是在ODS层、DWD层数据存储以及超大规模数据聚合计算场景中。
第六章 Paimon建表范式
本章为前文【第二章】的范式总结,可供读者后续在实际的湖表开发、方案选型时进行查阅。
6.1 明细表
6.2 维度表
下图中涉及的【分区写入方式】的具体方案,可查阅【第二章-2.2 维度表设计探索】中的具体内容
6.3 汇总表
图中涉及的指标横表、指标竖表的具体设计,可查阅【第二章-2.3.4 表设计】
第七章 回顾与展望
7.1 回顾
在探索初期,我们围绕“Why Paimon”,与多方进行沟通交流,明确应用场景与预期价值。
在探索中期,我们聚焦“Paimon关键特性”,与平台、引擎、业务团队密切合作,充分应用与试验。
在探索后期,我们关注“成本与质量”,结合研发经验、平台能力、多方调研,对湖表的运维和成本方面进行了一定补充。
我们从0到1建设了广告业务的数据湖资产,以更简单的方式(Paimon+Flink)和更低成本(1/6)构建了更统一(流+批)的数据链路,并在过程中充分探索了各类建表规范、性能考量、方案选型。
当然,在探索过程中也有众多遗憾,比如准实时链路开发的研发效率评估、稳定性的评估、广告算法样本链路的应用等等,这些都希望能够在后续的工作中有所探索~
7.2 展望
基于Paimon的数据湖技术应用的展望,笔者目前认为有以下几方面:
- 对比当前的离线、实时技术栈,基于paimon的准实时链路的研发效率、稳定性该如何进行有效衡量(当前只是定性直观对比);
- Paimon的表性能的可观测性做得还不够完善,如小文件数、join性能、压缩性能的监控指标透出;
- 已有的实时和离线资产,有多少可以用湖来代替?怎么进行评估和识别?对应的成本收益会有多少呢?
- 既然已经整理出湖表建表范式,那能否有进一步的“傻瓜式选择”,识别用户的业务诉求,自动生成对应的湖表乃至flink任务,不再需要用户了解Paimon的众多参数(尤其是数据量少的场景),甚至不再需要对Paimon表、flink任务进行常规运维。
- Paimon表在很多情况需要多任务写入,对应需要“评估分桶数”,这对用户来说是一个很重的操作,而且往往存在一定误差(比如用odps的数据量来预估Paimon的数据量,大致有20~30%的误差),如果能够实现“全面的动态分桶”,将有效降低Paimon表的研发门槛。
- 当以Paimon为代表的数据湖技术方案成熟后,面对一个新的数据需求,我们后续如何评判使用 离线/实时/准实时技术方案呢?有没有一个评判标准?最直观的评判因素是“时效性诉求”,但这不能作为唯一依据,因为有时候用户不一定能做出这个判断,秒级、分钟级、小时级 这几种时效差异的业务感知在没有完成开发前是比较很弱的。有时候用户只是拍脑袋地认为“时效越高越好”,实际可能小时级的数据产出就已经能满足业务诉求。
相关文章:
万字长文详解|蚂蚁数据湖深度探索与业务应用实践
作者介绍:黄超傑,蚂蚁数据智能部实时数据团队数据研发工程师,致力于数据湖技术在蚂蚁落地应用、蚂蚁广告实时数仓建设、蚂蚁数据成本治理等工作。 在开始之前 如果: 你想要提升数据时效,能够接受分钟级延迟你好奇准实…...
rk3568 以太网eth1 , 定制板 phy调试问题。
问题: 客户定制底板的 phy 网络不通。 解决逻辑: phy 问题, 就是 照着 公司底板对硬件就行,完全是硬件问题,不用改 软件。 原理图如下:...
React相关面试题
以下是150道React面试题及其详细回答,涵盖了React的基础知识、组件、状态管理、路由、性能优化等多个方面,每道题目都尽量详细且简单易懂: React基础概念类 1. 什么是React? React是一个用于构建用户界面的JavaScript库ÿ…...
IT工具 | node.js 进程管理工具 PM2 大升级!支持 Bun.js
P(rocess)M(anager)2 是一个 node.js 下的进程管理器,内置负载均衡,支持应用自动重启,常用于生产环境运行 node.js 应用,非常好用👍 🌼概述 2025-03-15日,PM2发布最新版本v6.0.5,这…...
php 高性能,高并发,有哪些框架,扩展,推荐一下,或者技术的实现有哪些
以下是针对PHP高性能、高并发场景的框架、扩展及技术实现推荐,结合最新技术趋势和行业实践进行总结: 一、高性能框架推荐 1. C扩展类框架 YAF (Yet Another Framework) 特点:由C语言编写,直接嵌入PHP内核,仅提供核心M…...
golang单机锁实现
1、锁的概念引入 首先,为什么需要锁? 在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量、数据结构、文件)等,若不引入合适的同步机制,会引发以下问题: 数据竞…...
面试中JVM常被问到的问题以及对应的答案
在面试中,关于JVM常被问到的问题以及对应的答案可能包括: 什么是JVM?它的作用是什么? 答:JVM是Java虚拟机的缩写,是Java程序运行的环境。它负责将Java源代码编译成字节码并运行在不同平台上。 请解释一下J…...
算法——广度优先搜索——跨步迷宫
原题链接 思路:找出最短路径,然后判断是否存在连续三个点是横纵坐标相等的,如果有就步数减1 但是有两个样例过不了 错误原因:在错误的测试案例中,最短路径可能有多条,而我刚好选了一条比较曲折的&#x…...
Python pyqt+flask做一个简单实用的自动排班系统
这是一个基于Flask和PyQt的排班系统,可以将Web界面嵌入到桌面应用程序中。 系统界面: 功能特点: - 读取员工信息和现有排班表 - 自动生成排班表 - 美观的Web界面 - 独立的桌面应用程序 整体架构: 系统采用前后端分离的架构…...
链表操作:分区与回文判断
目录 链表分区(Partition) 功能概述 代码实现 要点与难点 注意事项 链表回文判断(PalindromeList) 功能概述 代码实现 要点与难点 注意事项 总结 在链表相关的算法问题中,理解链表的基本结构和操作至关重要…...
基于大模型的腮腺多形性腺瘤全周期诊疗方案研究报告
目录 一、引言 1.1 研究背景与目的 1.2 研究现状与趋势 二、大模型预测原理与方法 2.1 大模型概述 2.2 数据收集与预处理 2.3 模型训练与优化 三、术前预测与评估 3.1 肿瘤特征预测 3.2 风险评估 3.3 案例分析 四、术中方案制定与实施 4.1 手术方案选择 4.2 面神…...
Vue3 界面设计插件 microi-pageengine 入门教程一
系列文章目录 一、Vue3空项目快速集成 microi-pageengine 插件 文章目录 系列文章目录一、前言二、排版布局2.1 功能导航区2.2 组件容器区2.3 属性面板区 三、数据来源配置3.1 json数据源3.2 html数据源 四、事件穿透五、数据保存持久化六、总结 一、前言 上一篇文章介绍了 v…...
如何选择合适的SSL服务器证书
在数字化时代,网络安全已成为企业不可忽视的重要环节。SSL(Secure Sockets Layer,安全套接层)服务器证书作为保障网站数据传输安全的关键工具,其选择和使用至关重要。 一、SSL证书类型:基础与进阶 SSL证书…...
STC89C52单片机学习——第26节: [11-2]蜂鸣器播放音乐
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.03.19 51单片机学习——第26节: [11-2]蜂鸣器播放音乐 前言开发板说明引用解答和科普一…...
ABC396题解
A 算法标签: 模拟 问题陈述 给你一个长度为 N N N 的整数序列: A ( A 1 , A 2 , … , A N ) A (A_1, A_2, \ldots, A_N) A(A1,A2,…,AN)。 请判断在 A A A 中是否有相同元素连续出现三次或三次以上的位置。 更正式地说,请判断是否存在一个整…...
如何用Python和Selenium实现表单的自动填充与提交?
在今天的数字化时代,自动化工具可以极大地提高工作效率。很多人可能会觉得填表单是个繁琐的任务,不过你知道吗?用Python和Selenium可以轻松解决这一问题!本文将带你走进如何利用这两个强大的工具,实现表单的自动填充和…...
使用`plot_heatmap`绘制热力图时
在Python中,使用plot_heatmap绘制热力图时,颜色图例(colorbar)的定制化设置是关键步骤。以下是实现方法及优化建议: 一、基础图例绘制 自动生成颜色条 使用seaborn.heatmap()时,默认会生成颜色条࿰…...
极简桌面待办清单软件,❌不会增加工作量
工作邮件、会议安排、生活琐事……事情多到根本记不住,又怕错过重要事项,焦虑感油然而生。相信这是很多职场朋友遇到的问题,也急需一款好用的极简桌面待办清单软件来辅助我们。 今天我要给大家推荐一款拯救我们于水火的神器——好用便签&…...
QT学习笔记4
一、音视频播放(Qt Multimedia) 1. 多媒体框架架构 核心类关系 : mermaid #mermaid-svg-mwHLYcpaJDU14uFM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mwHLYcpaJDU14uFM .e…...
软考 中级软件设计师 考点知识点笔记总结 day06
文章目录 6、树和二叉树6.1、树的基本概念6.2、二叉树的基本概念6.3、二叉树的遍历6.4、查找二叉树(二叉排序树)BST6.5、构造霍夫曼树6.6、线索二叉树6.7、 平衡二叉树 7、 图7.1 、 存储结构 - 邻接矩阵7.2 、 存储结构 - 邻接表7.3、图的遍历7.4、拓扑…...
爬虫基础之爬取猫眼Top100 可视化
网站: TOP100榜 - 猫眼电影 - 一网打尽好电影 本次案例所需用到的模块 requests (发送HTTP请求) pandas(数据处理和分析 保存数据) parsel(解析HTML数据) pyecharts(数据可视化图表) pymysql(连接和操作MySQL数据库) lxml(数据解析模块) 确定爬取的内容: 电影名称 电影主演…...
微信小程序面试内容整理-如何使用wx.request()进行网络请求
wx.request() 是微信小程序中用于发送网络请求的 API,类似于浏览器中的 fetch 或 XMLHttpRequest。通过 wx.request(),开发者可以向服务器发送 HTTP 请求,获取数据或者提交数据。 基本用法 wx.request() 通过提供一个配置对象来进行配置,配置对象中包括请求的 URL、请求方法…...
力扣100二刷——图论、回溯
第二次刷题不在idea写代码,而是直接在leetcode网站上写,“逼”自己掌握常用的函数。 标志掌握程度解释办法⭐Fully 完全掌握看到题目就有思路,编程也很流利⭐⭐Basically 基本掌握需要稍作思考,或者看到提示方法后能解答⭐⭐⭐Sl…...
该错误是由于`KuhnMunkres`类未定义`history`属性导致的
该错误是由于KuhnMunkres类未定义history属性导致的。以下是具体分析及解决方案: 错误原因分析 属性缺失 代码中试图访问km.history,但KuhnMunkres类未在初始化或算法执行过程中定义该属性,因此触发AttributeError。动画实现逻辑不完整 用户…...
DAPO:一个开源的大规模大型语言模型LLM强化学习系统
推断扩展赋予了大型语言模型前所未有的推理能力,强化学习作为激发复杂推理的核心技术,清华大学联合字节提出了解耦片段与动态采样策略优化(DAPO)算法,并全面开源了一个最先进的大规模强化学习系统,该系统使用Qwen2.5-32B基础模型在AIME 2024上取得了50分的高分。还开源了…...
数据结构中的引用管理对象体系
数据结构中的引用管理对象体系 (注:似复刻变量即实例对象) 引用管理对象的,有引用就能管理到它所指向的对象,我们拿引用最终的目的就是管理那些我们需要管理的最终直接对象,引用也是对象,同时…...
【自学笔记】智能合约基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 智能合约基础知识点总览目录1. 智能合约简介2. 以太坊与Solidity示例代码:Hello World智能合约 3. Solidity基础语法示例代码:简单的计数器合…...
Linux的Shell编程
一、什么是Shell 1、为什么要学习Shell Linux运维工程师在进行服务器集群管理时,需要编写Shell程序来进行服务器管理。 对于JavaEE和Python程序员来说,工作的需要。Boss会要求你编写一些Shell脚本进行程序或者是服务器的维护,比如编写一个…...
【八股文】volatile关键字的底层原理是什么
volatile只能保证可见性和有序性 volatile如何保证可见性 当对volatile变量进行写操作的时候,JVM会向处理器发送一条lock前缀的命令,将这个缓存中的变量会写到系统内存中。 所以,如果一个变量被volatile所修饰,每次数据变化之后…...
一种基于大规模语言模型LLM的数据分析洞察生成方法
从复杂数据库中提取洞察对数据驱动决策至关重要,但传统手动生成洞察的方式耗时耗力,现有自动化数据分析方法生成的洞察不如人工生成的有洞察力,且存在适用场景受限等问题。下文将介绍一种新的方法,通过生成高层次问题和子问题,并使用SQL查询和LLM总结生成多表数据库中的见…...
三大供应链管理模式——解决方案与实操案例详解
库存压到喘不过气,紧急订单却总赶不上交付?这是许多企业的真实困境——推式供应链盲目备货导致积压,拉式供应链又因响应慢而丢单。供应链管理,本质上是在“计划”与“变化”之间把握平衡。上篇文章,我们系统梳理了供应…...
【C++】STL库面试常问点
STL库 什么是STL库 C标准模板库(Standard Template Libiary)基于泛型编程(模板),实现常见的数据结构和算法,提升代码的复用性和效率。 STL库有哪些组件 STL库由以下组件构成: ● 容器…...
集成学习之随机森林
目录 一、集成学习的含义 二、集成学习的代表 三、集成学习的应用 1、分类问题集成。(基学习器是分类模型) 2、回归问题集成。(基学习器是回归模型) 3、特征选取集成。 四、Bagging之随机森林 1、随机森林是有多个决策树&a…...
【SpringBatch】03步骤对象 (Step)控制与执行流程设计
目录标题 六、步骤对象 Step6.1 步骤介绍6.2 简单Tasklet6.3 chunk居于块Tasklet**ChunkTasklet 泛型** 6.4 步骤监听器6.5 多步骤执行6.6 步骤控制6.6.1 条件分支控制-使用默认返回状态6.6.2 条件分支控制-使用自定义状态值 6.7 步骤状态6.8 流式步骤 六、步骤对象 Step 前面…...
走进Java:String字符串的基本使用
❀❀❀ 大佬求个关注吧~祝您开心每一天 ❀❀❀ 目录 一、什么是String 二、如何定义一个String 1. 用双引号定义 2. 通过构造函数定义 三、String中的一些常用方法 1 字符串比较 1.1 字符串使用 1.2 字符串使用equals() 1.3 使用 equalsIgnoreCase() 1.4 cpmpareTo…...
AI如何变革亚马逊广告投放?DeepBI的智能优化解析
在亚马逊平台上,广告投放的方式正经历着从人工精细化运营到AI自动化优化的深刻变革。传统的广告投放依赖人工操作,涉及海量数据分析、手动调价、竞价策略优化等环节,既耗时又易受人为因素影响。而AI驱动的智能投放系统,如DeepBI&a…...
小程序电子画册制作,用户体验为王!
家人们,现在小程序电子画册超火,不管是展示产品还是分享故事都超实用。但在小程序电子画册制作过程中,用户体验真的得好好考量!今天就和大家唠唠这其中的门道。 1、加载速度:快才是王道 打开小程序的瞬间,…...
【商城实战(49)】解锁小程序端适配与优化,让商城飞起来
【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配…...
英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析
【完整版】3月19日,黄仁勋Nvidia GTC 2025 主题演讲|英伟达 英伟达GTC 2025大会产品全景剖析与未来路线深度洞察分析 一、引言 1.1 分析内容 本研究主要采用了文献研究法、数据分析以及专家观点引用相结合的方法。在文献研究方面,广泛收集了…...
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用
《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用 《TCP/IP网络编程》学习笔记 | Chapter 19:Windows 平台下线程的使用内核对象内核对象的定义内核对象归操作系统所有 基于 Windows 的线程创建进程与线程的关系Windows 中线程的创建方…...
线性规划的基本解、基本可行解和可行解
在线性规划中,基本解、基本可行解和可行解是非常重要的概念,特别是在使用单纯形法求解时。下面详细解释这些概念,并说明如何计算它们。 1. 线性规划问题的标准形式 线性规划的标准形式为: 其中: A 是 mn 的矩阵&…...
【AVRCP】服务发现互操作性:CT 与 TG 的 SDP 协议契约解析
目录 一、服务发现的核心目标:能力画像对齐 二、控制器(CT)服务记录:控制能力的声明 2.1 必选字段:角色与协议的刚性契约 2.1.1 服务类标识(Service Class ID List) 2.1.2 协议描述列表&am…...
[从零开始学习JAVA] Stream流
前言: 本文我们将学习Stream流,他就像流水线一样,可以对我们要处理的对象进行逐步处理,最终达到我们想要的效果,是JAVA中的一大好帮手,值得我们了解和掌握。(通常和lambda 匿名内部类 方法引用相…...
K8S学习之基础三十八:Kube-static-metrics监控
Kube-static-metrics监控 kube-static-metrics组件可以通过监听apiserver生成有关资源对象的状态指标,比如Node、Pod,需要注意的是kube-state-metrics只是简单的提供一个metrics数据,并不会存储这些指标数据,所以可以使用Prom…...
JAVA-多线程join()等待一个线程
引言:更多线程的认识可以看一篇博客: JAVA-Thread类实现多线程-CSDN博客 一、join()的作用 我们知道线程是随机调度执行的,但是有时候我们需要另一个任务完成了,我们才能继续,这个时候我们就可以使用join去等待线程结束…...
HashMap 常用方法
HashMap 常用方法 方法作用示例put(K key, V value)添加键值对map.put("apple", 10);get(Object key)获取指定键的值map.get("apple"); → 10remove(Object key)删除指定键的键值对map.remove("orange");containsKey(Object key)检查是否包含指…...
LogicFlow介绍
LogicFlow介绍 LogicFlow是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批流配置、机器人逻辑编排、无代码平…...
Docker搭建MySQL主从服务器
一、在主机上创建MySQL配置文件——my.cnf master服务器配置文件路径:/data/docker/containers/mysql-cluster-master/conf.d/my.cnf slave服务器配置文件路径: /data/docker/containers/mysql-cluster-master/conf.d/my.cnf master服务配置文件内容 …...
计算机二级web易错点(4)-选择题
选项 A:<input type"radio"> 用于创建单选按钮,同一组单选按钮中只能选择一个选项,所以该选项不符合要求。选项 B:HTML 中没有 type"check" 这种类型,是错误的写法,不能产生复选…...
3.19学习总结
学习了Java中的面向对象的知识点 完成一道算法题,找树左下角的值,错误的以为左下角只能是最底层的左节点,但指的是最底层最左边的节点...