大数据-离线项目

第一章 需求分析

需求分析与设计

项目需求背景

"某APP上线后 经营得当  使用户 日活量增多 出现以下问题"

"营销分析断层:"
	市场营销成本居高不下,投放拉新的效果追踪出现断层,无法追踪各渠道实际转化率,难以准确分析 ROI。
"产品迭代无法量化:"
	缺少实时的用户行为分析能力,使产品功能不知道怎么改  改好了也不知道效果怎么样
"用户运营不精准:"
	“千人一面”的全量用户营销,投入产出难以把控,不精准的粗犷方式难以真正提升存量用户的长期活跃度。
"全局运营指标监控不实时:"
	有运营的 BI 系统,但运营指标监控不及时,未形成核心的指标预警机制,决策滞后

项目组成

"数据采集"
	数据从哪来,怎么来,到哪去
"数据仓库"
	各种数据的中央存储系统,提供数据的存储、管理和分析功能  为用户提供 数据报表 和决策支持
"可视化展示"
	将数据转为更方便查看的状态 如数据表、折线图、饼状图
"服务治理"
	对于数仓数据的元数据管理和用于维护集群环境的集群监控服务

开发目标

"开发一个综合性的数据采集平台、数据分析平台、可视化展示平台以及数据治理平台"

"技术架构"
	以HDFS作为最底层存储
	以Hive作为数仓基础设施
	以Spark作为核心运算引擎
	以Flume、Datax、Azkaban(任务调度)、Atlas(元数据管理)、Griffin(数据质量监测系统)等
	作为外围粘合辅助系统
	以Kylin/Clickhouse作为OLAP(联机数据分析)分析引擎
"前端展示"
	报表与数据可视化平台
	模型分析平台

需求分析

行为域基础分析

"行为域基础(流量)分析"
整体概况: 从产品整体的使用情况出发,对产品整体的使用情况有基础了解。
用户获取: 从获客渠道和版本的方向出发  观察用户更容易从那个渠道访问APP 
活跃与留存:从用户的访问和粘性出发,可以观察出产品在用户访问、回访等方面的趋势变化,
事件转化: 根据选择的事件和属性,生成该事件的发生次数、人数、分布等数据指标,可以了解整体的用户转化以及收益相关的数据情况。
用户特征:根据地址位置、性别、操作系统等一些基础属性,将用户进行分组,方便了解用户的分布占比情况。

"整体流量概况"
	每日新增多少用户  累计多少用户  每日的全部访问的人均次数/时长/深度
"访问渠道分析"
	每个渠道的用户的使用情况   各渠道新用户人均访问时长
"用户分布分析"
	用户的地址 性别  在那个渠道注册的  什么时候注册的
"APP版本分析"
	看每个版本的使用情况  版本访问流量  人均访问时长
"活跃度分析"
	帮助了解新老用户在使用产品时的一些行为特征  新老用户人均使用时长 访问次数
"用户留存/流失分析"
	提供用户7日,次日,次周,次月留存/流失的数据,帮助了解新老用户的使用粘性
"事件转化分析"
	各类关键事件(如收藏,分享,转发,加购等),发生次数、人数以及分布情况
	自定义收益类事件,神策自动生成该事件的发生次数、人数以及分布情况

用户留存:用户从指定时间开始,经历一段时间以后仍然有活跃行为,则记为1次留存。最常见的是新用户留存
用户流失:人为定义一个时间点为流失节点,比如用户12个月未登录之类。达到节点的,即为流失用户

行为域进阶分析

"转化漏斗分析"
	漏斗模型主要用于分析一个多步骤过程中每一步的转化与流失情况。
	如:浏览商品--添加购物车--结算购物车--点击付款--完成付款
"事件留存分析"
	留存分析是用来分析用户 参与情况/活跃程度 的分析模型   新生教程
	新手引导页面的优化   某个功能修改后的期望
"行为分布分析"
	告诉你 哪些功能比较常用   策略调整前后 提高用户复购率 提升核心用户量
"行为归因分析"  ***
	分析某个广告位、推广位对目标事件的转化贡献率
"行为路径分析"	
	主要用于分析用户在使用产品时的路径分布情况  
"行为间隔分析"
	产品,运营,市场等人员的日常工作都需要观察某某业务的转化情况
	复杂注册流程的整个过程花费的时长分布  新用户登录到第一次下单的间隔分布
"其他高阶分析"
	对于暂时无法满足的高级数据需求,提供了更加自由的自定义查询功能
"行为归因分析" ***

目标转化事件: 选择产品的目标事件  比如这个产品的目标是  客户付款这个事件
前向关键事件: 选择目标转化事件的前向事件 提升归因模型的计算精度 一般选择与目标事件有强关联的事件
关联属性      将目标转化事件和前向关键事件进行关联的属性
待归因事件    一般为与广告曝光、推荐曝光等运营相关事件,如:点击广告位、点击推荐位..

业务域分析

