首先,先了解一下ODS层的任务即其功能:
1)保持数据原貌不做任何修改,起到备份数据的作用。
2)数据采用LZO压缩,减少磁盘存储空间。100G数据可以压缩到10G以内。
3)创建分区表,防止后续的全表扫描,在企业开发中大量使用分区表。
4)创建外部表。在企业开发中,除了自己用的临时表,创建内部表外,绝大多数场景都是创建外部表。
1)创建支持lzo压缩的分区表
(1)建表语句
hive (gmall)>
drop table if exists ods_log;
CREATE EXTERNAL TABLE ods_log (`line` string)
PARTITIONED BY (`dt` string) -- 按照时间创建分区
STORED AS -- 指定存储方式,读数据采用LzoTextInputFormat;
INPUTFORMAT 'com.hadoop.mapred.DeprecatedLzoTextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION '/warehouse/gmall/ods/ods_log' -- 指定数据在hdfs上的存储位置
;
(2)分区规划
2)加载数据---将hdfs中的日志加载到表中,也就是DataGrip中
代码实现:
load data inpath '/origin_data/gmall/log/topic_log/2020-06-14'
into table ods_log partition(dt='2020-06-14');
注意:时间格式都配置成YYYY-MM-DD格式,这是Hive默认支持的时间格式
3)为lzo压缩文件创建索引
[axing@hadoop102 bin]$ hadoop jar
/opt/module/hadoop-3.1.3/share/hadoop/common/hadoop-lzo-0.4.20.jar
com.hadoop.compression.lzo.DistributedLzoIndexer
/warehouse/gmall/ods/ods_log/dt=2020-06-14
总结:
(1)单引号不取变量值
(2)双引号取变量值
(3)反引号`,执行引号中命令
(4)双引号内部嵌套单引号,取出变量值
(5)单引号内部嵌套双引号,不取出变量值
4.1.3 ODS层日志表加载数据脚本(数据转移:hdfs--》DataGrip表中)
1)在hadoop102的/home/atguigu/bin目录下创建脚本
[axing@hadoop102 bin]$ vim hdfs_to_ods_log.sh
添加如下代码:
#!/bin/bash
# 定义变量方便修改
APP=gmall
# 如果是输入的日期按照取输入日期;如果没输入日期取当前时间的前一天
if [ -n "$1" ] ;then
do_date=$1
else
do_date=`date -d "-1 day" +%F`
fi
echo ================== 日志日期为 $do_date ==================
sql="
load data inpath '/origin_data/$APP/log/topic_log/$do_date' into table ${APP}.ods_log partition(dt='$do_date');
"
hive -e "$sql"
hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/common/hadoop-lzo-0.4.20.jar
com.hadoop.compression.lzo.DistributedLzoIndexer /warehouse/$APP/ods/ods_log/dt=$do_date
代码说明:
(1)说明1:
[ -n 变量值 ] 判断变量的值,是否为空
-- 变量的值,非空,返回true
-- 变量的值,为空,返回false
注意:[ -n 变量值 ]不会解析数据,使用[ -n 变量值 ]时,需要对变量加上双引号(" ")
(2)说明2:
查看date命令的使用,date --help
2)增加脚本执行权限
3)执行脚本
[axing@hadoop102 module]$ hdfs_to_ods_log.sh 2020-06-14
最后,查看导入数据。
ODS层业务表分区规划如下
ODS层业务表数据装载思路如下
(也是hdfs到DataGrip,不过传输的是业务,不是日志--用户行为数据)
(1)在/home/atguigu/bin目录下创建脚本hdfs_to_ods_db_init.sh
[axing@hadoop102 bin]$ vim hdfs_to_ods_db_init.sh
脚本太长了,就不展示了。
编写完后设置权限
就可以导入数据了。
(2)脚本使用
axing@hadoop102 bin]$ hdfs_to_ods_db_init.sh all 2020-06-14
然后查看是否导入数据。
(1)在/home/atguigu/bin目录下创建脚本hdfs_to_ods_db.sh
[axing@hadoop102 bin]$ vim hdfs_to_ods_db.sh
步骤同上。
1.建表语句:略(代码太长了)
2.分区规划:
3.数据装载:
这里插入一个问题:
(1)两种方式,分别查询数据有多少行
hive (gmall)> select * from ods_log;
Time taken: 0.706 seconds, Fetched: 2955 row(s)
hive (gmall)> select count(*) from ods_log;
2959
(2)两次查询结果不一致。
原因是select * from ods_log不执行MR操作,直接采用的是ods_log建表语句中指定的DeprecatedLzoTextInputFormat,能够识别lzo.index为索引文件。
select count(*) from ods_log执行MR操作,会先经过hive.input.format,其默认值为CombineHiveInputFormat,其会先将索引文件当成小文件合并,将其当做普通文件处理。更严重的是,这会导致LZO文件无法切片。
hive (gmall)>
hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
解决办法:修改CombineHiveInputFormat为HiveInputFormat
hive (gmall)>
set hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
首日装载代码:略
每日装载代码:略
1.建表语句:略
2.分区规划:
3.数据装载:
首日装载代码:略
每日装载代码:略
1.建表语句:略
2.分区规划:
3.数据装载:
首日装载代码:略
每日装载代码:略
1.建表语句:略
2.数据装载:
地区维度表数据相对稳定,变化概率较低,故无需每日装载。(不需要分区)
数据装载代码:
insert overwrite table dim_base_province
select
bp.id,
bp.name,
bp.area_code,
bp.iso_code,
bp.iso_3166_2,
bp.region_id,
br.region_name
from ods_base_province bp
join ods_base_region br on bp.region_id = br.id;
1.建表语句:略
2.数据装载:
通常情况下,时间维度表的数据并不是来自于业务系统,而是手动写入,并且由于时间维度表数据的可预见性,无须每日导入,一般可一次性导入一年的数据。
1)在DataGrip中创建一张临时表(不是列式存储)
DROP TABLE IF EXISTS tmp_dim_date_info;
CREATE EXTERNAL TABLE tmp_dim_date_info (
`date_id` STRING COMMENT '日',
`week_id` STRING COMMENT '周ID',
`week_day` STRING COMMENT '周几',
`day` STRING COMMENT '每月的第几天',
`month` STRING COMMENT '第几月',
`quarter` STRING COMMENT '第几季度',
`year` STRING COMMENT '年',
`is_workday` STRING COMMENT '是否是工作日',
`holiday_id` STRING COMMENT '节假日'
) COMMENT '时间维度表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
LOCATION '/warehouse/gmall/tmp/tmp_dim_date_info/';
2)将数据文件上传到HFDS上临时表指定路径/warehouse/gmall/tmp/tmp_dim_date_info/,上传后就可以在临时表中查看我们的数据。
该数据文件就是我们要手动导入到DataGrip中的数据。
3)现在要建立一张时间维度表(正式的表),然后将临时表中的数据导入到正式表中
建表语句:(正式表)
DROP TABLE IF EXISTS dim_date_info;
CREATE EXTERNAL TABLE dim_date_info(
`date_id` STRING COMMENT '日',
`week_id` STRING COMMENT '周ID',
`week_day` STRING COMMENT '周几',
`day` STRING COMMENT '每月的第几天',
`month` STRING COMMENT '第几月',
`quarter` STRING COMMENT '第几季度',
`year` STRING COMMENT '年',
`is_workday` STRING COMMENT '是否是工作日',
`holiday_id` STRING COMMENT '节假日'
) COMMENT '时间维度表'
STORED AS PARQUET
LOCATION '/warehouse/gmall/dim/dim_date_info/'
TBLPROPERTIES ("parquet.compression"="lzo");
4)执行以下语句将其导入时间维度表(HDFS--》DataGrip)
insert overwrite table dim_date_info select * from tmp_dim_date_info;
这里解释一下为什么要先用一张临时时间维度表来存数据。
因为我们导入的时间数据是txt文本的,但是我们的时间维度表是列式存储,并且只能识别LZO文件,因此,我们要创建一张临时表来存储txt文件的,然后从临时表中选取数据insert到我们的时间维度表中。
1)什么是拉链表?
拉链表,记录每条信息的生命周期,一旦一条记录的生命周期结束,就重新开始一条新的记录,并把当前日期放入生效开始日期。
如果当前信息至今有效,在生效结束日期中填入一个极大值(如9999-99-99)
2)为什么要做拉链表
连接表适合于:数据会发生变化,但是变化频率并不高的维度(即:缓慢变化维)
比如:用户信息会发生变化,但是每天变化的比例不高。如果数据量有一定规模,按照每日全量的
方式保存效率很低。
3)如何使用拉链表
通过,生效开始日期<=某个日期 且 生效结束日期>=某个日期,能够得到某个时间点的数据全量切片。
4)拉链表形成过程
1.建表语句
DROP TABLE IF EXISTS dim_user_info;
CREATE EXTERNAL TABLE dim_user_info(
`id` STRING COMMENT '用户id',
`login_name` STRING COMMENT '用户名称',
`nick_name` STRING COMMENT '用户昵称',
`name` STRING COMMENT '用户姓名',
`phone_num` STRING COMMENT '手机号码',
`email` STRING COMMENT '邮箱',
`user_level` STRING COMMENT '用户等级',
`birthday` STRING COMMENT '生日',
`gender` STRING COMMENT '性别',
`create_time` STRING COMMENT '创建时间',
`operate_time` STRING COMMENT '操作时间',
`start_date` STRING COMMENT '开始日期',
`end_date` STRING COMMENT '结束日期'
) COMMENT '用户表'
PARTITIONED BY (`dt` STRING)
STORED AS PARQUET
LOCATION '/warehouse/gmall/dim/dim_user_info/'
TBLPROPERTIES ("parquet.compression"="lzo");
2.分区规划
3.数据装载
1)首日装载
拉链表首日装载,需要进行初始化操作,具体工作为将截止到初始化当日的全部历史用户导入一次性导入到拉链表中。目前的ods_user_info表的第一个分区,即2020-06-14分区中就是全部的历史用户,故将该分区数据进行一定处理后导入拉链表的9999-99-99分区即可。
insert overwrite table dim_user_info partition(dt='9999-99-99')
select
id,
login_name,
nick_name,
md5(name),
md5(phone_num),
md5(email),
user_level,
birthday,
gender,
create_time,
operate_time,
'2020-06-14',
'9999-99-99'
from ods_user_info
where dt='2020-06-14';
2)每日装载
(1)实现思路
(2)代码实现:略
1)编写脚本
(1)在/home/atguigu/bin目录下创建脚本ods_to_dim_db_init.sh
[axing@hadoop102 bin]$ vim ods_to_dim_db_init.sh
代码实现:略
设置权限
同上。
DWD层的作用:
1)对用户行为数据解析。
2)对业务数据采用维度模型重新建模。
1)日志结构回顾
(1)页面埋点日志
(2)启动日志
2)日志解析思路
1)取出第一个json对象
hive (gmall)>
select get_json_object('[{"name":"大郎","sex":"男","age":"25"},{"name":"西门
庆","sex":"男","age":"47"}]','$[0]');
2)取出第一个json的age字段的值
hive (gmall)>
SELECT get_json_object('[{"name":"大郎","sex":"男","age":"25"},{"name":"西门
庆","sex":"男","age":"47"}]',"$[0].age");
启动日志解析思路:启动日志表中每行数据对应一个启动记录,一个启动记录应该包含日志中的公共信息和启动信息。先将所有包含start字段的日志过滤出来,然后使用get_json_object函数解析每个字段。
1)建表语句:略
2)数据导入:
页面日志解析思路:页面日志表中每行数据对应一个页面访问记录,一个页面访问记录应该包含日志中的公共信息和页面信息。先将所有包含page字段的日志过滤出来,然后使用get_json_object函数解析每个字段。
动作日志解析思路:动作日志表中每行数据对应用户的一个动作记录,一个动作记录应当包含公共信息、页面信息以及动作信息。先将包含action字段的日志过滤出来,然后通过UDTF函数,将action数组“炸开”(类似于explode函数的效果),然后使用get_json_object函数解析每个字段。
1)建表语句:略
2)创建UDTF函数——设计思路
UDTF设计思路:
3)创建UDTF函数——编写代码(创建Maven工程,然后打包)
略
4)创建函数
(1)打包
(2)将hivefunction-1.0-SNAPSHOT.jar上传到hadoop102的/opt/module,然后再将该jar包上传到HDFS的/user/hive/jars路径下
[axing@hadoop102 module]$ hadoop fs -mkdir -p /user/hive/jars
[axing@hadoop102 module]$ hadoop fs -put
hivefunction-1.0-SNAPSHOT.jar /user/hive/jars
(3)创建永久函数与开发好的java class关联
create function explode_json_array as
'com.atguigu.hive.udtf.ExplodeJSONArray' using jar
'hdfs://hadoop102:8020/user/hive/jars/hivefunction-1.0-SNAPSHOT.jar';
(4)注意:如果修改了自定义函数重新生成jar包怎么处理?只需要替换HDFS路径上的旧jar包,然后重启Hive客户端即可。
5)数据导入:略
曝光日志解析思路:曝光日志表中每行数据对应一个曝光记录,一个曝光记录应当包含公共信息、页面信息以及曝光信息。先将包含display字段的日志过滤出来,然后通过UDTF函数,将display数组“炸开”(类似于explode函数的效果),然后使用get_json_object函数解析每个字段。
错误日志解析思路:错误日志表中每行数据对应一个错误记录,为方便定位错误,一个错误记录应当包含与之对应的公共信息、页面信息、曝光信息、动作信息、启动信息以及错误信息。先将包含err字段的日志过滤出来,然后使用get_json_object函数解析所有字段。