项目说明
基于litemall构建的数据仓库
核心模块
- 数据仓库(离线/实时)
- 推荐系统
- 用户画像
使用技术
- 大数据
- Hadoop
- hive on tez
- sqoop
- spark
- kafka
- azkaban
- 后端
- spring boot
- mysql
- redis
- mongo
- openresty
运行环境
- Hadoop主机
- Hadoop:3.1.2
- hive:3.1.2
- tez:0.10.1 结合对应的hive版本进行源码编译
- sqoop:1.4.7
- spark $\ge$2.4.7
- Kafka
- zookeeper:3.6.1
- Kafka:2.13-2.6.0
- CDH:6.3.2
数据准备
埋点
处理用户行为数据,采用json数据格式
- openresty采集数据发送到kafka
-
创建topic
kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 2 --partitions 3 --topic litemall-action
-
配置nginx
-
生成工作目录
mkdir -p {litemall/logs,litemall/conf,litemall/luas}
-
配置nginx
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
user root root; # 定义用户名、用户组,防止权限问题
http {
# 设置共享字典
lua_shared_dict kafka_data 10m;
resolver 192.168.241.20; #使用能解析kafka域名的dns
server {
listen 80;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("<p>hello, world</p>")
}
}
location /process{
default_type application/json;
content_by_lua_file /root/project/litemall/luas/kafka_test.lua;
}
}
}
-
发送到kafka
local cjson=require "cjson"
local producer = require "resty.kafka.producer"
local broker_list = {
{ host = "192.168.241.180", port = 9092 },
{ host = "192.168.241.181", port = 9092 },
{ host = "192.168.241.182", port = 9092 },
}
local topic="litemall-action"
local partitions=3
local shared_data = ngx.shared.kafka_data
local partitionNum=shared_data:get("count")
if not partitionNum then
shared_data:set("count",1)
partitionNum=shared_data:get("count")
end
local key = ""..(partitionNum%partitions)
shared_data:incr("count",1)
ngx.req.read_body()
local request_body = ngx.req.get_body_data()
if request_body then
local p = producer:new(broker_list)
local offset, err = p:send(topic, key, request_body)
end
ngx.say(cjson.encode{code = 200, msg = request_body})
- 页面埋点
- 不同日志的处理:监听生命周期函数
- 启动日志:在
main.js通过绑定vue对象的mounted事件来处理,保证只有一次请求
- 事件日志
- 在
main.js通过绑定vue对象的mounted和beforeDestory来启动或清除定时器
- 在定时器中发送事件日志,消除频繁请求日志服务器
- 采用数组来记录事件,并在发送之后清空
- 跨域的处理
-
前端通过代理处理(修改vue.config.js配置文件,vue cli 3.0+)
devServer: {
port:6255,
proxy:{
'/process': {
target: process.env.VUE_LOG_BASE_API,
ws: true,
changeOrigin: true //允许跨域
},
'/wx': {
target: process.env.VUE_APP_BASE_API,
ws: true,
changeOrigin: true
}
}
},
-
后端通过nginx处理
location /process{
default_type application/json;
# 添加响应头,在opthons请求返回的响应中,从而在下个请求中携带
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers *;
add_header Access-Control-Allow-Origin *;
content_by_lua_file /root/project/litemall/luas/kafka_test.lua;
}
预处理
将数据导入hdfs
hdfs dfs -mkdir -p {/original_data/litemall/db,/original_data/litemall/log}
-
将事件日志导入hdfs
- 自定义flume的interceptor将数据拆分为start和event类型的数据,并存储到hdfs中不同的文件夹
- 打包之后的jar放入到flume的
plugins.d/interceptor/lib文件夹下
plugins.d文件夹用于集成第三方的plugin
- flume守护进程问题
-
原因:启动配置参数-c设置不正确
-
解决:
FLUME_HOME=/opt/apache-flume-1.9.0-bin
# 需要配置到flume安装路径下的conf文件夹
nohup $FLUME_HOME/bin/flume-ng agent -c $FLUME_HOME/conf -f $FLUME_HOME/conf/litemall_kafka_to_hdfs.conf --name a1 -Dflume.root.logger=INFO,console >$FLUME_HOME/logs/litemall 2>&1 &
- 处理hive报错:Cannot obtain block length for LocatedBlock
- 在跑Azkaban任务的时候出现以上错误,其原因是:文件处于
openforwrite的状态,没有关闭
- 解决:
-
查看文件状态
hdfs fsck dir -files
-
查看路径下是否有openforwrite的文件
hdfs fsck dir -openforwrite
-
释放租约
hdfs debug recoverLease -path filepath [-retries retry-num]
- 分析:
- 没有关闭flume重启了hdfs
-
将业务数据导入hdfs
- sqoop将mysql中
tinyint的数据在HDFS 上面显示的true、false及在hive中需要使用boolean存储
- 原因:jdbc会把tinyint 认为是
java.sql.Types.BIT
- 解决:
jdbc:mysql://localhost/test?tinyInt1isBit=false
- sqoop:源数据含有默认换行符导致的数据不正确(出现数据错位)
-
解决:import添加参数
# Drops \n, \r, and \01 from string fields when importing to Hive.
--hive-drop-import-delims
- 支付和退款数据
- 因该数据都存在于订单表中,所以进行特殊处理
- 支付:除了分区数据以外,同时添加
pay_time作为过滤条件
- 退款:除了分区数据以外,同时添加
refund_time作为过滤条件
-
自定义hive函数来解析events事件日志
-
hive-function:中定义UDF和UDTF
-
将打包后的jar包上传到hdfs
hdfs dfs -put hive-function.jar /user/hive/jars
-
注册全局函数
# udf
create function litemall.parse_json_object as 'org.tlh.litemall.udf.ParseJsonObject' using jar 'hdfs:///user/hive/jars/hive-function.jar'
# udtf
create function litemall.extract_event_type as 'org.tlh.litemall.udtf.ExtractEventType' using jar 'hdfs:///user/hive/jars/hive-function.jar'
-
对于分区表,新增加列,重新覆盖写已有的分区,新增列为null
-
原因:由于分区元数据缺少新添加的字段导致的
-
解决:往出错的分区元数据中添加这个列
alter table dwd_fact_cart_info partition(dt = '2020-12-04') add columns(add_time string comment '加购时间');
-
加载时间维度数据
LOAD DATA INPATH '/date_info.txt' OVERWRITE INTO TABLE dwd_dim_date_info;
导出数据
- 将数据导出到mysql数据重复
- 对于以日期为基础的数据
- 如果只有一条,则mysql中日期列设置为主键
- 如果是多条,则hive中需要以日期进行分区
- 目前通过Sqoop从Hive的parquet抽数到关系型数据库的时候会报kitesdk找不到文件的错,这是Sqoop已知的问题
- 将需要导出的表存储方式采用默认的
textfile
推荐系统
加载hive中的数据
-
启动hiveserver
hive --service hiveserver2
-
spark配置通过域名通信
conf.set("dfs.client.use.datanode.hostname", "true")
-
注意事项
- mac环境下读取hive中lzo压缩的数据
-
使用homebrew安装lzo、lzop
brew install lzop lzo
-
编译安装hadoop-lzo
git clone [email protected]:twitter/hadoop-lzo.git
mvn clean package install -DskipTests
用户画像
环境准备
-
创建画像namespace
create_namespace 'litemall_profile'
将用户标签导入solr
-
开启hbase表的复制功能
# 禁用表
disable 'litemall_profile:user_profile'
# 开启表复制
alter 'litemall_profile:user_profile',{NAME=>'cf',REPLICATION_SCOPE =>1}
# 启用表
enable 'litemall_profile:user_profile'
-
创建solr的collection
-
生成配置
solrctl instancedir --generate $HOME/hbase_indexer/litemall
-
定义schema
cd $HOME/hbase_indexer/litemall/conf
# 修改managed-schema配置,添加如下内容
vim managed-schema
<field name="gender" type="string" indexed="true" stored="true"/>
<field name="age" type="string" indexed="true" stored="true"/>
<field name="consumptionAbility" type="string" indexed="true" stored="true"/>
<field name="consumption" type="string" indexed="true" stored="true"/>
<field name="coupon" type="string" indexed="true" stored="true"/>
<field name="discountRate" type="string" indexed="true" stored="true"/>
<field name="singleOrderMax" type="string" indexed="true" stored="true"/>
<field name="orderFrequency" type="string" indexed="true" stored="true"/>
<field name="pct" type="string" indexed="true" stored="true"/>
<field name="refundRate" type="string" indexed="true" stored="true"/>
<field name="rfm" type="string" indexed="true" stored="true"/>
<field name="psm" type="string" indexed="true" stored="true"/>
<field name="brandFavor" type="string" indexed="true" stored="true"/>
-
创建solr的collection并上传配置
# 上传配置
solrctl instancedir --create litemall $HOME/hbase_indexer/litemall
# 创建collection
solrctl collection --create litemall -s 3 -r 3 -m 9
-
创建Lily HBase Indexer 配置
-
定义morphline 和 hbase的映射配置
cd $HOME/hbase_indexer/litemall
vim morphline-hbase-mapper.xml
<?xml version="1.0"?>
<!--
table: hbase中表名
-->
<indexer table="litemall_profile:user_profile" mapper="com.ngdata.hbaseindexer.morphline.MorphlineResultToSolrMapper">
<!-- 设置morphlines文件的路径:绝对路径或相对路径 -->
<param name="morphlineFile" value="morphlines.conf"/>
<!-- 设置使用的是那个ETL -->
<param name="morphlineId" value="userProfile"/>
</indexer>
-
创建Morphline配置文件(主要用于导入历史数据),定义ETL(hbase-solr的转换关系),同时将userProfile的配置信息通过CDH控制台页面进行添加(Lily HBase Indexer->配置->Morphlines 文件),注册一个新的ETL
cd $HOME/hbase_indexer/litemall
vim morphlines.conf
# 内容如下
SOLR_LOCATOR : {
# Name of solr collection
collection : collection
# ZooKeeper ensemble
zkHost : "$ZK_HOST"
}
morphlines : [
{
id : userProfile
importCommands : ["org.kitesdk.**", "com.ngdata.**"]
commands : [
{
extractHBaseCells {
mappings : [
{
inputColumn : "cf:gender"
outputField : "gender"
type : string
source : value
}
{
inputColumn : "cf:age"
outputField : "age"
type : string
source : value
}
{
inputColumn : "cf:consumptionAbility"
outputField : "consumptionAbility"
type : string
source : value
}
{
inputColumn : "cf:consumption"
outputField : "consumption"
type : string
source : value
}
{
inputColumn : "cf:coupon"
outputField : "coupon"
type : string
source : value
}
{
inputColumn : "cf:discountRate"
outputField : "discountRate"
type : string
source : value
}
{
inputColumn : "cf:singleOrderMax"
outputField : "singleOrderMax"
type : string
source : value
}
{
inputColumn : "cf:orderFrequency"
outputField : "orderFrequency"
type : string
source : value
}
{
inputColumn : "cf:pct"
outputField : "pct"
type : string
source : value
}
{
inputColumn : "cf:refundRate"
outputField : "refundRate"
type : string
source : value
}
{
inputColumn : "cf:rfm"
outputField : "rfm"
type : string
source : value
}
{
inputColumn : "cf:psm"
outputField : "psm"
type : string
source : value
}
{
inputColumn : "cf:brandFavor"
outputField : "brandFavor"
type : string
source : value
}
]
}
}
{ logDebug { format : "output record: {}", args : ["@{}"] } }
]
}
]
-
注册Lily HBase Indexer Configuration 和 Lily HBase Indexer Service
hbase-indexer add-indexer \
--name litemallIndexer \
--indexer-conf $HOME/hbase_indexer/litemall/morphline-hbase-mapper.xml \
--connection-param solr.zk=cdh-master:2181,cdh-slave-3:2181,cdh-slave-2:2181/solr \
--connection-param solr.collection=litemall \
--zookeeper cdh-master:2181,cdh-slave-3:2181,cdh-slave-2:2181
-
查看Indexer
hbase-indexer list-indexers
-
导入历史数据
hadoop --config /etc/hadoop/conf \
jar /opt/cloudera/parcels/CDH/lib/hbase-solr/tools/hbase-indexer-mr-*-job.jar \
--conf /etc/hbase/conf/hbase-site.xml -Dmapreduce.map.java.opts="-Xmx512m" \
-Dmapreduce.reduce.java.opts="-Xmx512m" \
--morphline-file $HOME/hbase_indexer/litemall/morphlines.conf \
--hbase-indexer-file $HOME/hbase_indexer/litemall/morphline-hbase-mapper.xml \
--zk-host cdh-master:2181,cdh-slave-3:2181,cdh-slave-2:2181/solr \
--collection litemall \
--go-live
-
solr数据备份与恢复(将其他集群中的数据导入)
-
备份其他集群的数据
-
创建快照
solrctl collection --create-snapshot litemall_bk -c litemall
-
创建备份目录
hdfs dfs -mkdir -p /backup/litemall_16
# 修改权限
sudo -u hdfs hdfs dfs -chown solr:solr /backup/litemall_16
-
准备导出(目的导出元数据合索引数据)
solrctl collection --prepare-snapshot-export litemall_bk -c litemall -d /backup/litemall_16
-
导出
solrctl collection --export-snapshot litemall_bk -s /backup/litemall_16 -d hdfs://cdh-master:8020/backup/bk
-
复制到本地
hdfs dfs -copyToLocal /backup/bk litemall
tar -zcvf litemall.tar.gz litemall/
-
恢复到当前集群
-
创建目录上传到出的数据
hdfs dfs -mkdir -p /path/to/restore-staging
hdfs dfs -copyFromLocal litemall /path/to/restore-staging
-
数据恢复
solrctl collection --restore litemall -l /path/to/restore-staging/litemall -b litemall_bk -i restore-litemall
-
查看状态
solrctl collection --request-status restore-litemall
注意事项
- CDH中使用lzo压缩,本地读取数据问题(报
No LZO codec found, cannot run.错误)
-
原因:在hadoop-common包中使用的是SPI来加载解压缩方式,默认配置中并不包含lzo的配置
-
解决:添加core-site.xml文件,并添加lzo解压缩配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property>
<name>io.compression.codecs</name>
<value>
org.apache.hadoop.io.compress.DefaultCodec,
org.apache.hadoop.io.compress.GzipCodec,
org.apache.hadoop.io.compress.BZip2Codec,
org.apache.hadoop.io.compress.DeflateCodec,
org.apache.hadoop.io.compress.SnappyCodec,
org.apache.hadoop.io.compress.Lz4Codec,
com.hadoop.compression.lzo.LzoCodec,
com.hadoop.compression.lzo.LzopCodec
</value>
</property>
</configuration>
术语
- 支付转换率:所选时间内,支付买家数除以访客数(支付买家数/访客数),即访客转化为支付买家的比例
$$
支付转换率={支付买家数 \over 访客数}
$$
- 客单价:每一个顾客平均购买商品的金额,也即是平均交易金额
$$
客单价={销售额 \over 成交顾客数}
$$
致谢
本项目基于或参考以下项目:
- litemall:又一个小商场系统,Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端
评论