"业务域的数据大概包括交易域、营销域、运营活动域等等"

购物车分析  交易金额分析  复购率分析  优惠券分析  团购分析  
秒杀限时购分析  其他营销活动   广告运营位分析   拉新注册分析  会员分析(用户画像)

用户画像分析

"用户画像是根据用户社会属性、生活习惯和消费行为等信息而抽象出的一个标签化的用户模型。"
就是给用户贴标签    标签是通过对用户信息分析而来的高度精炼的特征标识

作用: 精准营销   根据历史用户特征,分析产品的潜在用户和用户的潜在需求  
	  用户统计   根据用户的属性、行为特征对用户进行分类后,统计不同特征下的用户数量、分布
	  数据挖掘   以用户画像为基础构建推荐系统、搜索引擎、广告投放系统,提升服务精准度
	  服务产品   对产品进行用户画像,对产品进行受众分析
	  行业报告&用户研究  通过用户画像分析了解行业动态,如人群消费习惯、消费偏好 不同地域品类消费差异

基本属性画像:性别  地域  注册时间  手机号   手机类型
行为习惯画像:月登陆次数  月下单次数  月收藏次数  月点赞次数
消费习惯画像:月消费金额  周消费金额  月优惠券使用金额

项目框架

"技术选型主要考虑因素:数据量大小、业务需求、行业内经验、技术成熟度、开发维护成本、总成本预算"

数据采集传输	Flume、 Kafka、 Sqoop、 DataX、 Logstash
数据存储:	 Mysql、 HDFS、  HBase、 Redis、 MongoDB
数据计算:	 Hive、  Tez、   Spark、 Flink、 Storm
数据查询:	 Presto、 Kylin、 Impala、 Druid
数据可视化    Echarts、 Superset、 QuickBI、  DataV
任务调度:	 Azkaban、 Oozie、    DolphinScheduler
集群监控:	 Zabbix、  Ganglia、  Prometheus
元数据管理    Atlas

系统数据流程设计
组件版本选型     框架选型尽量不要选择最新的框架,选择最新框架半年前左右的稳定版
服务器选型       物理机:    云主机
集群资源规划设计   确认集群规模  

流程设计

"数据生成"
	业务数据已经在业务系统的数据库中   历史数据   其他第三方数据
"数据采集汇聚"  
	行为域数据   业务域数据
"数据仓库&用户画像" 
	数据仓库   用户画像
"数据服务& OLAP分析平台"
	用户明细数据    固定报表查询   规范模型自助多维分析
"其他辅助系统"
	Azkaban/Oozie任务调度系统   Atlas元数据和血缘追溯管理(数据治理)

第二章 数据仓库

数仓分层

常见分层模型

大数据-离线项目_第1张图片

  • ODS层:原始数据层,存放原始数据,直接加载原始日志、数据,数据保持原貌不被处理。
  • DWD层:对ODS层数据进行清洗(去除空值,脏数据,超过极限范围的数据)、脱敏。
  • DWS层:以DWD层为基础,按天进行轻度汇总。一行信息代表一个主题对象一天的汇总行为,例如一个用户一天下单次数。
  • ADS层:为各种统计报表提供数据。

数据仓库为什么分层

  1. 拆解复杂任务:每一层只处理简单的任务,方便定位问题。
  2. 减少重复开发:规范数据分层,通过的中间层数据,能够减少计大的重复计算,增加一次计算结
    果的复用性。
  3. 隔离原始数据:不论始数据的异常还是数据的敏感性,使真实数据与统计数据解耦开。
  4. 清晰数据结构:每一个数据分层都有它的作用域,这样我们在使用表的时候能更方便地定位和理
    解。
  5. 方便数据血缘追踪:我们最终给业务呈现的是一个能直接使用业务表,但是它的来源有很多,如
    果有一张来源表出问题了,我们希望能够快速准确地定位到问题,并清楚它的危害范围。

数据集市与数据仓库

  • 面向目标不同
    • 数据集市则是一种微型的数据仓库,它通常有更少的数据,更少的主题区域,以及更少的历
      史数据,因此是部门级的,一般只能为某个局部范围内的管理人员服务。
    • 数据仓库是企业级的,额能为整个企业各个部门的运行提供决策支持手段。
  • 数仓存储整个企业内非常详细的数据;数据集市数据详细程度低一些,包含概要和综合数据多一
    些。
  • 数据集市的数据组织一般采用星形模型。大型数仓的数据组织,星形或雪花形都可以。
  • 数据集市较少保留历史数据。

数仓命名规范

表命名

  • ODS层命名为ods_表名
  • _DWD层命名为dwd_dim/fact_表名
  • DWS层命名为dws_表名
  • _DWT层命名为dwt_表名
  • ADS层命名为ads_表名
  • 临时表命名为xxx_tmp
  • 用户行为表,以log为后缀

脚本命名

  • 数据源 to 目标_db/log.sh
  • 用户行为脚本以log为后缀
  • 业务数据脚本以db为后缀

