一、数据仓库电商项目——数据的生成与传输

电商大数据项目,就是将电商的业务数据以及用户的行为数据进行ETL,最后清洗出来有用的数据进行展示。
这个项目中的数据都是仿造出来的,真正生产线上的数据也是通过埋点得到相应规则的数据,不过省略了这一步,现在靠做好的jar包生产数据就可以了。
刚才也说了,该项目有两部分数据,一部分叫做业务数据,一部分叫做用户行为数据。

电商项目架构图

一、用户行为数据的生成与传输

  1. 1,日志前端埋点,生成日志数据
  2. 2,日志服务器存储为日志文件
  3. 3,Flume采集落地hdfs
  4. 5,日志预处理
  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. 1,业务系统增删改数据库,形成业务数据
  2. 2,Sqoop/DataX 数据抽取。 
  3. 3,落hive数仓ODS层
  4. 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如何落盘的?
————————————————
 
原文链接:https://blog.csdn.net/weixin_29057619/article/details/109819480

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四月天03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值