市场项目交接文档初稿
市场项目交接,一个需求的解决逻辑
一、首先拿到需求
分析一下需求,需要提数的内容
移除需求二、从web层入手,找到mongodb中的表。
我们获取到php的代码,从源头解决该需求;因为php的关系,我们可以直接通过url地址确定到代码段。
首先我们根据页面,查到:推送部署,eclipse使用ctrl-h。
移除点击此处添加图片说明文字一下子就定位到哪个php页面了,可以看到这个页面的表单名称:
移除点击此处添加图片说明文字然后根据需求,我们需要: 查询具体内容:message用户数(UV)、展现用户数(UV)、下载用户数(UV)、安装用户数(UV)四个数据维度成功的手机型号、分辨率、android版本
那,php代码里,这里可以确定字段名称和字段的变量。
移除点击此处添加图片说明文字接下来干嘛咧,没错,就是通过数据源找到数据库连接配置和语句。我之前的php代码增删改查文档派上了用场,我们来看一下这段代码:
移除点击此处添加图片说明文字还等什么,点进去看看何方神圣。
然后这段代码里面,我们看到了数组结果变成result变量的过程,代码在这里
移除点击此处添加图片说明文字可以,那我们是现在确定了是这张表。数据的最终来源于这张表:push_detail_statistics_deploy
ok,那我们看一下架构图,发现其实mongodb的数据来自于后台hive的推送,那么推送的过程是写到定时器里面了,定时器每天定时来做推送。所以,接下来,我们要去查看推送的定时器,推送了啥。
请注意,因为服务器发布在不同的集群节点上,所以,这里我们需要去询问服务器集群的定时器crontable位置。
三、从crontable中找到定时推送的python代码
我们来查看从hive到mongodb的过程。拿到了服务器集群的地址和用户名密码,192.168.0.141,root,密码:好运,走起,我们去看看。嗯,很轻松就找到了
移除点击此处添加图片说明文字关于crontable的用法,我现在就粘贴一部分demo:
设置系统计划任务
修改/etc/crontab
添加对应的操作
* * * */1 * root reboot 每个月重启一次
第一个* 表示 “分钟” 0~59 可单个指定,用逗号分开 "*/2" 表示每两分钟运行一次
第二个* 表示 “小时” 0~23 可单个指定,用逗号分开 "*/1" 表示每个小时运行一次
第三个* 表示 “天数” 0~30 可单个指定,用逗号分开 "*/4" 表示这个月每4天运行一次
第四个* 表示 “月数” 1~12 可单个指定,用逗号分开 "*/3" 表示每个季度运行一次
第五个* 表示 “星期几” 0~6 单个指定,用逗号分开
具体实例:
23 6 5 12 * root rebooot 表示,在每年的12月5号的早晨6点23分00秒以root用户执行一次重启
5 */1 1 1 * root sh /etc/happy.sh 表示每年的元旦那天,每个小时的5分00秒执行一次happy.sh的脚本
好的,继续,切换用户
su hdfs
crontab -l
查看到定时任务。
移除点击此处添加图片说明文字现在我们要找到那个定时任务,将后台hive向mongodb里面导的脚本,找出来。注意这里定时脚本很多,也符合crontab的开发方式,是哪一个呢,很尴尬,注释是后来加上的,本来都没有写注释,很难受。
然后我们了解到,这个sh是做市场的任务计算的,将hive表的数据往mongodb里面推,也是在这里做的。
移除点击此处添加图片说明文字咱们决定去看看这个脚本:
more /etl/tools/etl-python/push/push_report.sh
移除点击此处添加图片说明文字好了,那现在搞清楚了,这两段红色部分,先看下面这部分,是干嘛的呢,是做同步用的,还记得前文说的mongodb数据吗,那mongodb从哪里来的呢,其实就是从后台的hive同步过去的,我们可以看一下,主要这里看《推送部署统计查询》的部分,而不是静默的。
那我们看下面这个部分,我举其中一个例子。
cd /etl/tools/etl-python/push;
/opt/cloudera/parcels/CDH/lib/hadoop/bin/hadoop jar /home/OTAtest/ToMongo-0.0.1-SNAPSHOT-jar-with-dependencies.jar push_detail_statistics_deploy $yesday_date $yesday_date > ./hive2Mongo.log
第一步进入目录,第二步咱们将hadoop里面的jar包执行, 给入的参数main类,还有两个时间参数。这个模块还好,后期,咱们也会去看这个jar包里面到底做什么的,那现在的业务是提数,所以这里我们也不纠结,接着看上面两个。
好了,我们随便来看看这两个java包里面到底做了什么:
我想了解一下到底,hive是怎么to mongodb的,都在这里解释。
说到上面两个的话,我们就得先去hive里,看看这两个报表同步的原始表了。
四、hive表中的数据算法追踪。
那我们来看看前台业务的点击按钮下,触发的两张表,那我们着重看需求的这张表,推送部署统计查询:
看一下这张表的大概。
移除点击此处添加图片说明文字看下这种数据格式:
移除点击此处添加图片说明文字如果字段不是很清楚,因为没有文档,我们应该怎么看呢,对了,对照php的字段类型。
移除点击此处添加图片说明文字嗯,与字段对比发现pv、uv的前面首字母被小写了,嗯,好,那这个关系找到了,我们看下需求:
移除点击此处添加图片说明文字文字版本是:
请帮忙查询卓易市场锐嘉科渠道push用户相关信息,具体查询条件如下:
查询push案件:卡牛信用管家(com.mymoney.sms)
查询时间段:2017年7月8日-2017年7月11日
push--锐嘉科渠道号:bshrjke01(z001@bshrjke01)
好的,我们写一下sql语句,查询这个渠道好的四个指标。
移除点击此处添加图片说明文字这个其实不是他最后要的,所以我们还得接着往下看的。
好的,其实这个sql语句我们查和不查好像也没有什么意义的,因为,需求要的是具体的内容,所以,我们需要去看一下,这四个指标是如何算的,那,到这里,我们还得追刚才我们看到的crontab里面找到的脚本里的第一个红框框,接着去找他的出处。那下面,我们就带着这4个结果,去找手机型号、分辨率、安卓版本了。
五、python脚本解读,找到计算的各个指标的逻辑。
那,我们看
/usr/bin/python /etl/tools/etl-python/push/push_detail_statistics_deploy.py $yesday_date $yesday_date
其实就是这个:
移除点击此处添加图片说明文字那我们打开看看,这个python脚本。
豁然开朗了没有?
移除点击此处添加图片说明文字开朗了吧,我粘贴一下代码,因为notepad的高亮不是很清楚,那我们就这主要做的两个sql拿出来粘贴一下。
记住指标是这四个,message用户数、展现用户数、下载用户数、安装用户数
分别对应的字段为:udcnt,s_uv,d_uv,i_uv这四个用户数的算法。
我们就把分析的sql在下方用红色高亮出来吧。
use push_report;set hive.auto.convert.join=false; insert overwrite table push_detail_statistics_deploy partition(pt ='"""+date+"""')
select t3.package_name,t3.channel_id,udcnt,
s_pv,
s_uv,
c_pv,
c_uv,
d_pv,
d_uv,
i_pv,
i_uv from
(select package_name,trim(channel_id) as channel_id,count(imsi) as s_pv,count(distinct imsi) as s_uv from oz_log.cap_data_market_rec_mt where action = 3 and source1 = 3 and pt='"""+date+"""' and package_name<>'null' group by package_name,channel_id)t3 left outer join
(select package_name,trim(channel_id) as channel_id,count(imsi) as c_pv,count(distinct imsi)as c_uv from oz_log.cap_data_market_rec_mt where action = 4 and source1 = 3 and pt='"""+date+"""' and package_name<>'null' group by package_name,channel_id)t4 on
(t3.package_name=t4.package_name and t3.channel_id=t4.channel_id) left outer join
(select file_name,trim(channel_id) as channel_id,count(imsi) as d_pv,count(distinct imsi) as d_uv from oz_log.cap_data_download_rec_mt where result=3 and source1 = 3 and pt='"""+date+"""' and file_name<>'null' group by file_name,channel_id)t6 on
(t3.package_name=t6.file_name and t3.channel_id=t6.channel_id) left outer join
(select package_name,trim(channel_id) as channel_id,count(imsi) as i_pv,count(distinct imsi) as i_uv from oz_log.cap_data_market_rec_mt where source1 = 3 and action = 6 and imsi is not null
and pt='"""+date+"""' group by package_name,channel_id)t8 on
(t3.package_name=t8.package_name and t3.channel_id=t8.channel_id) left outer join
(select package_name,trim(channel_id) as channel_id,count(distinct imsi) as udcnt from oz_log.cap_appstore_push_rec_mt t1 join oz_log.cap_resource_info t2 on push_apk=res_apk_id where t1.pt='"""+date+"""' and t2.pt='"""+date+"""' and imsi is not null group by package_name,channel_id)t2
on (t3.channel_id=t2.channel_id and t3.package_name=t2.package_name )
group by t3.package_name,t3.channel_id,udcnt,s_pv,s_uv,c_pv,c_uv,d_pv,d_uv,i_pv,i_uv;
好了,这么看很难受吧,我截图吧:
移除点击此处添加图片说明文字干哈的呢?这就是udcnt的算法了,显示查出来t3表然后用t3表和我们的t1t2关联表来关联,就查到了这个message用户数。而且,这个是由oz_log库算出来的结果,那我们就把这个sql场景还原。
咱们整理出来,其实就是这么算的
移除点击此处添加图片说明文字oz_log.cap_appstore_push_rec_mt t1 join oz_log.cap_resource_info t2
两表关联得到message用户数
现在咱们就去看看这两张表。
大致分析了一下这两张表,我们可以看一下大概的内容,因为没有文档,只能通过数据和名字来了解了。
移除点击此处添加图片说明文字这个应该就是分辨率了。那我们再看一下需求吧,手机型号、分辨率、android版本
那么最后的sql可以这么写,来筛选维度:
select imei,imsi,package_name,channel_id,hstype,screen_width,screen_height,version_code from oz_log.cap_appstore_push_rec_mt t1
join oz_log.cap_resource_info t2 on push_apk=res_apk_id where t1.pt between '2017-07-08' and '2017-07-11' and t2.pt between '2017-07-08' and '2017-07-11'
and imsi is not null and channel_id='z001@bshrjke01'
当然关于是否去重,我就不清楚了,反正,就把数据导出来把。查询完了,我们发现需求分析错了,从新分析一下,他要的可能是四个字段值,
移除点击此处添加图片说明文字那,这个就是他最后要的东西了,咱们解决了一个,还有另一个,分辨率的维度,我们检测一下,分析一下。其实这些统计也就是想看看哪些型号,哪些用户数多,哪些分辨率的对用户数的影响,
移除点击此处添加图片说明文字那最后,这个就是我们说的,这个所有需求的结果,我们发送给运营人员了。那,这边就慢慢的接入市场项目了。
继续解决遗留的一个问题,就是那个java文件到底做了什么,我们首先下载一个反编译工具,看一下,反编译的内容,可以看到,这里有个udf,粘贴一下,咱们写过复杂的,这种简单的udf,直接带过了,粘贴一下代码,然后看一下.
package com.ToMongo;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.log4j.Logger;
import redis.clients.jedis.Jedis;
public class Udf_mst_dict extends UDF
{
private static final Logger log = Logger.getLogger(Udf_mst_dict.class.getName());
private final String REDIS_IP = "192.168.3.48";
private final int REDIS_PORT = 6379;
private final int REDIS_DB_1 = 41;
private Jedis jedis_db_1 = null;
public Udf_mst_dict() {
this.jedis_db_1 = new Jedis("192.168.3.48", 6379);
this.jedis_db_1.select(41);
}
public String evaluate(String app_id, String name, String type)
{
if (name == null) {
return "00000000";
}
if ("0".equals(type)) {
String ch_cd = this.jedis_db_1.hget(app_id + name, "mst_ch");
return ch_cd;
}if ("1".equals(type)) {
String ver_cd = this.jedis_db_1.hget(app_id + name, "mst_ver");
return ver_cd;
}
log.error("字典表中出现无效");
return "无效类型";
}
}
那我们看,它引入了redis,从redis里面拿了一些东西出来,这里声明redis的ip端口和库名称。
我们看,这个evaluate声明了三个参数,如果name为null返回好多个0,如果类型是0,我们把redis里面的appid和name和mst渠道取出来,用字符串返回,如果为1的类型,返回mst的版本拼接的字符出去了。这个udf搞这件事情的,在脚本里面一定有用的。
另外一个类,ImportDataToMongo类,代码比较多,我就不解释太多了,直接粘贴一些代码,然后大致说一下吧。
package com.ToMongo;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.bson.Document;
public class ImportDataToMongo
{
protected static Logger logger = Logger.getLogger(ImportDataToMongo.class);
private static String driverName = "org.apache.hive.jdbc.HiveDriver";
private static String mongo_url = "192.168.0.141";
private static int port = 27010;
public static void main(String[] args) { logger.info("usage hadoop jar <table name> <start> <end>");
String table_name = args[0];
String dt_start = "";
String dt_end = "";
if (args.length > 2) {
dt_start = args[1];
dt_end = args[2];
}
if ((table_name == "") || (table_name == null)) {
logger.error("please input table name...");
}
if ((dt_start == "") || (dt_start == null)) {
Calendar cal = Calendar.getInstance();
cal.add(5, -1);
dt_start = new SimpleDateFormat("yyyy-MM-dd").format(cal.getTime());
}
if ((dt_end == "") || (dt_end == null)) {
dt_end = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
}
Properties prop = new Properties();
String dir = System.getProperty("user.dir");
String reg = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$";
if ((dt_start.trim().matches(reg)) && (dt_end.trim().matches(reg)))
try {
logger.info("start import data..............." + dt_start + ":" + dt_end);
InputStream in = new BufferedInputStream(new FileInputStream(dir + File.separator + "database.properties"));
prop.load(in);
String db_name = prop.getProperty("db_name");
String hive_name = prop.getProperty("hive_name");
String hive_port = prop.getProperty("hive_port");
if ((db_name == null) || (db_name == "")) {
logger.error("your db_name isn't exists please check");
}
if ((hive_name == null) || (hive_name == "")) {
logger.error("your hive_name isn't exists please check");
}
if ((hive_port == null) || (hive_port == "")) {
logger.error("your hive_port isn't exists please check");
}
in.close();
Class.forName(driverName);
Connection con = DriverManager.getConnection("jdbc:hive2://" + hive_name + ":" + hive_port, "root", "goodluck");
String sql = "select * from " + db_name + "." + table_name + " where pt<='" + dt_end + "' and pt>='" + dt_start + "'";
logger.info(sql);
Statement stmt = con.createStatement();
ResultSet res = stmt.executeQuery(sql);
MongoClient client = getClient(mongo_url, port);
MongoDatabase db = client.getDatabase(db_name);
MongoCollection col = db.getCollection(table_name);
col.deleteMany(new Document("pt", new Document("$gte", dt_start).append("$lt", dt_end)));
ResultSetMetaData meta = res.getMetaData();
int index = 0;
while (res.next()) {
Document doc = new Document();
for (int i = 1; i <= meta.getColumnCount(); i++) {
String coln = meta.getColumnName(i).replaceAll(table_name + ".", "").trim();
Object obj = res.getObject(i);
if ((obj == null) || (obj == "null")) {
if (meta.getColumnTypeName(i).toLowerCase() == "int")
obj = Integer.valueOf(0);
else if (meta.getColumnTypeName(i).toLowerCase() == "string") {
obj = " ";
}
}
doc.append(coln, res.getObject(i));
}
index++;
col.insertOne(doc);
}
logger.info("import success " + index + " lines into " + db_name + "." + table_name);
} catch (Exception e) {
logger.error(e.getMessage());
System.exit(1);
} }
public static MongoClient getClient(String url, int port)
{
MongoClient client = null;
try {
client = new MongoClient("192.168.0.141", 27010);
}
catch (Exception e) {
}
return client;
}
}
我带你们看一下,就是首先呢,我们声明驱动名称,是hive的,因为要hive搬到mongodb,所以,还要声明mongo的url和端口。而且,我们通过脚本看到了,是通过hadoop集群来执行这个jar的,并且,当时给了三个参数,看来在这里都明朗了,表名,开始时间,结束时间,都是昨天。
然后我们if-else判断一下,这里吧时间都重新校验了一下,时间也用了正则校验了。
移除点击此处添加图片说明文字好的,接着,我们看到了如果开始时间和结束时间都匹配的话,我们进入导入部分,读取配置文件,配置文件里面放了
InputStream in = new BufferedInputStream(new FileInputStream(dir + File.separator + "database.properties"));
prop.load(in);
String db_name = prop.getProperty("db_name");
String hive_name = prop.getProperty("hive_name");
String hive_port = prop.getProperty("hive_port");
嗯,然后我们关闭流,创建hive的jdbc,拼写sql语句,jdbc这种形式,将所查到的结果集循环遍历然后col.insertOne(doc)。好了,就这么个东西,把一切都高通了,就gg了。