表字段类型

数量类型为bigint
金额类型为decimal(16, 2),表示:16位有效数字,其中小数部分2位
字符串(名字,描述信息等)类型为string
主键外键类型为string
时间戳类型为bigint

数仓理论

第三章 数据采集

埋点

埋点:植入业务系统 收集事件数据的SDK(工具代码)
后端埋点:java PHP  前端埋点:原生APP  页面JS
用于收据数据

行为数据采集开发

概述

埋点日志在本项目中,有3大类:
app端行为日志
pc web端行为日志
微信小程序端行为日志
日志生成在了公司的N台(5台)日志服务器中,现在需要使用flume采集到HDFS

Flume采集开发

采集数据
行为数据  通过埋点 记录用户行为存到文件中
业务数据  通过业务系统  如淘宝 京东   记录的是一条条事件

客户端 Client:		 生产数据		
事件 Event :		 一个数据单元 由消息头和消息体组成 
流 Flow: 	  	  Event 事件从源头到目的地的抽象过程
选择器 selector:   作用于源文件Source端 决定数据发往那个地方
拦截器 interceptor: Flume允许使用拦截器拦截数据  作用于 源文件 和 目标地址

代理	Agent : 一个独立的Flume进程,包含组件Source、 Channel、 Sink
源文件Source :数据收集组件 (文件  路径  命令)
管道 Channel:中转事件Event的一个临时存储,保存由Source组件传递过来的事件Event(内存  文件  数据表)
目标地址Sink ;从Channel中读取并移除Event, 将Event传递到下一个代理Agent(文件 HDFS Flume  数据库)

sources

Exec source 适用于监控一个实时追加的文件,但不能保证数据不丢失;(监听单个的文件)

Spooldir Source 能够保证数据不丢失,且能够实现断点续传,但延迟较高,不能实时监控;(监听文件个数的变化,不监听文件内容)

Taildir Source 既能够实现断点续传,又可以保证数据不丢失,还能够进行实时监控。建议使用

Avro 类型的Source:监听Avro 端口来接收外部avro客户端的事件流。avro-source接收到的是经过avro序列化后的数据,然后 反序列化数据继续传输。所以,如果是avro-source的话,源数据必须是经过avro序列化后的数据。利用 Avro source可以实现多级流动、扇出流、扇入流等效果。接收通过flume提供的avro客户端发送的日 志信息。

taildir source : 近实时读取指定的多组文件的新增行,它会记录偏移量
(如果一个被跟踪的文件,重命名了,这个文件会被taildir source重复采集)
(如何解决:修改taildir source的源码,将判断文件是否采集过的依据从“文件名”改成“inode号”)
这个source,允许为不同的文件组配置不同的header-key 和 header-value

# 命名此代理上的组件
a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.channels = c1
#类型
 a1.sources.r1.type = TAILDIR
 #文件组名称:可跟多个
 a1.sources.r1.filegroups = f1 
 #读取该文件夹下以app开头的文件
 a1.sources.r1.filegroups.f1 = /opt/module/applog/log/app.*
 #存储读取文件偏移量,断点续传的关键
 a1.sources.r1.positionFile = /var/log/flume/taildir_position.json
 #日志为1K一条时,batchSize设置为500最合适(在一个事物中可以处理多少个event,超过就会提交;batchSize一定不能大于transactionCapacity)
 a1.sources.r1.batchSize = 500
 #控制从同一个文件连续读取的最大次数量
 a1.sources.ri.maxBatchCount = 1000
 #是否添加存储绝对路径文件名的标头。
 a1.sources.r1.fileHeader = true
 #将绝对路径文件名附加到事件标头时使用的标头键。
 a1.sources.r1.fileHeaderKey=value--

batchSize——Maximum number of messages written to Channel in one batch 每批次写入channel的最大条数

capacity——The maximum number of events stored in the channel channle 容纳的最大event条数

transactionCapacity
——The maximum number of events the channel will take from a source or give to a sink per transaction
从source获取或传递给sink的每个事务(每个处理过程)最大event条数

batchSize <= transactionCapacity <= capacity
最后的对比关系是学来的,可以大概理解一下,如果写入channel量大于存储量或者处理量,则会造成数据堆积。如果每个处理过程条数大于存储条数,本身这句话就有歧义,就好比一个足球想放在打针用的注射器里面。

channel

## channel1
a1.sources.r1.channels = c1
 #channel类型
 a1.channels.c1.type = file
 #存放检查点的目录(断点续传的关键)
 a1.channels.c1.checkpointDir = /opt/module/flume/checkpoint/
 #数据存放目录,Filechannel会先把数据存在文件内
 a1.channels.c1.dataDirs = /opt/module/flume/data/
 #最大的一个文件的大小
 a1.channels.c1.maxFileSize = 2146435071
 #先把1000000字节的数据存在内存
 a1.channels.c1.capacity = 1000000
 #从内存写入到磁盘时,如果写入失败,会自动重试,6S如果还没有写入成功,断开
 a1.channels.c1.keep-alive = 6

