电商大数据项目,就是将电商的业务数据以及用户的行为数据进行ETL,最后清洗出来有用的数据进行展示。
这个项目中的数据都是仿造出来的,真正生产线上的数据也是通过埋点得到相应规则的数据,不过省略了这一步,现在靠做好的jar包生产数据就可以了。
刚才也说了,该项目有两部分数据,一部分叫做业务数据,一部分叫做用户行为数据。
一、用户行为数据的生成与传输
- 1,日志前端埋点,生成日志数据
- 2,日志服务器存储为日志文件
- 3,Flume采集落地hdfs
- 5,日志预处理
- 6,落hive数仓ODS层
1埋点技术介绍
1, 后端埋点代码示例(JAVA)
// 使用 ConcurrentLoggingConsumer 初始化收集器 DoitEventCollector
final DoitEventCollector sa = new DoitEventCollector(new DoitEventCollector.ConcurrentLoggingConsumer("日志路径"));
1,用户的商品浏览事件
// 用户的 Distinct ID
String distinctId = "ABCDEF123456789";
// 用户浏览商品
{
Map<String, Object> properties = new HashMap<String, Object>();
// '$time' 属性是系统预置属性,表示事件发生的时间,如果不填入该属性,则默认使用系统当前时间
properties.put("$time", new Date());
// '$ip' 属性是系统预置属性,如果服务端中能获取用户 IP 地址,并填入该属性
properties.put("$ip", "123.123.123.123");
// 商品 ID
properties.put("ProductId", "123456");
// 商品类别
properties.put("ProductCatalog", "Laptop Computer");
// 是否加入收藏夹,Boolean 类型的属性
properties.put("isAddedToFav", true);
// 记录用户浏览商品事件
sa.track(distinctId, true, "ViewProduct", properties);
支付订单事件
// 用户订单付款
{
// 订单中的商品 ID 列表
List<String> productIdList = new ArrayList<String>();
productIdList.add("123456");
productIdList.add("234567");
productIdList.add("345678");
Map<String, Object> properties = new HashMap<String, Object>();
// 用户 IP
properties.put("$ip", "123.123.123.123");
// 订单 ID
properties.put("OrderId", "abcdefg");
// 商品 ID 列表,List<String> 类型的属性
properties.put("ProductIdList", productIdList);
// 订单金额
properties.put("OrderPaid", 12.10);
// 记录用户订单付款事件
sa.track(distinctId, true, "PaidOrder", properties);
}
2 前端埋点代码示例(WEB JS)
1,初始化收集器,设置公共属性
<script>// 初始化 SDK
// 注册公共属性
collector.registerPage({
current_url: location.href,
referrer: document.referrer});
</script>
2,采集用户登录事件
<script>
// 业务代码执行登录动作
var user = userLogin();
//判断登录成功后,发送登录成功事件
if ......
collector.login(user.account)
<script>
3,采集用户添加购物车事件
collector.track('AddCart',
{
ProductName: "MacBook Pro",
ProductPrice: 15600.45,
IsAddedToFav: false,
}
);
二、数据的生成与传输,Sqoop/DataX
- 1,业务系统增删改数据库,形成业务数据
- 2,Sqoop/DataX 数据抽取。
- 3,落hive数仓ODS层
- 4,增量合并处理
所谓业务数据就是电商内的业务,由一些订单,优惠券,商品信息,评价等等业务组成,这些数据是以日志的格式打印出来,通过数据流保存到HDFS上进行备份,同时通过Hive处理。
用户行为数据,这部分数据是存在数据库中的,以表的形式存储进来,因为用户都是存库存表的,规律性很强,新增用户及商品增加,还有一些订单等信息,都是存在数据库中的,大公司一般会存在Oracle中,但是现在Oracle国内都在替换它,做迁徙方案,因为有一些国际因素,都在替换成MySQL的集群,我们目前使用的也是MySQL,但是现在只需要使用jar包生辰就可以了。
1、Sqoop
#! /bin/bash
##这个是sqoop的路径,date的日期
sqoop=/opt/module/sqoop/bin/sqoop
do_date=`date -d '-1 day' +%F`
if [ -n "$2" ]; then
do_date=$2
fi
##这是其中的一个函数,通过该函数可以进行sqoop的传输
##注意第一 $1 不是传入的参数,而是传入该函数的sql语句,$2 是传入数据库表名
##最后的hadoop是传入的lzo压缩格式
import_data(){
##如果是CDH环境,请把上面的路径注释掉,然后改为 sqoop import \
#sqoop import \
$sqoop import \
--connect jdbc:mysql://hadoop103:3306/gmall \
--username root \
--password 123456 \
--target-dir /origin_data_cdh/gmall/db/$1/$do_date \
--delete-target-dir \
--query "$2 and \$CONDITIONS" \
--num-mappers 1 \
--fields-terminated-by '\t' \
--compress \
--compression-codec lzop \
--null-string '\\N' \
--null-non-string '\\N'
hadoop jar /opt/module/hadoop-2.7.2/share/hadoop/common/hadoop-lzo-0.4.20.jar com.hadoop.compression.lzo.DistributedLzoIndexer /origin_data_cdh/gmall/db/$1/$do_date
}
## 函数中也分增量和全量表,看到where 1=1 的都是全量表,带有日期的都是增量表
import_order_info(){
import_data order_info "select
id,
final_total_amount,
order_status,
user_id,
out_trade_no,
create_time,
operate_time,
province_id,
benefit_reduce_amount,
original_total_amount,
feight_fee
from order_info
where (date_format(create_time,'%Y-%m-%d')='$do_date'
or date_format(operate_time,'%Y-%m-%d')='$do_date')"
}
import_coupon_use(){
import_data coupon_use "select
id,
coupon_id,
user_id,
order_id,
coupon_status,
get_time,
using_time,
used_time
from coupon_use
where (date_format(get_time,'%Y-%m-%d')='$do_date'
or date_format(using_time,'%Y-%m-%d')='$do_date'
or date_format(used_time,'$Y-%m-%d')='$do_date')"
}
import_order_status_log(){
import_data order_status_log "select
id,
order_id,
order_status,
operate_time
from order_status_log
where date_format(operate_time,'%Y-%m-%d')='$do_date'"
}
import_activity_order(){
import_data activity_order "select
id,
activity_id,
order_id,
create_time
from activity_order
where date_format(create_time,'%Y-%m-%d')='$do_date'"
}
##进行参数调用
case $1 in
"order_info")
import_order_info
;;
"base_category1")
import_base_category1
;;
"base_category2")
import_base_category2
;;
"base_category3")
import_base_category3
;;
"order_detail")
import_order_detail
;;
"first")
import_base_category1
import_base_category2
import_base_category3
import_order_info
import_order_detail
import_sku_info
import_user_info
import_payment_info
;;
"all")
import_base_category1
import_base_category2
import_base_category3
import_order_info
import_order_detail
import_sku_info
import_user_info
import_payment_info
import_base_trademark
;;
esac
1.1、sqoop自动化脚本
传参:1、业务库 2、表名 3、增量全量:all、inc 4、增量字段:field 5、指定日期
#1.识别业务库oracle、mysql的表结构,
#从oracle视图all_tab_cols中获取表结构字段信息
oracle_sql_text="select '#'||c.owner||'|'||c.TABLE_NAME||'|'||c.COLUMN_NAME||'|'||c.DATA_TYPE||'|'||c.COLUMN_ID
from all_tab_columns c
where c.owner in (${src_dbs})
and c.TABLE_NAME in (${src_tbl_names})
order by c.owner,c.TABLE_NAME,c.COLUMN_ID
"
sqlplus $src_user/$src_pwd@$src_conn <<EOF
set heading off feedback off pagesize 0 verify off echo off linesize 100 termout off
spool ${tmp_path}/all_tab_cols.txt;
${oracle_sql_text};
exit;
EOF
#从mysql的information_schema库COLUMNS表中获取表结构字段信息
mysql_sql_text="select concat('#',TABLE_SCHEMA,'|',TABLE_NAME,'|',COLUMN_NAME,'|',DATA_TYPE,'|',ORDINAL_POSITION) tbls
from COLUMNS
where TABLE_SCHEMA in ($src_dbs)
and TABLE_NAME in ($src_tbl_names)
"
mysql -u${src_user} -p${src_pwd} -h${src_conn} > ${tmp_path}/all_tab_cols.txt << EOF
use information_schema;
${mysql_sql_text};
EOF
--2、建表hive:拼接hive的ods表明字段等建表语句,字段最后拼接etl时间
Gen_Hive_DDL()
ddl_mysql=`awk -F '\t' 'NR>1{printf $1 $2 $3 $4}' ${schema_cfg} `
ddl_hive=`echo $ddl_mysql | sed -e 's/bigint/bigint/g' -e 's/int/bigint/g' -e 's/smallint/bigint/g' -e 's/tinyint/bigint/g' -e 's/decimal/decimal/g' -e 's/double/double/g' -e 's/float/double/g' -e 's/binary/binary/g' -e 's/varbinary/binary/g' -e 's/char/string/g' -e 's/varchar/string/g' -e 's/mediumtext/string/g' -e 's/text/string/g' -e 's/datetime/string/g' -e 's/time/string/g' -e 's/timestamp/string/g' -e 's/date/date/g' -e 's/json/map<string,string>/g' `
ddl_hive=$ddl_hive"
dml_type string ,
orig_etl_jobdate string ,
orig_etl_systime string
)
stored as parquet
location '/user/hive/warehouse/ods.db/${hive_table_name}';
"
#2、执行hive建表
hive -e ‘’
--3、拼接sqoop的 query:select 语句
if [ "${src_db_type}" == "oracle" ] && [ $num -eq 1 ]
then
where_condition=$where_condition" (${field} >= TO_DATE(\\'%BEG_DATE%\\',\\'yyyy-mm-dd hh24:mi:ss\\') AND ${field} < TO_DATE(\\'%END_DATE%\\',\\'yyyy-mm-dd hh24:mi:ss\\'))"
elif [ "${src_db_type}" == "oracle" ] && [ $num -ne 1 ]
then
where_condition=$where_condition" or (${field} >= TO_DATE(\\'%BEG_DATE%\\',\\'yyyy-mm-dd hh24:mi:ss\\') AND ${field} < TO_DATE(\\'%END_DATE%\\',\\'yyyy-mm-dd hh24:mi:ss\\'))"
elif [ "${src_db_type}" == "mysql" ] && [ $num -eq 1 ]
then
where_condition=$where_condition" (${field} >= STR_TO_DATE(\\'%BEG_DATE%\\',\\'%Y-%m-%d %H:%i:%s\\') AND ${field} < STR_TO_DATE(\\'%END_DATE%\\',\\'%Y-%m-%d %H:%i:%s\\'))"
elif [ "${src_db_type}" == "mysql" ] && [ $num -ne 1 ]
then
--3、执行sqoop
2、DataX
{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"column": [
"id",
"activity_name",
"activity_type",
"activity_desc",
"start_time",
"end_time",
"create_time"
],
"connection": [
{
"jdbcUrl": [
"jdbc:mysql://hadoop102:3306/gmall"
],
"table": [
"activity_info"
]
}
],
"password": "123456",
"splitPk": "",
"username": "root"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"column": [
{
"name": "id",
"type": "bigint"
},
{
"name": "activity_name",
"type": "string"
},
{
"name": "activity_type",
"type": "string"
},
{
"name": "activity_desc",
"type": "string"
},
{
"name": "start_time",
"type": "string"
},
{
"name": "end_time",
"type": "string"
},
{
"name": "create_time",
"type": "string"
}
],
"compress": "gzip",
"defaultFS": "hdfs://hadoop102:8020",
"fieldDelimiter": "\t",
"fileName": "activity_info",
"fileType": "text",
"path": "${targetdir}",
"writeMode": "append"
}
}
}
],
"setting": {
"speed": {
"channel": 1
}
}
}
}
注:由于目标路径包含一层日期,用于对不同天的数据加以区分,故path参数并未写死,需在提交任务时通过参数动态传入,参数名称为targetdir。
2.1、 DataX配置文件自动生成脚本
gen_import_config.py脚本
# coding=utf-8
import json
import getopt
import os
import sys
import MySQLdb
#MySQL相关配置,需根据实际情况作出修改
mysql_host = "hadoop102"
mysql_port = "3306"
mysql_user = "root"
mysql_passwd = "123456"
#HDFS NameNode相关配置,需根据实际情况作出修改
hdfs_nn_host = "hadoop102"
hdfs_nn_port = "8020"
#生成配置文件的目标路径,可根据实际情况作出修改
output_path = "/opt/module/datax/job/import"
#获取mysql连接
def get_connection():
return MySQLdb.connect(host=mysql_host, port=int(mysql_port), user=mysql_user, passwd=mysql_passwd)
#获取表格的元数据 包含列名和数据类型
def get_mysql_meta(database, table):
connection = get_connection()
cursor = connection.cursor()
sql = "SELECT COLUMN_NAME,DATA_TYPE from information_schema.COLUMNS WHERE TABLE_SCHEMA=%s AND TABLE_NAME=%s ORDER BY ORDINAL_POSITION"
cursor.execute(sql, [database, table])
fetchall = cursor.fetchall()
cursor.close()
connection.close()
return fetchall
#获取mysql表的列名
def get_mysql_columns(database, table):
return map(lambda x: x[0], get_mysql_meta(database, table))
#将获取的元数据中mysql的数据类型转换为hive的数据类型 写入到hdfswriter中
def get_hive_columns(database, table):
def type_mapping(mysql_type):
mappings = {
"bigint": "bigint",
"int": "bigint",
"smallint": "bigint",
"tinyint": "bigint",
"decimal": "string",
"double": "double",
"float": "float",
"binary": "string",
"char": "string",
"varchar": "string",
"datetime": "string",
"time": "string",
"timestamp": "string",
"date": "string",
"text": "string"
}
return mappings[mysql_type]
meta = get_mysql_meta(database, table)
return map(lambda x: {"name": x[0], "type": type_mapping(x[1].lower())}, meta)
#生成json文件
def generate_json(source_database, source_table):
job = {
"job": {
"setting": {
"speed": {
"channel": 3
},
"errorLimit": {
"record": 0,
"percentage": 0.02
}
},
"content": [{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": mysql_user,
"password": mysql_passwd,
"column": get_mysql_columns(source_database, source_table),
"splitPk": "",
"connection": [{
"table": [source_table],
"jdbcUrl": ["jdbc:mysql://" + mysql_host + ":" + mysql_port + "/" + source_database]
}]
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"defaultFS": "hdfs://" + hdfs_nn_host + ":" + hdfs_nn_port,
"fileType": "text",
"path": "${targetdir}",
"fileName": source_table,
"column": get_hive_columns(source_database, source_table),
"writeMode": "append",
"fieldDelimiter": "\t",
"compress": "gzip"
}
}
}]
}
}
if not os.path.exists(output_path):
os.makedirs(output_path)
with open(os.path.join(output_path, ".".join([source_database, source_table, "json"])), "w") as f:
json.dump(job, f)
def main(args):
source_database = ""
source_table = ""
options, arguments = getopt.getopt(args, '-d:-t:', ['sourcedb=', 'sourcetbl='])
for opt_name, opt_value in options:
if opt_name in ('-d', '--sourcedb'):
source_database = opt_value
if opt_name in ('-t', '--sourcetbl'):
source_table = opt_value
generate_json(source_database, source_table)
if __name__ == '__main__':
main(sys.argv[1:])
脚本使用说明
python gen_import_config.py -d database -t table
gen_import_config.sh脚本
#!/bin/bash
python ~/bin/gen_import_config.py -d gmall -t activity_info
python ~/bin/gen_import_config.py -d gmall -t activity_rule
python ~/bin/gen_import_config.py -d gmall -t base_category1
python ~/bin/gen_import_config.py -d gmall -t base_category2
python ~/bin/gen_import_config.py -d gmall -t base_category3
python ~/bin/gen_import_config.py -d gmall -t base_dic
python ~/bin/gen_import_config.py -d gmall -t base_province
python ~/bin/gen_import_config.py -d gmall -t base_region
python ~/bin/gen_import_config.py -d gmall -t base_trademark
python ~/bin/gen_import_config.py -d gmall -t cart_info
python ~/bin/gen_import_config.py -d gmall -t coupon_info
python ~/bin/gen_import_config.py -d gmall -t sku_attr_value
python ~/bin/gen_import_config.py -d gmall -t sku_info
python ~/bin/gen_import_config.py -d gmall -t sku_sale_attr_value
python ~/bin/gen_import_config.py -d gmall -t spu_info
mysql_to_hdfs_full.sh
#!/bin/bash
DATAX_HOME=/opt/module/datax
# 如果传入日期则do_date等于传入的日期,否则等于前一天日期
if [ -n "$2" ] ;then
do_date=$2
else
do_date=`date -d "-1 day" +%F`
fi
#处理目标路径,此处的处理逻辑是,如果目标路径不存在,则创建;若存在,则清空,目的是保证同步任务可重复执行
handle_targetdir() {
hadoop fs -test -e $1
if [[ $? -eq 1 ]]; then
echo "路径$1不存在,正在创建......"
hadoop fs -mkdir -p $1
else
echo "路径$1已经存在"
fs_count=$(hadoop fs -count $1)
content_size=$(echo $fs_count | awk '{print $3}')
if [[ $content_size -eq 0 ]]; then
echo "路径$1为空"
else
echo "路径$1不为空,正在清空......"
hadoop fs -rm -r -f $1/*
fi
fi
}
#数据同步
import_data() {
datax_config=$1
target_dir=$2
handle_targetdir $target_dir
python $DATAX_HOME/bin/datax.py -p"-Dtargetdir=$target_dir" $datax_config
}
case $1 in
"activity_info")
import_data /opt/module/datax/job/import/gmall.activity_info.json /origin_data/gmall/db/activity_info_full/$do_date
;;
"activity_rule")
import_data /opt/module/datax/job/import/gmall.activity_rule.json /origin_data/gmall/db/activity_rule_full/$do_date
;;
"base_category1")
import_data /opt/module/datax/job/import/gmall.base_category1.json /origin_data/gmall/db/base_category1_full/$do_date
;;
"base_category2")
面试提问
Sqoop方面
1、你们公司采用什么方式同步数据库中的数据到HDFS的?
2、你知道Sqoop1和Sqoop2的区别吗?
3、说说看Sqoop的运行机制?
4、你使用Sqoop是如何处理全量表和增量表的?具体实现可以详细说明一下吗?
5、同步你说的这些数据量用了大概多久时间?
Flume及Kafka方面
你这个项目如何传输日志文件的?
分别说一下你的两层传输。
为什么使用kafka?目的是什么?
你们的数据量是多少,kafka处理这些数据需要多久?
说说处理LZO你还知道哪些压缩格式。
Flume中你为什么采用memory channel?考虑过其它的channel吗?
数据存入到HDFS下是怎样的一种状态,你要用这些数据做什么?
可能会问Flume和Kafka的原理,说一下Flume和Kafka的原理?Flume的扇形模式(多sink或者多channel模式),Kafka的patitiaon,topic,officeset等等,这些都需要了解,如果传输到一半,突然失败了,再传输时kafka会怎么做,从头来还是接着上次来,为什么会这样?kafka如何落盘的?
————————————————