DMP说明:
DMP(Data Management Platform)数据管理平台,是把分散的多方数据进行整合纳入统一的技术平台,并对这些数据进行标准化和细分,让用户可以把这些细分结果推向现有的互动营销环境里的平台。
1.项目背景
互联网广告(本项目针对手机,OTT,PC)的崛起得益于信息技术的发展和普及,智能的终端设备迅猛的发展。
互联网广告的优势:
1)受众多 10亿+网民
2)可以跟踪用户的行为,进而可以做精准营销
2.dsp流程
如果用户是第一次进来,在DMP中没有信息,有默认的广告投放公司,比如可口可乐会去投(追求曝光率)
DSP主要是有两个属性,1就是广告,2就是DMP系统,里面有我们用户的信息(比如关注的物品的权重)
3.项目开发部分
项目开展之前进行一下操作,成为项目准备阶段
1)需求分析
这里简单的说几句,因为涉及到公司机密和技术要点,不便于详细说明,望理解。
dmp项目就是用来支撑精准广告投放的,主要是用用户画像,根据埋点采集的数据,进行ETL处理,然后进行数据分析,抽象出来一些标签,再根据用户行为数据,从不同维度数据的权重,给用户打上标签,实现广告精准投放。
2)技术架构
详细的请看下图:
3)技术选型
CDH5.16.1(hadoop2.6.x,hbase等)
Spark2.3(sparksql,sparkstreaming)
Java1.8
Scala2.11.8
kafka10.1
这里贴出几种类型数据展示一下:
1>.行为数据
0bb49045000057eee4ed3a580019ca06,0,0,0,100002,未知,26C7B9C83DB4B6197CEB80D53B3F5DA,1,1,0,0,2016-10-0106:19:17,139.227.161.115,com.apptreehot.horse,马上赚,AQ+KIQeBhehxf6xf98BFFnl+CV00p,A10%E55F%BC%E6%AO%B%,1,4.1.1,760,980,上海市,上海市,4,3,Wifi,0,0,2,插屏,1,2,6,未知,1,0,0,0,0,0,0,0,0,555,240,290,AQ+KIQeBhexf6x988FFnl+CVOOp,1,0,0,0,0,0,mm_26632353_8068780_27326559,2016-10-01 06:19:17,
2>.dmp检测数据
2019-08-15 00:07:35 {dmp:admaster;deviceid:DEVICE_948134c7097826d47ac1680388ef1811;adxtype:39;ip:112.23.190.244;dealid:d700cb8047d96660eef5dba91054f81f;4|1121394:1;4|1121393:0;}
3>.投放日志数据(默认分隔符"\001")
dsp_imp_uv_table.ip221.15.29.238
dsp_imp_uv_table.request_typeGET
dsp_imp_uv_table.url_hostdspo.htm
dsp_imp_uv_table.media_id17
dsp_imp_uv_table.creative_id2050317000
dsp_imp_uv_table.device_idDEVICE_b2f945a1ea91809b4832d8b160d01bd8
dsp_imp_uv_table.ts1551369958
dsp_imp_uv_table.str1-
dsp_imp_uv_table.str2-
dsp_imp_uv_table.uaCupid/3.0;NetType/unknown
dsp_imp_uv_table.str3-
dsp_imp_uv_table.http_type200
dsp_imp_uv_table.date_id20190301
以上为三种数据格式。
所以第一步要考虑数据存储问题,这里还考虑到以后要进行数据分析,数据量大(每天近乎10T采集数据),这里选用hdfs用于存放原始数据。
竟然选用了hadoop,那数据分析工具自然选用hive。对上述数据类型进行建表,完成数据仓库的ODS(临时存储层)层。
简单的列举几个sql:
表一:
create EXTERNAL table IF NOT EXISTS danp_data_db.dsp_imp_uv_table(
ip string,
request_type string,
url_host string,
media_id string,
creative_id string,
device_id string,
ts string,
str1 string,
str2 string,
ua string,
str3 string,
http_type string
)
partitioned by (date_id string)
row format delimited fields terminated by '\t'
stored as TEXTFILE
LOCATION '/hive_data/danp_data/dsp_get';
表二:
create EXTERNAL table x_test.dmp(
dmp string,
deviceid string,
adxtype string,
ip string,
dealid string,
audiences map<STRING,STRING>
)
partitioned by (date_id string,media string)
row format delimited fields terminated by '\001'
COLLECTION ITEMS TERMINATED BY ','
map keys terminated by '='
stored as TEXTFILE
LOCATION '/dmp_dataclean/dmp_table';
分区表刷新分区
MSCK REPAIR TABLE dmp_db.dmp_partition_table;
当我们对ODS层搭建完成,这时需要数据流入,所以写了定时脚本(crontab),让数据每天自己入库,相关脚本如下:
#!/bin/bash
export JAVA_HOME=/usr/share/java/jdk1.8.0_211
export CLASSPATH=.:${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
export PATH=$PATH:${JAVA_HOME}/bin
date_idv=`date -d "-1 day" +"%Y%m%d"`
echo "--------------------clean data date:$date_idv--------------------"
hdfs dfs -mkdir /hive_data/dmp/data/$date_idv;
for name in /data/dmp_data/*/*$date_idv.txt
do
hdfs dfs -put $name /hive_data/dmp/data/$date_idv;
echo "--------------------date into hdfs success:$name--------------------"
done;
echo "--------------------data clean begin--------------------"
hadoop jar /data/dmp_data/xyy_jars/DmpDataClean.jar work01 /hive_data/dmp/data/$date_idv /hive_data/dmp/data/$date_idv/output $date_idv;
echo "--------------------clean success--------------------"
hdfs dfs -mv /hive_data/dmp/data/$date_idv/output/* /hive_data/dmp/table;
hdfs dfs -mkdir /hive_data/dmp/dmp_partition_table/date_id=$date_idv;
echo "--------------------data analyze--------------------"
hive -e "insert overwrite directory \"/hive_data/dmp/dmp_partition_table/date_id=$(date -d "-1 day" +"%Y%m%d")\" select logtime, dmp, deviceid, adxtype, ip, dealid, audiences from dmp_db.dmp_table where logdate=\"$(date -d "-1 day" +"%Y-%m-%d")\" ";
echo "--------------------get data success--------------------"
echo "--------------------add patition--------------------"
hive -e "MSCK REPAIR TABLE dmp_db.dmp_partition_table;";
echo "--------------------everything be ok--------------------"
DmpDataClean.jar代码如下(公司要求,有删减)
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DataClean {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://"+args[0]+":8020");
Job job = Job.getInstance(conf);
job.setJarByClass(DataClean.class);
job.setMapperClass(DCmapper.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path(args[1]));
FileOutputFormat.setOutputPath(job, new Path(args[2]));
//FileInputFormat.setInputPaths(job, new Path("C:\\Users\\yxiong02\\Desktop\\data\\input"));
//FileOutputFormat.setOutputPath(job, new Path("C:\\Users\\yxiong02\\Desktop\\data\\output"));
job.setNumReduceTasks(0);
MyOut.setOutputName(job, args[3]);
boolean res = job.waitForCompletion(true);
System.exit(res?0:1);
}
}
class MyOut extends TextOutputFormat {
protected static void setOutputName(JobContext job, String name) {
job.getConfiguration().set(BASE_OUTPUT_NAME, name);
}
}
class DCmapper extends Mapper<LongWritable,Text,Text,NullWritable>{
protected void map(LongWritable key, Text value, Context context){
System.out.println("开始执行");
String line=value.toString();
System.out.println("原始数据---------->"+line);
String cleandata=null;
try{
cleandata = clean(line);
}catch (Exception e){
System.out.println("清洗失败~");
}
try{
if(cleandata!=null){
context.write(new Text(cleandata),NullWritable.get());
System.out.println("清洗数据---------->"+cleandata);
}
}catch (Exception e){
System.out.println(e);
}
}
public static String clean(String s){
Pattern pattern = Pattern.compile("([0-9]{4}-[0-9]{2}-[0-9]{2})\\s([0-9]{2}:[0-9]{2}:[0-9]{2})\\s\\{(.*dealid:\\w+);(.*);}");
Matcher matcher = pattern.matcher(s);
if(matcher.find()){
String data1=matcher.group(1);
String data2=matcher.group(2);
String data3=matcher.group(3);
String data4=matcher.group(4);
StringBuilder sb=new StringBuilder();
sb.append(data1+"\001");
sb.append(data2+"\001");
for(String s1:data3.split(";")){
String[] split = s1.split(":");
sb.append(split[1]+"\001");
}
Map<String,String> map=new HashMap();
if(data4.contains(";")){
for(String ds:data4.split(";")){
String[] a=((ds.split("\\|"))[1]).split(":");
map.put(a[0],a[1]);
}
}else {
String[] a=((data4.split("\\|"))[1]).split(":");
map.put(a[0],a[1]);
}
if(!map.isEmpty()){
String s1 = map.toString();
String substring = s1.substring(s1.indexOf("{") + 1, s1.indexOf("}"));
sb.append(substring);
return sb.toString();
}else {
return null;
}
}else {
return null;
}
}
}
以上均为让检测数据每天自动化流入ODS层。
以上工作完成之后,需要根据需求完成数据分析与提取。这里举一例:
select date_id,
dealid,
substr(logtime,1,2) as hours,
(map_keys(audiences))[0] as pg,
(map_values(audiences))[0] as zt,
count((map_values(audiences))[0]) as num
from dmp_db.dmp_partition_table
group by date_id,dealid,substr(logtime,1,2),(map_keys(audiences))[0],(map_values(audiences))[0]
order by date_id,dealid,hours
将抽取出来的数据存入PDW(数据仓库)层,降低表的复杂度,减少连表查询。
这里的相关工作同上面的脚本,也可以看到数据会自动加载到PDW层。
然后就是数据的MID(数据集市层)层:
-----为数据集市层,这层数据是面向主题来组织数据的,通常是星形或雪花结构的数据。从数据粒度来说,这层的数据是轻度汇总级的数据,已经不存在明细数据了。从数据的时间跨度来说,通常是PDW层的一部分,主要的目的是为了满足用户分析的需求,而从分析的角度来说,用户通常只需要分析近几年(如近三年的数据)的即可。从数据的广度来说,仍然覆盖了所有业务数据。
最后就是针对每一波投放,准备数据的APP(应用层)层
-----为应用层,这层数据是完全为了满足具体的分析需求而构建的数据,也是星形或雪花结构的数据。从数据粒度来说是高度汇总的数据。从数据的广度来说,则并不一定会覆盖所有业务数据,而是MID层数据的一个真子集,从某种意义上来说是MID层数据的一个重复。从极端情况来说,可以为每一张报表在APP层构建一个模型来支持,达到以空间换时间的目的数据仓库的标准分层只是一个建议性质的标准,实际实施时需要根据实际情况确定数据仓库的分层,不同类型的数据也可能采取不同的分层方法。
针对这一层的数据,处理逻辑会复杂一些,相关操作如下:
1>.整理训练集数据,在历史数据中心抽取60%做为训练集,提取相关字段,给每个维度定义相应的权重(维度:deviceid,sex,people,ip,address,saletype,cars…等)。
2>.第一步适当的降低维度,排除无关精要的维度,提高有用维度的权重,相关model如下
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import numpy as np
import matplotlib.pyplot as mp
import sklearn.linear_model as lm
import sklearn.metrics as sm
import pickle
#采集数据
x,y = [],[]
with open('./single.txt','r') as f:
for line in f.readlines():
data = [float(substr) for substr in line.split(',')]
x.append(data[:-1])
y.append(data[-1])
x = np.array(x)
y = np.array(y)
#创建模型,也就意味着选择了算法
model = lm.LinearRegression() #线性回归
#训练模型
model.fit(x,y)
#根据输入预测输出
pred_y = model.predict(x)
#打印每个样本的实际输出和预测输出
for true, pred in zip(y,pred_y):
print(true,'----->',pred)
mp.figure('Linear Regression', facecolor='lightgray')
mp.title('Linear Regression', fontsize=20)
mp.xlabel('x', fontsize=14)
mp.ylabel('y', fontsize=14)
mp.tick_params(labelsize=10)
mp.grid(linestyle=':')
mp.scatter(x, y, c='dodgerblue', alpha=0.75, s=60, label='Sample')
#得到输入从小到大的索引值
sorted_indices = x.T[0].argsort()
mp.plot(x[sorted_indices],pred_y[sorted_indices],c='orangered',label='Regression')
mp.legend()
mp.show()
#检测model:
import sklearn.metrics as sm
# 平均绝地值误差: 1/m * ∑|实际输出减去预测输出| 取绝地值
print(sm.mean_absolute_error(y,pred_y))
# 平均平方误差: SQRT(1/m * ∑(实际输出减去预测输出)^2)
print(sm.mean_squared_error(y,pred_y))
# 中位数绝对值误差:MEDIAN(|实际输出减去预测输出|)
print(sm.median_absolute_error(y,pred_y))
# R2得分 误差越接近于正无穷,r2值越接近于0,误差越接近0,r2值越接近1(相当于做了一次归一化)
print(sm.r2_score(y,pred_y))
因为算法为公司机密,这里就没有放源码,这里只是我自己写的小demo,望大佬们海涵。
其实model的作用就在于判断一个数据库里没有的数据,我们如何判断这一个流量吃不吃。至此dmp的数据存储和分析告一段落,但是在dsp投放的流程中,dmp只能算底层,接下来开始投放逻辑这一阶段。
第一:解决实时投放问题
当一个流量来了,要判断这个deviceid是否存在于我们库里面(这里的库指的是我们通过分析,存下来的人群数据。当然这里不会查询hive,这将是一个极其漫长的过程,在广告投放之前,我们会摘取数据先存放到redis里面,这里又涉及到一个技术点,docker,我们在docker里面搭建redis),如果存在,这个流量我们就吃,不存在我们再走数据分析阶段,通过之前离线分析的算法,实时对行为进行分析,然后再判断要不要投放,这里用到kafka+sparkstreaming的Receiver方式。
代码如下:
import org.apache.spark.SparkConf
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
/**
* Created by jy02268879 on 2018/7/19.
*
* Spark Streaming 基于 Receiver 对接Kafka
*/
object KafkaReceiver {
def main(args: Array[String]): Unit = {
if(args.length != 4){
System.err.println("Usage: KafkaReceiver <zkQuorum> <groupId> <topics> <numPartitions>")
System.exit(1)
}
val Array(zkQuorum, groupId, topics, numPartitions) = args
val sparkConf = new SparkConf().setAppName("KafkaReceiver").setMaster("local[3]")
val ssc = new StreamingContext(sparkConf,Seconds(5))
val topicMap = topics.split(",").map((_,numPartitions.toInt)).toMap
val messages = KafkaUtils.createStream(ssc,zkQuorum,groupId,topicMap)
messages.print()
messages.map(_._2).flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()
ssc.start()
ssc.awaitTermination()
}
}
只是流程代码,不包含逻辑。
第二:超时数据处理
对于超时数据的处理就很简单了,直接存入hdfs的ODS层,然后走批处理,被定时任务处理掉。
到这里我们的dmp项目大致完成,剩下的就是日常bug修复,考虑超时问题的解决方案。
这里再规整一下技术点:
sparkstreaming+kafka的Receiver模式,完成数据实时分析。
hive on spark提高数据处理速度。
线性回归和逻辑回归等相关算法进行数据分析(由于本人不是算法人员,只知道一个大概,望大佬们谅解)。
就先写到这里,之后代码整理好了再上传,或者相关逻辑的修改,也会更新,开发就是一个测试过程,看哪个技术更合适,所以慢慢来吧
地址在这里:http://www.cctry.com/thread-1-1-1.html
依赖版本<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version></dependency>错误现象启动流程实例时出现org.activiti.api.runtime.shared.Unp_dependency 'org.activiti:activiti-spring-boot-starter-basic:7.1.0.m6' not fo
论文阅读:Scene Graph Generation by Iterative Message Passing(CVPR17)
大学学的是计算机科学与技术专业,现在已经从事编码十几年,其实关于这个问题从理论上讲都是可以切入到程序员领域的!我们先从这两个专业着手分析,计科全称为计算机科学与技术专业,顾名思义:科学实际上指的就是计算机理论部分,技术呢,讲的就是实践的部分了,就是说,计科专业有理论也有实践,而且这个理论可不是简单的理论,就是说基本上关于计算机的相关理论,不管是软件还是硬件都会或多或少的接触到;软件工程的话,它和计科学的知识有些是一样的,但是不同的是,软件工程更注重将系统化的、严格约束的、可量化的方法应用于软件的_计科和软工怎么选
Jetson Nano 配置流程( 三)这篇写下nano下编译安装opencv 4.1.1流程,以及一些填坑。1.卸载系统中老版本opencvsudo apt-get purge libopencv*sudo apt autoremovesudo apt-get update2.安装依赖项sudo apt-get install build-essentialsudo apt-get install libglew-dev libtiff5-dev zlib1g-dev libjpeg-_jetson nano 编译opencv
1名A + ( が) あっての+名B表示条件,“正因为有了A,才有B的存在”。①前项和后项是不同的名词。强调前项的助词「が」可以省略,后项也会以「もの」、「こと」的形式出现,如「~あってのことだ」、「~あってのものだ」。②意思与「~があるからこそ」相同,“正因为……才”。例:あなたあっての私。【因为有了你才有了我。】例:今の
力扣88 - 合并两个有序数组,将两个有序数组做一个融合_力扣88
Linux和ubuntu的关系linux简介:生日:1991年10月5日类unix操作系统遵循posix标准信仰:一切皆文件linux与unix的关系:ios与android都是基于unix开发出来的,linux是基于unix开发出来的。linux是一个内核,最开始是没有图形化界面的。随着时代的发展在linux的基础上开发了debian,再到现在的ubuntu。linux基本命令的使用shell是什么?shell是操作系统的用户界面,提供了用户和内核进行交互操作的_unudtulinx
公司人物画像项目数据量非常大,技术上准备使用kafka,以前只使用过RabbitMq,但不是适合大数据的处理,所以加强学习下kafka,希望今年能有很好的提升。公司开发环境Windows10系统,以前使用过Docker安装过redis,mongoDb感觉使用非常方便,所以打算继续使用Docker安装kafka;至于Docker安装很简单,到官网下载安装即可https://www.docker....
影响范围:apache solo < 8.2solr默认使用端口为8983一:获取solr索引库core的名称http://192.168.96.79:8983/solr/admin/cores查看name字段为atom二:判断该索引库是否使用dataimporthandler模块Atom为根据一找到的库名http://192.168.96.79:8983/solr/atom..._如何判断solr 1.0 是否启用dataimporthandler模块
畅通工程Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 16994 Accepted Submission(s): 7134Problem Description省政府“畅通工程”的目标是使全省任何两个
连接过程可能出现的错误情况有:(1) 如果客户机TCP协议没有接收到对它的SYN数据段的确认,函数以错误返回,错误类型为ETIMEOUT。通常TCP协议在发送SYN数据段失败之后,会多次发送SYN数据段,在所有的发送都高中失败之后,函数以错误返回。注:SYN(synchronize)位:请求连接。TCP用这种数据段向对方TCP协议请求建立连接。在这个数据段中,TCP协议将它选择的初始序列号通知对方..._客户端与vone服务器建立tcp连接失败0x274c