随着业务不断横向扩张与数据纵向的不断增多,编写基于事务数据库的跨库跨服务程序解决统计、报表、内容搜索等,越来越麻烦且时效性差。我们需要一个数仓聚合数据解决这些问题,并期望带来如智能推荐、即席报表等业务能力的提升。总体目标包括:
实时的统计数据
目前平台统计功能面临一些痛点,包括:实时统计准确性无法保障,不能分布式计算充分利用资源,统计执行用时过长,数据过于分散难于汇总计算等。可以利用大数据分析技术解决这些问题。
内容安全检查防范法规风险
发表内容进行涉及违法网络法规的审查,或者根据需求进行查重查新等检查。
丰富的内容搜索服务
利用聚合的大数据处理关系数据,使得内容可以通过搜索引擎根据更多维度进行检索。
基于用户画像的内容推荐服务
通过用户行为分析,获得用户动态的标签,与静态标签组成用户画像,提供更为精准智能的内容推荐服务。
实时的热门内容呈现
实时记录用户对内容的访问频率,计算用户们当前最感兴趣的热门内容进行呈现。
用户行为记录与审计日志
全面记录用户的访问操作行为,与业务系统解耦合,记录可检索审计日志。
BI提供决策信息
在我们学习众多知识和积累大量经验之后,未来的我们可能利用智能算法进行数据挖掘,使得公司获得更多业务,带来更多盈利。
采用实时数仓Kappa架构,基于Flink进行分析计算。同时为了保障数据溯源能力、数据校验能力以及即席查询能力将各层数据输出到Clickhouse中。
经过设计开发,目前所有flink job和所获得数据如下:
元数据是关于数据的数据。 其用途包括记录数据仓库中模型的定义、各层级间的映射关系、监控数据仓库中数据的状态、监控 ETL 的任务的运行状态,以及帮助数据仓库管理员和开发人员理解数据关系,指导其进行数据管理和开发。甚至程序可以通过元数据规则自动执行各种模型的ETL任务。
对于我们从大数据入门开始,就考虑元数据问题是否不靠谱?由于我们是进行大数据的补课,二十多个子业务系统百多张业务表,业务还会不断扩张,再加上各种埋点数据,在数据分层的情况下要编写ODS、DIM、DWD、DWS和各种ADS的数据处理计算。然而这些很可能一次做不好,数据的建模可能要反复调整,随时增加新的维度。要完成所有工作不知到猴年马月了。如果实现一套基于元数据规则的自动化ETL程序, 将没一层数据处理逻辑抽象出来,根据配置适用于各种不同的业务模型,大量节省了重复的体力工作,起到减少开发周期节省人力成本的作用。
当然元数据管理是整个大数据体系中重要的一环,不过实现整套元数据管理体系也需要付出大量工作。因此考虑轻重缓急,当前先设计开发出一套仅包括“规则文件 + 解析组件”的元数据方案,优先解决规则数据处理能力。 下图为对整体元数据管理系统的设想,目前绿色图形为实现部分。
基于元数据规则的数据处理基于元数据规则进行数据处理是当前首要需求。元数据配置对于数据处理的关系大体如下图所示(有其注意红字)。下图简述了依靠元数据配置,将ODS原始操作数据处理成星型模型的宽表DIM维度数据。
根据教师与学校的关系,处理教师数据时,自动获取关系学校数据,将学校名称冗余赋值给教师的维度数据。
元数据模型有各数据分层配置,模型字段也有各数据分层字段配置。
某模型具备某层配置,表示该模型产生该层数据。
某字段具备某层配置,表示该模型产生该层数据包含该字段。
整体项目实现了实时统计、分析报表、热门数据、智能推送、多维搜索能力、用户行为(审计)日志的大数据解决方案。
并且依据数据仓库理论分层处理数据。包括ODS原始操作数据层、DIM维度数据层、DWD数据明细层、DWS数据汇总层、各类的ADS数据应用层数据。
为了自动化通用处理数据,各层数据分别抽象成为OperationData操作数据、DimensionData维度数据、FactData事实数据、SummaryData汇总数据、ServiceData应用服务数据等对象。
各层数据类型具体如下图。
在处理程序中,所有数据类型都继承ProcessedData类(抽象的处理数据类)。其中包含model模型属性,程序正是通过此字段根据所属模型元数据配置进行处理计算。
key属性是标识数据唯一性的键,支持联合主键形式。除了OperationData操作数据中是通过before和after属性记录变动的数据内容外,其他层数据是通过data属性记录数据内容。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | model | 模型名称 |
| Map | key | 主键值 |
| Long | ts | 时间戳(操作事件发生时间) |
| Map | data | 数据 |
| String | op | 操作:create/update/delete/... |
| String | operator | 操作者:[userId]/system/anonymity/... |
| 类型 | 字段 | 说明 |
|---|---|---|
| Map | before | 操作前数据 |
| Map | after | 操作后数据 |
| String | dirtyData | 脏数据 该属性非空表示当前数据为错误数据,且记录错误信息。 |
| Integer | retryTimes | 重试次数 对于可以重试出错数据重试次数的记录,进行有次数限度的重试。 |
| Boolean | existed | 数据是否存在,用于幂等判断 |
| Map | relationMap | 维度关系数据 |
| 类型 | 字段 | 说明 |
|---|---|---|
| String | relationName | 统计数据来源的关系名称 |
| ProcessedKey | summaryProcessedKey | 汇总的目标数据模型与主键值 如果教师创建事实数据,为学校的教师数+1,此属性为学校模型与学校编号 |
| 类型 | 字段 | 说明 |
|---|---|---|
| Double | score | 热门计算的分数 |
配置文件中,命名为xxxscript的属性一般为基于Groovy语言的规则脚本,用于自定义稍微复杂的数据处理规则。
编写groovy脚本内容基本为填充对应函数的一部分,如:某字段赋值函数valuescript为getDefaultDeleted(op),生成完整计算函数脚本则是下方内容。
Object metadata_model_xxx_field_xxx_value(OperationData od) {
String model = od.model;
String op = od.op;
Map key = od.key;
Map before = od.before;
Map after = od.after;
Long ts = od.ts;
// 填写脚本开始
getDefaultDeleted(op)
// 填写脚本结束
}
在赋值类型的脚本中,如果当前字段在某些条件下不需要赋值,可以返回常量DO_NOT_VALUE,处理后的数据将不包含此字段。
程序当前提供的脚本公用静态方法
| 返回类型 | 方法 | 说明 |
|---|---|---|
| String | convertHtmlToText(String html) | 转换HTML为内容文本(通过Jsoup) |
| String | convertIdentityCardToAgeTag(String identityCard) | 根据身份证获取年代标签(80后/90后/...) |
| String | convertPathToLast(String path) | 获取路径数据的最后节点的值 如:/aa/bb/cc/返回cc |
| Object | getDefaultDeleted(String op) | 根据操作类型获取默认的是否删除的值 |
| Object | getDefaultModelCount(String op) | 根据操作类型获取默认的当前主题域增量的值 如:教师创建操作返回教师数为1,删除操作返回教师数为-1,其他操作不赋值教师数 |
| List | splitTextToList(String text) | 拆分字符串为数组 根据"[,|.|;|,|。|;|\s]+"正则 |
同时程序中还提供了“赋值模式”和“统计模式”用于在脚本中调用系统中资源或复杂逻辑。
数据处理流程而整体的数据处理流程如下:
整套大数据项目的元数据规则形成一个JSON文件。文件配置到nacos中,被各个计算工具获取和解析。 文件为一个Namespace(命名空间)对象的JSON。组织结构如下图。
Namespace是一个项目所有模型的容器,配置文件的核心为Model对象集合,定义一个个数据模型。模型中Field集合描述数据的各字段,Operation集合描述数据的各种操作,Relation
集合描述模型与其他模型的级联关系。而模型的LayerMap和字段的FieldLayerMap中定义了数据在各个分层中的处理参数。
Namespace(命名空间)是一个项目的所有模型的容器。对于一个大数据项目将会涉及多个子业务,所有业务的将都包括在一个Namespace中。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | basescript | 基础脚本(配置通用方法) |
| List | models | 模型集合 |
| List | modules | 模块集合 |
Module(模块)是子业务的定义,其作用为将模型分组,方便管理。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | comment | 描述 |
| String | name | 名称 |
Model(模型)是元数据配置的核心,在此应当属于主题域的概念.一个Model对象其中包括不同分层的数据模型设定。 如定义一个模型用户,其中设置有字段姓名、登录时间, 其中姓名
字段在维度数据和应用数据中包括,登录时间在用户的登录操作的事实数据中包括。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | module | 模块 |
| String | name | 名称 |
| String | comment | 描述 |
| List | fields | 字段集合 |
| LayerMap | layers | 各层配置 |
| List | operations | 操作集合 |
| List | relations | 关系集合 |
模型的字段集合将包括该主题域中涉及的全部字段,通过layers配置其在哪些层使用。
字段对象包括关于dim、dwd层赋值的系列配置属性,关于dws层统计与合并统计的系列配置属性。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | comment | 描述 |
| String | dataType | 数据类型 |
| Boolean | dimension | 是否维度(作用仅为参考标记,一般情况会与某模型有关系) |
| Boolean | measure | 是否度量(作用仅为参考标记,一般情况会与某模型有关系) |
| String | name | 名称 |
| Boolean | pk | 主键 |
| String | statMergePattern | 统计合并模式(调用复杂模式方法进行统计合并。 |
| String | statMergescript | 统计合并脚本 |
| String | statPattern | 统计模式(调用复杂模式方法进行统计。 |
| String | statRelationFieldName | 统计关系字段 |
| String | statRelationModelName | 统计模型(此属性有值即为根据统计模型的操作统计对此字段,一般用于DWS层度量值汇总,也可以用于DIM层一对一关系推送赋值) |
| String | statRelationName | 统计关系 |
| String | statscript | 统计脚本 |
| String | valuePattern | 赋值模式(调用复杂模式方法进行赋值。 |
| String | valueRelationFieldName | 赋值关系字段 |
| String | valueRelationName | 赋值关系 |
| String | valuescript | 赋值脚本 |
| FieldLayerMap | layers | 各层配置 |
不同数据类型涉及的序列化和运算关系不同,是针对程序逻辑指定的一套数据类型。
| 类型值 | 描述 |
| uuid | |
| text_name | |
| text_title | |
| text_summary | |
| boolean | |
| long | |
| decimal | |
| int | |
| time | |
| date | |
| content_text | |
| content_path | |
| content_url | |
| object | |
| array_object | |
| array_text | |
| weight_tags | 具备权重的标签集合类型 |
模型与模型的依赖关系配置记录在外键模型对象的关系集合中。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | name | 名称(别名) |
| String | source | 关系源 模型名称 主键方/维表 |
| String | sourcePolymorphism | 关系源多态性字段 关系源可以动态为多种模型,关系目标target的一个字段存储着源是那种模型。 习惯性使用mainBodyType字段记录源模型,mainBodyId字段为外键。 sourcePolymorphism即可赋值为mainBodyType的值。如果不是多态性关系,此字段赋值null |
| List | keys | 主外键关系集合 有联合主键的可能,所以使用集合 |
属性keys是RelationKey关系主外键对应的集合。因为可能是联合键值关系,所以是集合
| 类型 | 字段 | 说明 |
|---|---|---|
| String | primary | 主键 |
| String | foreign | 外键 |
| String | foreignValue | 外键值,用于如教师模型民族字段对应字典条目表字典为民族的数据,确定某关系的主键为确定值 |
模型的layers属性映射各数据层配置,具备某层配置意味着当前模型代表的主题域具备该层数据模型。
| 类型 | 字段 | 说明 |
|---|---|---|
| AdsOlapLayer | adsOlap | ADS层存储OLAP |
| AdsSearchLayer | adsSearch | ADS层搜索引擎层(ES) |
| AdsTopLayer | adsTop | ADS层热门内容 |
| DimLayer | dim | DIM层 |
| DimOlapLayer | dimOlap | DIM层存储OLAP |
| DwdLayer | dwd | DWD层 |
| DwdOlapLayer | dwdOlap | DWD层存储OLAP |
| DwsLayer | dws | DWS层 |
| OdsDatabaseLayer | odsDatabase | ODS层业务数据库CDC |
| OdsEventLayer | odsEvent | ODS层业务埋点事件 |
| 类型 | 字段 | 说明 |
|---|---|---|
| Boolean | tableExistent | 表已存在 |
| String | tableName | 表名 |
| 类型 | 字段 | 说明 |
|---|---|---|
| List | tables | 表集合 |
tables 配置
| 类型 | 字段 | 说明 |
|---|---|---|
| String | db | 数据库 |
| String | name | 表名称 |
| String | source | 数据源(mysql) |
| 类型 | 字段 | 说明 |
|---|---|---|
| String | keyscript | 键的脚本,可以动态生成hbase的RowKey,还可以用于hash分区 |
| 类型 | 字段 | 说明 |
|---|---|---|
| String | clusterName | 分区名称 |
| String | engine | 表引擎 |
| String | orderBy | 排序 |
| String | partitionBy | 数据分区 |
| String | primaryKey | 主键 |
| 类型 | 字段 | 说明 |
|---|---|---|
| String | keyscript | 键的脚本,可以动态生成hbase的RowKey,还可以用于hash分区 |
| String | analyzer | |
| Integer | numberOfReplicas | |
| Integer | numberOfShards | |
| String | refreshInterval | |
| String | translogDurability | |
| String | translogSyncInterval |
| 类型 | 字段 | 说明 |
|---|---|---|
| List | topics |
topics 配置
| 类型 | 字段 | 说明 |
|---|---|---|
| String | name | 主题名称 |
| String | scoreField | 分数字段,根据此字段赋值分数 |
| String | scorescript | 分数脚本,根据此脚本赋值分数 |
| 类型 | 字段 | 说明 |
|---|---|---|
| OlapFieldLayer | adsOlap | ADS_OLAP层字段 |
| SearchFieldLayer | adsSearch | ADS搜索引擎层字段 |
| FieldLayer | adsTop | ADS热门内容层字段 |
| BytesFieldLayer | dim | DIM层字段 |
| OlapFieldLayer | dimOlap | DIM_OLAP层字段 |
| BytesFieldLayer | dwd | DWD层字段 |
| OlapFieldLayer | dwdOlap | DWD_OLAP层字段 |
| BytesFieldLayer | dws | DWS层字段 |
| JsonFieldLayer | odsDatabase | ODS层业务数据库CDC字段 |
| JsonFieldLayer | odsEvent | ODS层业务埋点事件字段 |
| 类型 | 字段 | 说明 |
|---|---|---|
| String | columnName | 名称 |
| String | dataType | 数据类型 |
| Boolean | nn | 是否不允许为null |
| Integer | position | 位置 |
| String | valueDefault | 赋值默认值 |
| 类型 | 字段 | 说明 |
|---|---|---|
| Boolean | index | 是否索引 |
主题域的不同操作可以产生不同的数据。
如:创建教师,将产生教师数+1的事实数据。删除教师,将产生教师数-1的事实数据。
同时操作也可以根据数据内容的不同进行转换。如:deleted字段由0变成1,代表当前是各删除操作。
| 类型 | 字段 | 说明 |
|---|---|---|
| String | name | 操作名称 |
| Boolean | needLoadingDim | 是否加载DIM数据。 |
| String | operatorField | 操作者字段,根据此字段赋值操作者 |
| String | operatorscript | 操作者脚本,根据此脚本赋值操作者 |
| String | opscript | 操作转换脚本 (如:op == "update" && before.published != after.published ? "published" : op) |
| String | origin | 操作来源 |
| String | tsField | 操作时间戳字段,根据此字段赋值操作者 |
| String | tsscript | 操作时间戳脚本,根据此脚本赋值操作者 |
ODS层数据在进行初始的ETL处理时,由于数据可能来源于CDC,数据的操作类型可能需要处理。
如业务数据库标记删除是修改deleted字段,CDC产生的ODS数据是update操作。需要修改成delete操作。
可以通过模型的Operation配置中opscript设置脚本,修改当前的ODS层操作数据的op操作类型为其他值。
操作数据和事实数据需要operator操作者和ts操作时间戳属性,用于幂等验证和后续应用数据计算。
Operation配置中operatorField和tsField属性分别指定这两项信息所对应的字段。
而Operation配置中operatorscript和tsscript属性分别可以通过设置groovy脚本获取两项信息的值。
opscript、operatorscript、tsscript属性的groovy脚本基本都是组织OperationData操作数据类型参数返回结果值的方法进行处理。
脚本形成的完整处理方法如下:
Object metadata_model_xxx_operation_xxx_xxx(OperationData od) {
String model = od.model;
String op = od.op;
Map key = od.key;
Map before = od.before;
Map after = od.after;
Long ts = od.ts;
// 填充`opscript`、`operatorscript`、`tsscript`内容
}
举例,博文业务数据库删除为标记删除,需要在涉及删除字段的update操作时处理为delete操作。
如此,之后的处理事实数据时,博文数度量值为-1。
并且在删除时需要指定创建者字段为操作者。配置如下:
{
"models": [
{
"comment": "博文",
"fields": [
{
"comment": "博文编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"dwd": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "标题",
"dataType": "text_title",
"layers": {
"odsDatabase": {
"columnName": "title",
"dataType": "varchar(200)"
}
},
"name": "title"
},
{
"comment": "博文栏目",
"dataType": "uuid",
"dimension": true,
"layers": {
"dwd": {
"columnName": "cateId"
},
"odsDatabase": {
"columnName": "cateId",
"dataType": "varchar(32)"
}
},
"name": "cateId"
},
{
"comment": "博客数",
"dataType": "long",
"layers": {
"dwd": {
"columnName": "blogCount"
}
},
"measure": true,
"name": "blogCount",
"// 博文事实数据提供度量值,操作'create'为1,操作'delete'为-1": "#######################",
"valuescript": "getDefaultModelCount(op)"
}
],
"layers": {
"dwd": {
"tableName": "dwd_blog"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blog",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blog",
"operations": [
{
"name": "create",
"operatorField": "creator",
"origin": "database",
"tsField": "createdTime"
},
{
"name": "update",
"// 操作'update'时,删除字段变成true,改为'delete'操作": "#######################",
"opscript": "after.deleted == true && before.deleted != after.deleted ? 'delete' : op",
"operatorField": "updater",
"origin": "database",
"tsField": "updatedTime"
},
{
"name": "delete",
"origin": "database",
"// 指定操作者和操作时间字段,不同操作对应的字段可能不一样": "#######################",
"operatorField": "creator",
"tsField": "updatedTime"
}
]
}
],
"modules": [
{
"name": "blog"
}
]
}
赋值处理 (ODS层到DIM与DWD数据层)
本文中赋值处理特指ODS层数据处理成DIM层数据和DWD层数据需要转换处理操作。
赋值操作可以根据元数据配置将维度关系模型的字段数据赋值。
字段配置的valueRelationName属性指定对应赋值维度模型的关系,
valueRelationFieldName属性指定对应赋值的字段,指定对应的模型和字段必须是DIM层数据。
举例,博文模型后续应用数据需要冗余的分类名称,现在处理博文DIM数据要包含栏目名称,但是来自ODS的数据不包含此字段。需要根据从博文栏目模型的维度数据中获取赋值。配置如下:
{
"models": [
{
"comment": "博文栏目",
"fields": [
{
"comment": "编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"dim": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "名称",
"dataType": "text_name",
"layers": {
"dim": {
"columnName": "name"
},
"odsDatabase": {
"columnName": "name",
"dataType": "varchar(50)"
}
},
"name": "name"
}
],
"layers": {
"dim": {
"tableName": "dim_blog_cate"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blogCate",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blogCate",
"operations": [
{
"name": "create",
"origin": "database"
}
]
},
{
"comment": "博文",
"fields": [
{
"comment": "博文编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"adsSearch": {
"columnName": "id"
},
"dim": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "标题",
"dataType": "text_title",
"layers": {
"adsSearch": {
"columnName": "title"
},
"dim": {
"columnName": "title"
},
"odsDatabase": {
"columnName": "title",
"dataType": "varchar(200)"
}
},
"name": "title"
},
{
"comment": "博文内容",
"dataType": "content_path",
"layers": {
"dim": {
"columnName": "content"
},
"odsDatabase": {
"columnName": "content",
"dataType": "varchar(1000)"
}
},
"name": "content"
},
{
"comment": "博文栏目",
"dataType": "uuid",
"dimension": true,
"layers": {
"adsSearch": {
"columnName": "cateId"
},
"dim": {
"columnName": "cateId"
},
"odsDatabase": {
"columnName": "cateId",
"dataType": "varchar(32)"
}
},
"name": "cateId"
},
{
"comment": "博文栏目文本",
"dataType": "text_name",
"layers": {
"adsSearch": {
"columnName": "cateName"
},
"dim": {
"columnName": "cateName"
},
"// ODS层没有提供此字段数据": "#######################"
},
"name": "cateName",
"// 赋值配置开始": "#######################",
"valueRelationFieldName": "name",
"valueRelationName": "blogCate",
"// 赋值配置结束": "#######################"
}
],
"layers": {
"adsSearch": {
"tableName": "ads_blog"
},
"dim": {
"tableName": "dim_blog"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blog",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blog",
"operations": [
{
"name": "create",
"origin": "database"
}
],
"// 维度关系开始": "#######################",
"relations": [
{
"keys": [
{
"foreign": "cateId",
"primary": "id"
}
],
"name": "blogCate",
"source": "blogCate"
}
],
"// 维度关系结束": "#######################"
}
],
"modules": [
{
"name": "blog"
}
]
}
赋值处理可以使用脚本进行复杂且灵活的赋值。
通过字段的valuescript属性设置groovy脚本表达式,用来补全根据OperationData操作数据类型参数返回字段值的方法。
同时还可以使用valuePattern属性指定一种“赋值模式”,指定“赋值模式”为方法增加ValuePattern类型参数。
“赋值模式”目前包括:
HttpPattern: 具备HttpClient可以进行Http请求的赋值模式KeywordsPattern: 具备hanlp分词组件可以提取内容关键词的赋值模式
赋值处理脚本形成的完整方法如下:
Object metadata_model_xxx_field_xxx_value_pattern_xxx(OperationData od, ValuePattern pattern) {
String model = od.model;
String op = od.op;
Map key = od.key;
Map before = od.before;
Map after = od.after;
Long ts = od.ts;
// valuescript内容将会填充在此
}
返回常量DO_NOT_VALUE表示最后逻辑不需要赋值。
举例,博文模型操作层数据内容字段是html文件的url,搜索应用需要全文检索,需要通过http获取html内容,然后转为纯文本内容。
如果是修改博文,但内容url没有变化,不需要重复提取。配置如下:
{
"models": [
{
"comment": "博文",
"fields": [
{
"comment": "博文编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"adsSearch": {
"columnName": "id"
},
"dim": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "标题",
"dataType": "text_title",
"layers": {
"adsSearch": {
"columnName": "title"
},
"dim": {
"columnName": "title"
},
"odsDatabase": {
"columnName": "title",
"dataType": "varchar(200)"
}
},
"name": "title"
},
{
"comment": "博文内容URL",
"dataType": "content_path",
"layers": {
"dim": {
"columnName": "content"
},
"odsDatabase": {
"columnName": "content",
"dataType": "varchar(1000)"
}
},
"name": "content"
},
{
"comment": "博文内容文本",
"dataType": "content_text",
"layers": {
"adsSearch": {
"columnName": "contentText"
},
"dim": {
"columnName": "contentText"
}
},
"name": "contentText",
"// 通过Http模式处理数据开始": "#######################",
"valuePattern": "com.qlteacher.bigdata.flink.pattern.HttpPattern",
"valuescript": "after.content != null && (op == 'create' || after.content != before.content) ? convertHtmlToText(pattern.toValue('https://downloadres.qlteacher.com/'+after.content)) : DO_NOT_VALUE",
"// 通过Http模式处理数据结束": "#######################"
}
],
"layers": {
"adsSearch": {
"tableName": "ads_blog"
},
"dim": {
"tableName": "dim_blog"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blog",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blog",
"operations": [
{
"name": "create",
"origin": "database"
},
{
"name": "update",
"origin": "database"
}
]
}
],
"modules": [
{
"name": "blog"
}
]
}
使用维度关系赋值也可以使用脚本赋值。字段配置的valueRelationName属性和valuescript属性,在脚本中可以使用od.relationMap['对应关系名称'].字段名获取某关系模型维度数据的某字段值。
统计处理 (DWD层到DWS数据层)本文中统计处理特指DWD层数据汇总计算成DWS层数据的操作。
统计处理可以根据元数据配置将的事实数据按照某关系维度进行汇总计算。
由于统计字段是属于被关系的维度模型统计具备关系的外键模型度量值,所以统计字段需要配置的statRelationModelName属性和statRelationName属性指定根据哪个模型的哪个关系的事实数据进行统计。
statRelationFieldName属性指定对应汇总的度量字段,计算的方式为累加,指定对应的模型和字段必须是DWD层数据。
举例,创建一条博文,产生一条博文创建的DWD事实数据,该数据会有一个“博文数量”的度量字段,值为1。
根据博文栏目维度关系配置,汇总处理将属于该栏目的事实数据的“博文数”累加汇总为该栏目的“博文数”。配置如下:
{
"models": [
{
"comment": "博文栏目",
"fields": [
{
"comment": "编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"dim": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "名称",
"dataType": "text_name",
"layers": {
"dim": {
"columnName": "name"
},
"odsDatabase": {
"columnName": "name",
"dataType": "varchar(50)"
}
},
"name": "name"
},
{
"// 博文栏目汇总数据字段开始": "#######################",
"comment": "博文数",
"dataType": "long",
"layers": {
"dws": {
"columnName": "blogCount"
}
},
"measure": true,
"name": "blogCount",
"statRelationFieldName": "blogCount",
"statRelationModelName": "blog",
"statRelationName": "blogCate",
"// 博文栏目汇总数据字段结束": "#######################"
}
],
"layers": {
"dim": {
"tableName": "dim_blog_cate"
},
"dws": {
"tableName": "dws_blog_cate"
}
},
"module": "blog",
"name": "blogCate",
"operations": [
{
"name": "create",
"origin": "database"
}
]
},
{
"comment": "博文",
"fields": [
{
"comment": "博文编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"dwd": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "标题",
"dataType": "text_title",
"layers": {
"odsDatabase": {
"columnName": "title",
"dataType": "varchar(200)"
}
},
"name": "title"
},
{
"comment": "博文栏目",
"dataType": "uuid",
"dimension": true,
"layers": {
"dwd": {
"columnName": "cateId"
},
"odsDatabase": {
"columnName": "cateId",
"dataType": "varchar(32)"
}
},
"name": "cateId"
},
{
"// 博文事实数据提供度量值开始": "#######################",
"comment": "博客数",
"dataType": "long",
"layers": {
"dwd": {
"columnName": "blogCount"
}
},
"measure": true,
"name": "blogCount",
"valuescript": "getDefaultModelCount(op)",
"// 博文事实数据提供度量值结束": "#######################"
}
],
"layers": {
"dwd": {
"tableName": "dwd_blog"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blog",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blog",
"operations": [
{
"name": "create",
"origin": "database"
}
],
"// 维度关系开始": "#######################",
"relations": [
{
"keys": [
{
"foreign": "cateId",
"primary": "id"
}
],
"name": "blogCate",
"source": "blogCate"
}
],
"// 维度关系结束": "#######################"
}
],
"modules": [
{
"name": "blog"
}
]
}
统计处理可以使用脚本进行如计算平均数等复杂且灵活的统计。
通过字段的statscript属性设置groovy脚本表达式,用来补全根据SummaryData汇总数据(某一段时间的)和AccumulatorData增量事实数据类型参数返回统计值的方法。
同时还可以使用statPattern属性指定一种“统计模式”,指定“统计模式”为方法增加StatPattern类型参数。 “统计模式”目前包括:
UserOperationTagsPattern: 计算用户行为标签的统计模式
统计处理脚本形成的完整方法如下:
Object metadata_model_xxx_field_xxx_stat_pattern_xxx(SummaryData sd, AccumulatorData accumulator, StatPattern pattern) {
String model = accumulator.model;
String op = accumulator.op;
String relation = accumulator.relationName;
Map key = accumulator.key;
Map data = accumulator.data;
Long ts = accumulator.ts;
Map summaryKey = sd.key;
Map summaryData = sd.data;
// statscript内容将会填充在此
}
返回常量DO_NOT_VALUE表示最后逻辑不需要累加计算。
字段的statscript属性负责一段时间的汇总数据与增量事实数据计算。
而一段时间的汇总数据需要和数据库持久化的汇总数据合并。
复杂的合并操作通过字段的statMergescript属性设置groovy脚本表达式,
用来补全根据两个SummaryData汇总数据类型参数返回合并值的方法。
统计处理的合并脚本形成的完整方法如下:
Object metadata_model_xxx_field_xxx_stat_merge(SummaryData sd, SummaryData accumulator) {
String model = accumulator.model;
Map key = accumulator.key;
Map data = accumulator.data;
Long ts = accumulator.ts;
Map summaryData = sd.data;
// statMergescript内容将会填充在此
}
举例,教师张三发表博文"如何指导小学生写好作文",说明张三对“小学生”、“作文”感兴趣,为其赋予这两项动态标签,可以为其推荐相关内容。
而博文首先要提取主题词为静态标签作为事实数据的度量值。配置如下:
{
"models": [
{
"comment": "用户",
"fields": [
{
"comment": "用户ID",
"dataType": "uuid",
"dimension": true,
"layers": {
"dim": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(45)"
}
},
"name": "id",
"pk": true
},
{
"comment": "用户姓名",
"dataType": "text_title",
"layers": {
"dim": {
"columnName": "name"
},
"odsDatabase": {
"columnName": "name",
"dataType": "varchar(768)"
}
},
"name": "name"
},
{
"// 根据博文等模型的事实数据汇总动态标签开始": "#######################",
"comment": "动态标签",
"dataType": "weight_tags",
"layers": {
"dws": {
"columnName": "dynamicTags"
}
},
"measure": true,
"name": "dynamicTags",
"statMergescript": "WeightTags.merge(summaryData.dynamicTags, data.dynamicTags).top()",
"statPattern": "com.qlteacher.bigdata.flink.pattern.UserOperationTagsPattern",
"statscript": "pattern.toStat(summaryData.dynamicTags,accumulator)",
"// 根据博文等模型的事实数据汇总动态标签结束": "#######################"
},
{
"comment": "用户博文数",
"dataType": "long",
"layers": {
"dws": {
"columnName": "blogCount"
}
},
"measure": true,
"name": "blogCount",
"statRelationFieldName": "blogCount",
"statRelationModelName": "blog",
"statRelationName": "user"
}
],
"layers": {
"dim": {
"tableName": "dim_user"
},
"dws": {
"tableName": "dws_user"
},
"odsDatabase": {
"tableName": "user",
"tables": [
{
"db": "user",
"name": "user",
"source": "mysql"
}
]
}
},
"module": "user",
"name": "user",
"operations": [
{
"name": "create",
"origin": "database",
"tsField": "createdTime"
}
]
},
{
"comment": "博客",
"fields": [
{
"comment": "日志编号",
"dataType": "uuid",
"dimension": true,
"layers": {
"dwd": {
"columnName": "id"
},
"odsDatabase": {
"columnName": "id",
"dataType": "varchar(32)"
}
},
"name": "id",
"pk": true
},
{
"comment": "标题",
"dataType": "text_title",
"layers": {
"odsDatabase": {
"columnName": "title",
"dataType": "varchar(200)"
}
},
"name": "title"
},
{
"comment": "作者",
"dataType": "text_name",
"dimension": true,
"layers": {
"dwd": {
"columnName": "creator"
},
"odsDatabase": {
"columnName": "creator",
"dataType": "varchar(32)"
}
},
"name": "creator"
},
{
"comment": "博客数",
"dataType": "long",
"layers": {
"dwd": {
"columnName": "blogCount"
}
},
"measure": true,
"name": "blogCount",
"valuescript": "getDefaultModelCount(op)"
},
{
"// 博文事实数据提取主题词为静态标签度量值开始": "#######################",
"comment": "静态标签",
"dataType": "weight_tags",
"layers": {
"dwd": {
"columnName": "staticTags"
}
},
"measure": true,
"name": "staticTags",
"valuePattern": "com.qlteacher.bigdata.flink.pattern.KeywordsPattern",
"valuescript": "WeightTags.from(pattern.toValue(after.title),30)",
"// 博文事实数据提取主题词为静态标签度量值结束": "#######################"
}
],
"layers": {
"dwd": {
"tableName": "dwd_blog"
},
"odsDatabase": {
"tables": [
{
"db": "blog",
"name": "blog",
"source": "mysql"
}
]
}
},
"module": "blog",
"name": "blog",
"operations": [
{
"name": "create",
"operatorField": "creator",
"origin": "database",
"tsField": "createdTime"
}
],
"relations": [
{
"keys": [
{
"foreign": "creator",
"primary": "id"
}
],
"name": "user",
"source": "user"
}
]
}
],
"modules": [
{
"name": "user"
},
{
"name": "blog"
}
]
}
热门内容处理
热门内容数据根据一段时间的累计汇总数据计算分数排序而得,模型AdsTopLayer热门内容层配置多个Topic,而Topic得scorescript属性是计算得分得脚本。
通过一段时间得SummaryData汇总数据类型参数返回得分的方法。生成完整的方法如下:
Double metadata_model_blog_layer_ads_top_topic_topBlog_score(SummaryData sd) {
Map key = sd.key;
Map data = sd.data;
Long ts = sd.ts;
// scorescript内容填写在此
}
举例,根据博文的访问量和点赞量计算出热门的博文,热门内容层配置如下:
{
"// 博文模型数据层配置": "############",
"layers": {
"adsTop": {
"topics": [
{
"name": "topBlog",
"scorescript": "(data.blogVisitCount ?: 0) + (data.blogPraiseCount ?: 0) * 2"
}
]
}
}
}
结语
我们进行大数据设计开发的人力有限,刚开始入门且没有可靠的指导,很多情况是现学现卖或摸着石头过河。选择的技术栈也都是自己熟悉的优先,元数据的定义和配置都可能不够合理,我们也会不断的重构完善。当然市面上可能已经有成熟的解决方案,但毕竟能掌握“核心科技”还是很重要的嘛,问题解决也变得可控。
网上基于规则进行大数据处理的文章和实例还是太少,希望我写的内容能其他抛砖引玉的作用,欢迎大家交流。