sink

a1.sources.r1.channels = c1
#类型
 a1.sinks.k1.type = avro
 #连接avro source
 a1.sinks.k1.hostname = node03
 #端口号
 a1.sinks.k1.port = 4646

#描述接收器
a2.sinks.k1.channel = c1
a2.sinks.k1.type = HDFS
a2.sinks.k1.hdfs.path = hdfs://hdfs-yjx/logdata/applog/%Y-%m-%d/
a2.sinks.k1.hdfs.filePrefix = yjx-
a2.sinks.k1.hdfs.fileSuffix = .log.snappy
#多久生成一个新的文件
a2.sinks.k1.hdfs.rollInterval = 300
 #设置每个文件的滚动大小大概是128M
a2.sinks.k1.hdfs.rollSize = 104857600
#文件的滚动的Event数量100
a2.sinks.k1.hdfs.rollCount = 100
a2.sinks.k1.hdfs.fileType = CompressedStream
a2.sinks.k1.hdfs.codeC = snappy
a2.sinks.k1.hdfs.useLocalTimeStamp = true

拦截器开发

  • 从日志数据提取事件时间戳
  • flume获取自定义的拦截器对象,就是通过调用builder的 build()方法来获取
    • 为什么要这么设计
    • 就是为了让你自己控制拦截器的构造过程,可以往里面传递配置文件参数

关键问题

数据延迟达到的问题

  • 表现:后续流程处理中,有同时发现,2021-06-06文件夹中的日志,存在2021-06-05号的数据
  • 原因:数据上报延迟,而sink在分桶的时候用的是本地时间戳
  • 解决:配置了拦截器,获取日志时间戳,得到事件时间

数据积压的问题

  • 表现:后续处理流程中,有同事发现,到了17:30了,hdfs中的日志数据还在16:00
  • 原因:通道阻塞,数据积压。如何发现:同事反馈,自己确认(通过查看监控系统,发现下游的agent中,channel的数据填充率长期保持接近100%)
  • 解决:减少上游数据的发送节点,增加下游数据的接受节点,负载均衡

业务数据说明

业务域的数据来自业务系统的数据库,表模型非常多且关系复杂

实体表

实体表,是对一个事物(实体)进行属性描述的表(商品信息表,会员信息表)

事实表

对一件发生过的事情(事实)进行描述的表(订单表,优惠券领取、使用记录表)

字典表

一个“码”,对应一个名称

业务数据采集工具

Sqoop数据抽取工具

  • sqoop 是 apache 旗下一款“Hadoop中的各种存储系统(HDFS、HIVE、HBASE) 和关系数据库(mysql、
    oracle、sqlserver等)服务器之间传送数据”的工具。

  • 核心的功能有两个:

    导入(迁入)

    导出(迁出)

  • 导入数据:MySQL,Oracle 导入数据到 Hadoop 的 HDFS、HIVE、HBASE 等数据存储系统

  • 导出数据:从 Hadoop 的文件系统中导出数据到关系数据库 mysql 等 Sqoop 的本质还是一个命令行工
    具,和 HDFS,Hive 相比,并没有什么高深的理论。

  • 底层工作机制

将导入或导出命令翻译成 MapReduce 程序来实现
在翻译出的 MapReduce 中主要是对InputFormat 和 OutputFormat 进行定制

DataX数据抽取工具

  • DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL、Oracle、SqlServer、
    Postgre、HDFS、Hive、HBase、TableStore(OTS)、MaxCompute(ODPS)、DRDS 等各种异构数据源之间高
    效的数据同步功能。

  • 官网地址:https://github.com/alibaba/DataX

  • DataX本身作为离线数据同步框架,采用Framework + plugin架构构建。将数据源读取和写入抽象成为
    Reader/Writer插件,纳入到整个同步框架中。

    Reader:Reader为数据采集模块,负责采集数据源的数据,将数据发送给Framework。

    Writer: Writer为数据写入模块,负责不断向Framework取数据,并将数据写入到目的端。

    ramework:Framework用于连接reader和writer,作为两者的数据传输通道,并处理缓冲,流控,并发,数据转换等核心技术问题

业务数据采集开发

数据抽取策略

本层直接对接DATAX/SQOOP从业务库抽取过来的各类数据表

实体/字典表/维度表

  • 实体表小表(品类信息表,活动信息表,优惠券信息表等),每天抽取过来一份全量(或者一周、一月)
  • 实体表大表(商品信息表),每天抽取过来一份增量数据

事实表

  • 订单相关表
  • 优惠券领取使用记录表
  • 秒杀订阅记录表
  • 每天都会抽取一份增量数据

总原则

  • 小表——全量抽取
  • 大表(而且会变化)——增量抽取
