离线数仓(六)【ODS 层开发】

前言

        今天开始正式的数仓搭建,所谓 ODS 层的工作就是把我们各种数据源采集发送来的各种类型的数据(Json、tsv类型)映射到 Hive 表中,映射时可以进行一些简单的处理,比如简单的数据清洗,舍弃一些没有必要的字段。

1、ODS 层开发

ODS层的设计要点如下:

(1)ODS层的表结构设计依托于从业务系统同步过来的数据结构(JSON/CSV/TSV)

(2)ODS层要保存全部历史数据,故其压缩格式应选择高压缩比的算法,此处选择gzip。

(3)ODS层表名的命名规范为:ods_表名_单分区增量全量标识(inc/full)

注意:ODS 层的表都是分区表,因为我们每天都会有数据被采集到数仓,所以我们的表是按照日期分区的,每天一张表。

我们当前保存在 HDFS 路径下的数据主要有两类:log 和 db,log 目录下存放是我们从 Flume 传过来的用户行为日志文件,我们已经用 gzip 压缩过了,可以通过下面的命令查看:

hadoop fs -cat /origin_data/gmall/log/topic_log/2020-06-14/* | zcat

        可以看到我们的格式是一个 JSON 格式,那我们要建表的话就得考虑怎么把 JSON 格式的数据映射到我们的 Hive 表中了。

        对于 db 目录下的文件主要有两类:DataX 同步过来的以 "full" 为目录后缀的全量业务数据和 Maxwell 同步过来的首日全量数据和以 "inc" 为目录后缀的增量业务数据。

hadoop fs -cat /origin_data/gmall/db/activity_rule_full/2020-06-14/* | zcat

可以看到 DataX 传输过来的文件是 ".tsv" 文件,我们将来只要拿 "\t" 分隔即可。

hadoop fs -cat /origin_data/gmall/db/comment_info_inc/2020-06-14/* | zcat

可以看到,拿 Maxwell 同步过来的数据和 Flume 一样,都是 JSON 格式的。 

1.1、用户行为日志表

1.1.1、ROW FORMAT 和 STORE AS 

打开 Hive 官网 -> LanguageManual -> DDL -> JSON

查看 Hive 3.x 版本支持的通过解析 JSON 映射表的语法:

CREATE TABLE my_table(a string, b bigint, ...)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.JsonSerDe'    -- 声明行的格式
STORED AS TEXTFILE;    -- 声明怎么解析文件

        这里的 ROW FORMATSTORE AS 都是 Hive 建表是所必须指定的,只不过 Hive 帮我们简化了这部分语法:

      ROW FORMAT :用 DELIMITED 关键字表示对文件中的每个字段按照特定分割符进行分割用 SERDE 关键字来指定 Hive 内置的 SERDE 或者 用户自定义的 SERDE。

       STORE AS :用 STORED AS + 简写文件格式来指定 InputFormat 和 OutputFormat ,默比如 TextFileInputFormat 和 TextFileOutputFormat 可以用 STORE AS TEXTFILE 来表示。

Hive SerDe

        Hive 的 ROW FORMAT 的 SERDE 属性指定了 Hive 的序列化器和反序列化器,映射 HDFS 文件时,使用反序列化器进行解析,写出文件时使用序列化器来封装数据。

1.1.2、复杂数据类型

        对于用户行为日志,它的存储格式是 json 格式,那我们就需要把它的每个字段映射到我们 Hive 表中。首先回顾一下 Hive 的三种复杂类型:

  • array
    • 声明:array<string>
    • 取值:arr[0]
    • 构造:array(val1,val2...),split(),collect_set()
  • map
    • 声明:map<string,bigint>
    • 取值:map[key]
    • 构造:map(key1,val1,key2,val2...)
  • struct
    • 声明:struct<id:int,name:strnig>
    • 取值:struct.id
    • 构造:named_struct(name1,val1,name2,val2...)

        对于上面的 common 字段,它虽然是键值对格式,但是我们不能使用 map ,因为 map 的 value 是同一数据类型,而我们这里的 common 字段不同键的值有的是 int 有的是 string。所以我们使用 struct。

        对于 displays 字段,由于它存储的是数组类型,然后数组嵌套键值对,所以我们需要使用 array(struct) 的嵌套类型。

1.1.3、设计日志表

DROP TABLE IF EXISTS ods_log_inc;
CREATE EXTERNAL TABLE ods_log_inc
(`common`   STRUCT<ar :STRING,ba :STRING,ch :STRING,is_new :STRING,md :STRING,mid :STRING,os :STRING,uid :STRING,vc:STRING> COMMENT '公共信息',`page`     STRUCT<during_time :STRING,item :STRING,item_type :STRING,last_page_id :STRING,page_id:STRING,source_type :STRING> COMMENT '页面信息',`actions`  ARRAY<STRUCT<action_id:STRING,item:STRING,item_type:STRING,ts:BIGINT>> COMMENT '动作信息',`displays` ARRAY<STRUCT<display_type :STRING,item :STRING,item_type :STRING,`order` :STRING,pos_id:STRING>> COMMENT '曝光信息',`start`    STRUCT<entry :STRING,loading_time :BIGINT,open_ad_id :BIGINT,open_ad_ms :BIGINT,open_ad_skip_ms:BIGINT> COMMENT '启动信息',`err`      STRUCT<error_code:BIGINT,msg:STRING> COMMENT '错误信息',`ts`       BIGINT  COMMENT '时间戳'
) COMMENT '活动信息表'PARTITIONED BY (`dt` STRING)ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.JsonSerDe'LOCATION '/warehouse/gmall/ods/ods_log_inc/';

 注意:我们数仓建的基本都是外部表,防止误删数据!

        这里的分区我们指定了一个 String 类型的分区键:如果我们有一个分区 dt='2020-06-14',那么在 HDFS 上就会有一个目录,路径类似于 /user/hive/warehouse/sales/dt=2020-06-14,这个目录就存放了所有 dt 为 '2020-06-14' 的数据。

gzip 和 bzip2 格式的文件可以直接以 textfile 的格式来 load ,而不需要在建表时指定压缩格式(其实就是指定 STORE AS )。

但是其他压缩格式是不行的,比如 LZO 压缩的话,必须指定 STORE AS 的 InputFormat 和 OutputFormat :

CREATE EXTERNAL TABLE IF NOT EXISTS hive_table_name (column_1  datatype_1......column_N datatype_N)PARTITIONED BY (partition_col_1 datatype_1 ....col_P  datatype_P)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'STORED AS INPUTFORMAT  \"com.hadoop.mapred.DeprecatedLzoTextInputFormat\"OUTPUTFORMAT \"org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat\";

1.1.4、装载脚本

我们的日志数据每天都要 load 到一张新的分区表中,装载语句还是比较简单的:

load data inpath '/origin_data/gmall/log/topic_log/2020-06-14' into table ods_log_inc partition(dt='2020-06-14')

思考:load 和 location 的区别?

load data会使数据目录发生改变(数据从源路径"移动"到我们表的路径)

location 则不会,而是以此目录作为源数据

注意:load 尽管是移动数据表但是并没有什么开销,因为它只是修改了我们 HDFS 文件块的在 NameNode 中的元数据路径,并没有真的移动数据。

但是我们不能每天都重复编写命令去执行,我们这里直接写一个 shell 脚本:

#!/bin/bash# 定义变量方便修改
APP=gmall# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;thendo_date=$1
elsedo_date=`date -d "-1 day" +%F`
fiecho ================== 日志日期为 $do_date ==================
sql="
load data inpath '/origin_data/$APP/log/topic_log/$do_date' into table ${APP}.ods_log_inc partition(dt='$do_date');
"
hive -e "$sql"

脚本用法:

hdfs_to_ods_log.sh 2020-06-14

可以看到我们的表被成功 load 。

1.2、业务表(全量 & 增量)

1.2.1、建表分析

因为 DataX 全量采集的结果是 tsv 格式所以我们可以直接对应 MySQL 中这些表格的格式,而 Maxwell 同步过来的增量表我们需要用 json 来序列化和反序列化。

比如全量同步的活动信息表:

DROP TABLE IF EXISTS ods_activity_info_full;
CREATE EXTERNAL TABLE ods_activity_info_full
(`id`            STRING COMMENT '活动id',`activity_name` STRING COMMENT '活动名称',`activity_type` STRING COMMENT '活动类型',`activity_desc` STRING COMMENT '活动描述',`start_time`    STRING COMMENT '开始时间',`end_time`      STRING COMMENT '结束时间',`create_time`   STRING COMMENT '创建时间'
) COMMENT '活动信息表'PARTITIONED BY (`dt` STRING)ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'NULL DEFINED AS ''LOCATION '/warehouse/gmall/ods/ods_activity_info_full/';

此外,Maxwell 增量同步过来的数据我们需要额外注意,因为 Maxwell 监听到的有三种类型的数据:insert、update 和 delete。其中 update 类型的数据它的 old 字段保存的是修改的数据字段,而 data 字段保存的是全部内容。

比如首日增量同步的购物车表:

DROP TABLE IF EXISTS ods_cart_info_inc;
CREATE EXTERNAL TABLE ods_cart_info_inc
(`type` STRING COMMENT '变动类型',`ts`   BIGINT COMMENT '变动时间',`data` STRUCT<id :STRING,user_id :STRING,sku_id :STRING,cart_price :DECIMAL(16, 2),sku_num :BIGINT,img_url :STRING,sku_name:STRING,is_checked :STRING,create_time :STRING,operate_time :STRING,is_ordered :STRING,order_time:STRING,source_type :STRING,source_id :STRING> COMMENT '数据',`old`  MAP<STRING,STRING> COMMENT '旧值'
) COMMENT '购物车增量表'PARTITIONED BY (`dt` STRING)ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.JsonSerDe'LOCATION '/warehouse/gmall/ods/ods_cart_info_inc/';

        但是我们这里的 database 字段、old 和 table 字段都不需要映射到我们的表中,因为它并没有意义。我们需要保留 type 字段,因为我们要知道用户的操作类型(比如如果这张表是购物车表 cart_info 那 type = 'insert' 就是加入购物车的操作),除此之外,我们还需要保留 old、 ts 和 data 字段,其中 old 我们只能使用 Map<string,string> 来保存,因为 old 字段是发生修改的字段,我们无法确定每个被修改的字段的类型。

注意:对于增量同步的表,我们之前用 Maxwell 做了历史数据首日全量同步,

1.2.2、装载脚本

        上面我们的用户行为日志表因为是一张表,所以我们的参数(日期)只有一个就够了,这里我们的全量和增量表需要指定两个参数(日期和表名)

#!/bin/bashAPP=gmallif [ -n "$2" ] ;thendo_date=$2
else do_date=`date -d '-1 day' +%F`
fiload_data(){sql=""for i in $*; do#判断路径是否存在hadoop fs -test -e /origin_data/$APP/db/${i:4}/$do_date#路径存在方可装载数据if [[ $? = 0 ]]; thensql=$sql"load data inpath '/origin_data/$APP/db/${i:4}/$do_date' OVERWRITE into table ${APP}.$i partition(dt='$do_date');"fidonehive -e "$sql"
}case $1 in"ods_activity_info_full")load_data "ods_activity_info_full";;"ods_activity_rule_full")load_data "ods_activity_rule_full";;"ods_base_category1_full")load_data "ods_base_category1_full";;"ods_base_category2_full")load_data "ods_base_category2_full";;"ods_base_category3_full")load_data "ods_base_category3_full";;"ods_base_dic_full")load_data "ods_base_dic_full";;"ods_base_province_full")load_data "ods_base_province_full";;"ods_base_region_full")load_data "ods_base_region_full";;"ods_base_trademark_full")load_data "ods_base_trademark_full";;"ods_cart_info_full")load_data "ods_cart_info_full";;"ods_coupon_info_full")load_data "ods_coupon_info_full";;"ods_sku_attr_value_full")load_data "ods_sku_attr_value_full";;"ods_sku_info_full")load_data "ods_sku_info_full";;"ods_sku_sale_attr_value_full")load_data "ods_sku_sale_attr_value_full";;"ods_spu_info_full")load_data "ods_spu_info_full";;"ods_cart_info_inc")load_data "ods_cart_info_inc";;"ods_comment_info_inc")load_data "ods_comment_info_inc";;"ods_coupon_use_inc")load_data "ods_coupon_use_inc";;"ods_favor_info_inc")load_data "ods_favor_info_inc";;"ods_order_detail_inc")load_data "ods_order_detail_inc";;"ods_order_detail_activity_inc")load_data "ods_order_detail_activity_inc";;"ods_order_detail_coupon_inc")load_data "ods_order_detail_coupon_inc";;"ods_order_info_inc")load_data "ods_order_info_inc";;"ods_order_refund_info_inc")load_data "ods_order_refund_info_inc";;"ods_order_status_log_inc")load_data "ods_order_status_log_inc";;"ods_payment_info_inc")load_data "ods_payment_info_inc";;"ods_refund_payment_inc")load_data "ods_refund_payment_inc";;"ods_user_info_inc")load_data "ods_user_info_inc";;"all")load_data "ods_activity_info_full" "ods_activity_rule_full" "ods_base_category1_full" "ods_base_category2_full" "ods_base_category3_full" "ods_base_dic_full" "ods_base_province_full" "ods_base_region_full" "ods_base_trademark_full" "ods_cart_info_full" "ods_coupon_info_full" "ods_sku_attr_value_full" "ods_sku_info_full" "ods_sku_sale_attr_value_full" "ods_spu_info_full" "ods_cart_info_inc" "ods_comment_info_inc" "ods_coupon_use_inc" "ods_favor_info_inc" "ods_order_detail_inc" "ods_order_detail_activity_inc" "ods_order_detail_coupon_inc" "ods_order_info_inc" "ods_order_refund_info_inc" "ods_order_status_log_inc" "ods_payment_info_inc" "ods_refund_payment_inc" "ods_user_info_inc";;
esac
  •  hadoop fs -test -e 用来判断路径是否存在,存在返回 0
  • si{1:4} 返回第 $i 个字符串的4个字符之后的字符串
hdfs_to_ods_db.sh all 2020-06-14

执行完毕,查看 hdfs web 端,共 29 张表,说明我们 load 完毕。 

总结

        至此,ODS 搭建完毕。ODS 层其实就是把我们的采集过来的数据(用户行为数据(json 格式)、业务数据(用 DataX 采集过来的数据是 tsv 格式,用 Maxwell 首日全量和之后增量采集过来的是 json 格式)都按照字段映射到我们的 Hive 表中。尤其是用户行为日志,因为业务数据我们还可以参考 MySQL 中的存储格式,但是对于 json 格式的日志数据我们必须自己去设计,而且一些字段还需要进行舍取。        

        Shell 脚本的简单编写还需要复习复习。

DataGrip 字段不显示的配置

Hive 3.3 之后的版本的解析 json 类型的表格时读取不了元数据信息,需要在 hive-site.xml 中配置下面的信息:
<property><name>metastore.storage.schema.reader.impl</name><value>org.apache.hadoop.hive.metastore.SerDeStorageSchemaReader</value>
</property>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/275951.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

金三银四!一个年薪160W+的就业方向!

前言 随着越来越多的科技大厂加入鸿蒙生态建设&#xff0c;鸿蒙开发人才正在市场上被争抢。资深工程师开出的年薪高达近百万&#xff0c;架构师更是高至160万&#xff0c;真可谓“鸿蒙猿年薪超百万”。如何抓住新技术红利&#xff0c;尽早上车&#xff1f;你会成为下一个鸿蒙开…

C语言——详解字符函数和字符串函数(一)

Hi,铁子们好呀&#xff01;今天博主来给大家更一篇C语言的字符函数和字符串函数~ 具体讲的内容如下&#xff1a; 文章目录 &#x1f386;1.字符分类函数&#x1f4af;&#x1f4af;⏩1.1 什么是字符分类函数的&#xff1f;&#x1f4af;&#x1f4af;⏩1.2 字符函数的类型有哪…

一文讲懂 C++ 类和对象(1)

0. 面向过程程序设计和面向对象程序设计的区别 面向对象程序设计往往关注的是怎么去做&#xff0c;是将解决问题的步骤分析出来&#xff0c;然后用函数把步骤一步一步实现&#xff0c;然后再依次调用就可以了。而面向对象是将构成问题的事物&#xff0c;分解成若干个对象&…

基于springboot+vue实现汽车改装方案网站系统项目【项目源码+论文说明】

基于springboot实现汽车改装方案网站系统演示 摘要 本文主要讲述了基于SpringBootMySql开发技术开发的汽车改装方案网站的设计与实现。这里的汽车改装方案网站是通过一个平台使所有的汽车爱好者们可以不用出门就可以体验到专业的汽车改装方案设计服务。现实生活中如果需要进行…

牛客-DP38 【模板】二维差分

【模板】二维差分_牛客题霸_牛客网 (nowcoder.com) b站有视频&#xff1a;讲解前缀和和差分 二维差分_哔哩哔哩_bilibili 注意&#xff1a;差分的过程叫差分&#xff0c;而不仅仅是d[]这个数组&#xff0c;其他数组经行了差分的操作&#xff0c;就也是差分啊&#xff01;&…

springcloud第3季 consul服务发现注册,配置中心2

一 consul的作用 1.1 为何使用注册中心 为何要用注册中心&#xff1f; 1.A服务调用B服务&#xff0c;使用ip和端口&#xff0c;如果B服务的ip或者端口发生变化&#xff0c;服务A需要进行改动&#xff1b; 2.如果在分布式集群中&#xff0c;部署多个服务B&#xff0c;多个服…

JAVA基础—集合详细解析

1.JAVA中的4种集合类型 Set&#xff1a;无序、不可重复的集合List&#xff1a;有序、重复的集合Queue&#xff1a; 队列Map&#xff1a;映射关系 首先&#xff0c;JAVA的集合类主要有两个接口派生而出&#xff1a;Collection和Map。Collection和Map就是JAVA集合的根接口。所以后…

Kubernetes弃用Dockershim,转向Containerd:影响及如何应对

Kubernetes1.24版本发布时&#xff0c;正式宣布弃用Dockershim&#xff0c;转向Containerd作为默认的容器运行环境。Kubernetes以CRI(Container Runtime Interface)容器运行时接口制定接入准则&#xff0c;用户可以使用Containerd、CRI-O、CRI- Dockerd及其他容器运行时作为Kub…

在Docker上传我们自己的镜像(以springboot项目为例)

首先确定好在我们的centOS服务器上已经安装并配置好docker 配置自己的springboot镜像并运行 获取springboot的jar包 maven clean--》mavenue package --》复制target目录下生成的jar包 在服务器选择一个文件夹上传jar包&#xff0c;我这里选用的文件夹叫做/opt/dockertest…

【话题】2024年AI辅助研发趋势,有那些应用领域

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读文章&#xff01; 此篇是【话题达人】系列文章&#xff0c;这一次的话题是《2024年AI辅助研发趋势》 目录 背景概念实践医药领域汽车设计领域展望未来文章推荐 背景 随着人工智能技术的持续发展与突破&#xff0c;2024年AI辅…

使用电脑的时候最怕有英文?微信的这个功能一定要用起来!

相信各位小伙伴对微信都不陌生。我们平常使用微信的大部分时间都是聊天、朋友圈、视频号等。 如果有人给你发来一张全英文图片的截图&#xff0c;你会咋整&#xff1f; 请人翻译&#xff1f;这个显然不需要。 一个个字母手打上去&#xff1f;这字数少了还行&#xff0c;但多了…

HarmonyOS NEXT应用开发之深色模式适配

介绍 本示例介绍在开发应用以适应深色模式时&#xff0c;对于深色和浅色模式的适配方案&#xff0c;采取了多种策略如下&#xff1a; 固定属性适配&#xff1a;对于部分组件的颜色属性&#xff0c;如背景色或字体颜色&#xff0c;若保持不变&#xff0c;可直接设定固定色值或…

strcat的模拟实现

一&#xff1a;strcat函数的定义&#xff1a; strcat函数功能的解释&#xff1a; 将源字符串追加&#xff08;连接&#xff09;到目的字符串的后面 strcat函数要点&#xff1a; 源字符串必须以 \0 结束。 目标空间必须有足够的大&#xff0c;能容纳下源字符串的内容。 目…

智慧城市与智慧乡村:共创城乡一体化新局面

一、引言 随着科技的不断进步和城乡发展的日益融合&#xff0c;智慧城市与智慧乡村的建设已成为推动城乡一体化发展的新引擎。智慧城市利用物联网、大数据、云计算等先进技术&#xff0c;实现城市治理、公共服务、产业发展等领域的智能化&#xff1b;而智慧乡村则借助现代科技…

spring中事务失效的场景有哪些?

异常捕获处理 在方法中已经将异常捕获处理掉并没有抛出。 事务只有捕捉到了抛出的异常才可以进行处理&#xff0c;如果有异常业务中直接捕获处理掉没有抛出&#xff0c;事务是无法感知到的。 解决&#xff1a;在catch块throw抛出异常。 抛出检查异常 spring默认只会回滚非检…

HTML5+CSS3+JS小实例:全屏范围滑块

实例:全屏范围滑块 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale…

手机和电脑同步的好用记事本软件有哪些

我常常需要随手记录各种信息&#xff0c;以便随时查阅和使用。比如&#xff0c;在下班路上&#xff0c;我会用手机记录明天要处理的工作事项、购物清单&#xff0c;或是某个突然迸发的创意想法&#xff1b;而在办公室&#xff0c;我则需要在电脑上整理会议纪要、项目计划&#…

C语言实战——扫雷游戏

目录 1. 扫雷游戏分析和设计2.扫雷游戏的代码实现 1. 扫雷游戏分析和设计 1.1扫雷游戏的功能说明 使用控制台实现经典的扫雷游戏游戏可以通过菜单实现继续玩或者退出游戏扫雷的棋盘是9*9的格子默认随机布置10个雷可以排查雷 如果位置不是雷&#xff0c;就显示周围有几个雷 如果…

经典数组和指针笔试题解析——C语言

【本节内容】 1. 数组和指针笔试题解析 2. 指针运算笔试题解析 1. 数组和指针笔试题解析 1.1 一维数组 #include <stdio.h> int main() {int a[] { 1,2,3,4 };printf("%zd\n", sizeof(a));printf("%zd\n", sizeof(a 0));printf("%zd\n&qu…

自动从Android上拉取指定文件

需求场景 利用Mac中的脚本编辑器实现从连接的Android设备中获取指定的文件。 环境 macOS Monterey 版本 12.7.1脚本编辑器adb环境&#xff08;如果没有的话&#xff0c;可以网上搜下Mac配置adb&#xff09; 实现方案 1、打开脚本编辑器&#xff1b; 2、新建一个脚本文件&…