原始数据层ODS
	从各个地方抽取来的源数据 进行简单分类
数据仓库层DW
	从ODS层获取的数据按照主题建立各种数据模型
数据服务层ADS
	生成具体的报表 供使用者进行分析

原始数据层ODS	数据仓库准备区,为DWD层提供基础原始数据
明细数据层DWD	为DW层提供来源明细数据,提供业务系统细节数据的长期沉淀
服务数据层DWS	为DW、ST层提供细粒度数据,细化成DWB和DWS;
主题数据层DWT	根据DW层数据按照各种维度或多种维度组合把需要查询数据进行汇总统计并作为单独的列进行存储
应用数据层ADS	面向用户应用和分析需求,包括前端报表、分析图表  面向最终结果用户

第四章 数据功能分析

数据入仓处理

行为域ODS开发

ODS层功能

ODS(Operation Data Store):操作数据层

  • 主要作用:直接映射操作数据(原始数据),数据备份;
  • 建模方法:与原始数据结构保持完全一致
  • 存储周期:相对来说,存储周期较短;视数据规模,增长速度,以及业务的需求而定;对于埋点日
    志数据ODS层存储,通常可以选择3个月或者半年;存1年的是土豪公司(或者确有需要,当然,也
    有可能是数据量很小)

表明命名规范:层级.主题数据域 功能_粒度
比如:app事件明细表: dwd.dwd_event_app_detai

入仓要求

  • 原始日志格式
    普通文本文件,JSON数据格式,导入hive表后,要求可以很方便地select各个字段
  • 分区表(减少扫描量)
  • 外部表(删除表时不会直接删除数据,相对效率较高)

入仓方案

  • Spark解析:通过Spark程序解析Json文件再写入Hive表中
  • getJsonObject():将整个Json看作一个字段先存入Hive表中,再通过Hive自带的函数
    (getJsonObject)解析再写入另一张表
  • JsonSerDe:通过Hive兼容的Json解析器直接将Json数据解析到一张表中。(Hive3.X之后提供原生
    的JsonSerDe)
创建表

​ 表的基础信息
​ 如何对一行数据进行解析
​ 文件是什么样的文件

ROW FORMAT:文件中的数据如何解析(json、csv、特殊分隔符文本文件,自定义格式数据)
STORED AS :是什么文件(textFile、ORC、Parquet ……)

入库脚本开发
入库命令
  • 入库命令
load data inpath 'hdfs://hdfs-yjx/yjx/app/ods/ods_app_event_log/dt=${start_date}'
into table ods.ods_app_event_log
partition(dt='${start_date}');

  • 存储符合分区格

修复分区

msck repair table ods.ods_app_event_log;

添加指定分区

alter table ods.ods_app_event_log add if not exists partition(dt='${start_date}');

脚本开发
#!/bin/bash

行为域DWD开发

DWD详细设计

存储规划
数据类型 源表 目标表
app端埋点日志 ods.ods_app_event_log dwd.dwd_app_event_detail
web端埋点日志 oods.ods_web_event_log dwd.dwd_web_event_detail
wx小程序埋点日志 ods.ods_wx_event_log dwd.dwd_wx_event_detail
技术选型

由于本层数据ETL的需求较为复杂,用hive sql实现非常困难
因而此环节将开发spark程序来实现
ETL:Extract+Transfrom+Load (Kettle(免费)、DataStage(收费))

需求分析

清洗过滤

去除json数据体中的废弃字段(前端开发人员在埋点设计方案变更后遗留的无用字段)

过滤掉json格式不正确的(脏数据)

过滤掉日志中缺少关键字段(deviceid/properties/eventid/sessionid 缺任何一个都不行)的记录!

过滤掉日志中不符合时间段的记录(由于app上报日志可能的延迟,有数据延迟到达)

对于web端日志,过滤爬虫请求数据(通过useragent标识来分析)

数据解析
  • 将Json打平,解析成扁平格式
  • properties字段不用扁平化,转成Map类型存储即可
SESSION分割

对于web端日志,按天session分割,不需处理

对于app日志,由于使用了会话保持策略,导致app进入后台很长时间后,再恢复前台,依然是同一个session,不符合session分析定义,需要按事件间隔时间切割(业内通用:30分钟)

对于wx小程序日志,与app类似,session有效期很长,需要按事件间隔时间切割(业内通用:30分钟)

数据规范处理
  • 数据口径统一

    Boolean字段,在数据中有使用1/0/-1标识的,也有使用true/false表示的,统一为Y/N/U

    字符串类型字段,在数据中有空串 ,有null值 ,统一为null值

    日期格式统一, 2020/9/2 2020-9-2 2020-09-02 20200902 都统一成 yyyy-MM-dd

    小数类型,统一成decimal

    字符串,统一成string

    时间戳,统一成bigint

数据集成
  1. 将日志中的GPS经纬度坐标解析成省、市、县(区)信息;(为了方便后续的地域维度分析)
  2. 将日志中的IP地址解析成省、市、县(区)信息;(为了方便后续的地域维度分析)

注:app日志和wxapp日志,有采集到的用户事件行为时的所在地gps坐标信息

web日志则无法收集到用户的gps坐标,但可以收集到ip地址

gps坐标可以表达精确的地理位置,而ip地址只能表达准确度较低而且精度较低的地理位置

ID_MAPPING(全局用户标识生成)
  • 为每一个用户每一条访问记录,标识一个全局唯一ID(给匿名访问记录,绑定到一个id上)

  • 选取合适的用户标识对于提高用户行为分析的准确性有非常大的影响,尤其是漏斗、留存、Session 等用户相关的分析功能。

  • 因此,我们在进行任何数据接入之前,都应当先确定如何来标识用户。

  • 本需求的意义:

    • 只使用deviceid来做用户标识的方案:

      一部设备上可能出现A账号和B账号,就会被认为是一个人;
      同一个账号,在不同的设备上登录使用,这些行为数据会被认为是多个人;

    • 只用account来做用户标识的方案:

      一部设备可能出现A账号和B账号,会被正确识别2个人;
      同一个账号,在不同设备上使用,这些行为数据会被正确识别为1个人;
      一部设备上,产生了一些匿名行为日志,那么这些数据该归属给谁?
      我们的做法是,看这部设备上最近一段时间内出现的账号登录次数谁多,就算给谁!
      (设备被动态绑定给了一个账号)

    • 本质上,就形成了“设备”和“账号”的绑定(动态)

    • 只要识别出来一个用户,则为这个用户专门生成一个整数类型的自增的全局唯一ID(guid)

新老访客标记
  • 新访客,标记为1
  • 老访客,标记为0
保存结果
  • 最后,将数据输出为parquet格式,压缩编码用snappy
  • 注:parquet和orc都是列式存储的文件格式,两者对于分析运算性的读取需求,都有相似优点在实际性能测试中(读、写、压缩性能),ORC略优于PARQUE

此处可以选择orc,也可以选择parquet,选择parquet的理由则是,parquet格式的框架兼容性更好,比如impala支持parquet,但不支持orc

ORC与Parquet总结对比
1、orc不支持嵌套结构(但可通过复杂数据类型如map间接实现),parquet支持嵌套结构
2、orc与hive的兼容性强,作为hive的常用存储格式
3、orc相比parquet的存储压缩率较高
4、orc导入数据和数据查询的的速度比parquet快

ORC与Parquet总结对比(补)

大数据-离线项目_第2张图片

orc.compress:表示ORC文件的压缩类型,「可选的类型有NONE、ZLB和SNAPPY,默认值是ZLIB(Snappy不支持切片)」—这个配置是最关键的。

parquet. compression:默认值为 UNCOMPRESSED,表示页的压缩方式。「可以使用的压缩方式有 UNCOMPRESSED、 SNAPPY、GZP和LZO」。

因为Hive 的SQL会转化为MR任务,如果该文件是用ORC存储,Snappy压缩的,因为Snappy不支持文件分割操作,所以压缩文件「只会被一个任务所读取」,如果该压缩文件很大,那么处理该文件的Map需要花费的时间会远多于读取普通文件的Map时间,这就是常说的「Map读取文件的数据倾斜」。

那么为了避免这种情况的发生,就需要在数据压缩的时候采用bzip2和Zip等支持文件分割的压缩算法。但恰恰ORC不支持刚说到的这些压缩方式,所以这也就成为了大家在可能遇到大文件的情况下不选择ORC的原因,避免数据倾斜。

ORC表支持None、Zlib、Snappy压缩,默认为ZLIB压缩。但这3种压缩格式不支持切分,所以适合单个文件不是特别大的场景。使用Zlib压缩率高,但效率差一些;使用Snappy效率高,但压缩率低。

Parquet表支持Uncompress、Snappy、Gzip、Lzo压缩,默认不压缩Uncompressed。其中Lzo压缩是支持切分的,所以在表的单个文件较大的场景会选择Lzo格式。Gzip方式压缩率高,效率低;而Snappy、Lzo效率高,压缩率低。

在Hve on Spark的方式中,也是一样的,Spark作为分布式架构,通常会尝试从多个不同机器上一起读入数据。要实现这种情况,每个工作节点都必须能够找到一条新记录的开端,也就需要该文件可以进行分割,但是有些不可以分割的压缩格式的文件,必须要单个节点来读入所有数据,这就很容易产生性能瓶颈。(下一篇文章详细写Spark读取文件的源码分析)

「所以在实际生产中,使用Parquet存储,lzo压缩的方式更为常见,这种情况下可以避免由于读取不可分割大文件引发的数据倾斜。 但是,如果数据量并不大(预测不会有超大文件,若干G以上)的情况下,使用ORC存储,snappy压缩的效率还是非常高的。

切分与不可切分理解(深度)

https://zhuanlan.zhihu.com/p/490729788

https://www.cnblogs.com/gentlemanhai/p/11275442.html

嵌套结构的模型(补)

parquet

https://blog.csdn.net/rlnLo2pNEfx9c/article/details/82880519

首先是嵌套结构的模型,此处选取的模型就跟 PB 类似. 多个 field 可以形成一个 group,一个 field 可以重复出现(叫做 repeated field),这样就简单地描述了嵌套和重复,没有必要用更复杂的结构如 Map / List / Sets,因为这些都能用 group 和 repeated field 的各种组合来描述. (熟悉 PB 的人,对这里说的东西应该很清楚,因为这就是跟 PB 一样的,如果此处有疑惑,最好的方法是立即左转出门去看一下 PB)
整个结构是从最外层一个 message 开始的. 每个 field 有三个属性:repetition、type、name. 一个 field 的 type 属性,要么是 group,要么是基本类型(int, float, boolean, string),repetition 属性,有以下三种:

a).required:出现,且只能出现 1 次.

b). 出现 1 或 0 次.

c). repeated:0 到 任意多次

例如,下边是一个 address book 的 schema.

message AddressBook {  required string owner;  repeated string ownerPhoneNumbers;  repeated group contacts {    required string name;    optional string phoneNumber;  }}

orc

https://www.cnblogs.com/ITtangtang/p/7677912.html

和Parquet不同,ORC原生是不支持嵌套数据格式的,而是通过对复杂数据类型特殊处理的方式实现嵌套格式的支持,例如对于如下的hive表:

CREATE TABLE `orcStructTable`(
`name` string,
`course` struct,
`score` map,
`work_locations` array
)

在ORC的结构中包含了复杂类型列和原始类型,前者包括LIST、STRUCT、MAP和UNION类型,后者包括BOOLEAN、整数、浮点数、字符串类型等,其中STRUCT的孩子节点包括它的成员变量,可能有多个孩子节点,MAP有两个孩子节点,分别为key和value,LIST包含一个孩子节点,类型为该LIST的成员类型,UNION一般不怎么用得到。每一个Schema树的根节点为一个Struct类型,所有的column按照树的中序遍历顺序编号。

ORC只需要存储schema树中叶子节点的值,而中间的非叶子节点只是做一层代理,它们只需要负责孩子节点值得读取,只有真正的叶子节点才会读取数据,然后交由父节点封装成对应的数据结构返回。

关键设计

  • GEOHASH编码介绍

    Geohash编码是一种地理位置编码技术,它可将一个gps坐标(含经、纬度)点,转化为一个字符串;
    通过编码后得到的字符串,表达的是:包含被编码gps坐标点的一个矩形范围;

  • GEOHASH编码原理

    在地球经纬度范围内,不断通过二分来划分矩形范围,通过观察gps坐标点所落的范围,来反复生成0/1
    二进制码

GeoHash算法的步骤:

根据经纬度计算GeoHash二进制编码
地球纬度区间是[-90,90], 北海公园的纬度是39.928167,可以通过下面算法对纬度39.928167进行逼近编
码:
1)区间[-90,90]进行二分为[-90,0),[0,90],称为左右区间,可以确定39.928167属于右区间[0,90],给标记
为1;
2)接着将区间[0,90]进行二分为 [0,45),[45,90],可以确定39.928167属于左区间 [0,45),给标记为0;
3)递归上述过程39.928167总是属于某个区间[a,b]。随着每次迭代区间[a,b]总在缩小,并越来越逼近
39.928167;
4)如果给定的纬度x(39.928167)属于左区间,则记录0,如果属于右区间则记录1,这样随着算法的进
行会产生一个序列1011100,序列的长度跟给定的区间划分次数有关。

组码

通过上述计算,纬度产生的编码为10111 00011,经度产生的编码为11010 01011。偶数位放经度,奇数
位放纬度,把2串编码组合生成新串:11100 11101 00100 01111。

最后使用用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,首先将11100 11101 00100 01111转
成十进制,对应着28、29、4、15,十进制对应的编码就是wx4g。同理,将编码转换成经纬度的解码算
法与之相反,具体不再赘述

  • 地理位置参考点映射

    因为在一个矩形范围可能没有办法相对精确的表示一个区,那我们可以提高精度,增加参考点,也
    就是说在一个地区下用数个矩形范围来填充并描述这个区域中的位置。
    使用 t_md_areas中的TB09_LNG、TB09_LAT

create external table dim.dim_area_dict
(
	geohash string,
	province string,
	city string,
	region string
)
	stored as parquet
location 'hdfs://hdfs-yjx/yjx/app/dim/dim_area_dict';


  • GEOHASH编码工具包
    gps坐标 转码成 geohash编码,这个算法不需要自己手写,有现成的工具包
<dependency>
	<groupId>ch.hsr</groupId>
	<artifactId>geohash</artifactId>
	<version>1.3.0</version>
</dependency>

api调用示例:

String geohashcode = GeoHash.withCharacterPrecision(45.667, 160.876547, 6).toBase32();

IP地址地理位置解析

ip地址数据形如:202.102.36.87
怎样才能解析为地理位置: 江苏省,南京市,电信
通过算法是无法从ip地址算出地理位置的
需要ip和地理位置映射字典才有可能做到,

  • IP查找算法

    将字典中的起始ip和结束ip,都设法转成整数,这样,ip地址段就变成了整数段
    接下来,将未知区域的ip按照相同方法转换成整数,则能相对方便地查找到字典数据了
    具体的搜索算法,可以使用二分查找算法

10.10.5.1 - 10.10.20.2 10 - 20 江苏省,南京市,雨花区
120.8.50.6 - 120.8.60.254 50 - 60 浙江省,杭州市,武林区
log -> ip:120.8.55.98 -> 55
10.10.5.1
00001010.00001010.00000101.00000001
10*256^3+10*256^2+5*256^1+1*256^0
——————————————————————————————————
IP地址:a.b.c.d
公式:a*256^3+b*256^2+c*256^1+d*256^0

IP地理位置处理工具包
  • 开源工具包ip2region(含ip数据库)
  • 项目地址: https://gitee.com/lionsoul/ip2region

使用方法:

  1. 引入 jar 包依赖

    <dependency>
    	<groupId>org.lionsoulgroupId>
    	<artifactId>ip2regionartifactId>
    	<version>1.7.2version>
    dependency>
    
    
  2. Api调用代码

    // 初始化配置参数
    val config = new DbConfig
    // 构造搜索器,dbFile是ip地址库字典文件所在路径
    val searcher = new DbSearcher(config, "initdata/ip2region.db")
    // 使用搜索器,调用查找算法获取地理位置信息
    val block = searcher.memorySearch("39.99.177.94")
    println(block)
    
    

难点设计ID_MAPPING

在登录状态下,日志中会采集到用户的登录id(account),可以做到用户身份的精确标识;而在匿名状
态下,日志中没有采集到用户的登录id
如何准确标识匿名状态下的用户,是一件棘手而又重要的事情;

一台设备登录多个用户

一个用户在多台设备登录

匿名登录一台设备

难点解析

在事件日志中,对用户能产生标识作用的字段有:
app日志中,有deviceid,account
web日志中,有cookieid,ip,account

备选方案
只使用设备 ID
  • 适用场景

    适合没有用户注册体系,或者极少数用户会进行多设备登录的产品,如工具类产品、搜索引擎、部分小
    型电商等。这也是绝大多数数据分析产品唯一提供的方案。

  • 局限性

    同一用户在不同设备使用会被认为不同的用户,对后续的分析统计有影响。
    不同用户在相同设备使用会被认为是一个用户,也对后续的分析统计有影响。
    但如果用户跨设备使用或者多用户共用设备不是产品的常见场景的话,可以忽略上述问题。

关联设备 ID 和登录 ID(一对一)
  • 适用场景

    成功关联设备 ID 和登录 ID 之后,用户在该设备 ID 上或该登录 ID 下的行为就会贯通,被认为是一个 全
    局 ID 发生的。在进行事件、漏斗、留存等用户相关分析时也会算作一个用户。
    关联设备 ID 和登录 ID 的方法虽然实现了更准确的用户追踪,但是也会增加复杂度。
    所以一般来说,我们建议只有当同时满足以下条件时,才考虑进行 ID 关联:
    需要贯通一个用户在一个设备上注册前后的行为。
    需要贯通一个注册用户在不同设备上登录之后的行为。

  • 局限性

    一个设备 ID 只能和一个登录 ID 关联,而事实上一台设备可能有多个用户使用。
    一个登录 ID 只能和一个设备 ID 关联,而事实上一个用户可能用一个登录 ID 在多台设备上登录。

关联设备 ID 和登录 ID(多对一)
  • 适用场景

    一个用户在多个设备上进行登录是一种比较常见的场景,比如 Web 端和 App 端可能都需要进行登录。
    支持一个登录 ID 下关联多设备 ID 之后,用户在多设备下的行为就会贯通,被认为是一个ID 发生的。

  • 局限性

    一个设备 ID 只能和一个登录 ID 关联(而事实上一台设备可能有多个用户使用)。
    一个设备 ID 一旦跟某个登录 ID 关联或者一个登录 ID 和一个设备 ID 关联,就不能解除(自动解除)。
    而事实上,设备 ID 和登录 ID 的动态关联才应该是更合理的。

关联设备 ID 和登录 ID(动态修正)
  • 基本原则,与方案3相同
  • 修正之处,一个设备ID被绑定到某个登陆ID(A)之后,如果该设备在后续一段时间(比如一个月内)被
    一个新的登陆ID(B)更频繁使用,则该设备ID会被调整至绑定登陆ID(B)

你可能感兴趣的:(hadoop,离线,大数据)