linux操作系统基本操作补充
ps -ef 查看系统中所有进程
ps -ef |grep test 查看linux系统的任务进程
ps -ef |grep flume 查看单个进程的详细信息(如果把flume换成yarn就是看yarn的详细信息)
ps -ef | grep yarn | wc -l 查看有多少条数据返回的是一个数字
yum install mailx -y 在linux操作系统装邮件客户端
which java 查看java路径(如果把Java换成hadoop那就是查看hadoop路径)
netstat -nltp | grep 8020 查看这个端口号存不存在
脚本在前台运行用Ctrl+z就可以挂起
jobs 就可以看查看挂起的任务(然后会看到每个任务前都有一个编号)
fg+编号 就可以继续运行这个任务
nohup sh test.sh 1>dev/null 2>1& & nohup如果不是root用户后台运行退出控制台也会继续运行,
sh test.sh要运行的脚本,1>dev/null写入这个特殊的文件夹进去就被删除,2>1&写入1的写入的文件夹
hive基本操作复习
create database db_name ; 创建数据库
show databases 查看系统中所有的数据库
use ods; 进入ods这个库里(把ods改成其他库名就进入别的库了)
show tables; 查看当前库的所有表
show partitions tb_user; 查看这个表的分区(如果把tb_user改成其他表名就查看其他表的分区)
alter table device_account_relation drop partition(dt='2021-01-10'); 删除分区
select * from tb_user limit 50; 查看tb_user表的50行(重点是limit后面跟什么数字就看多少行)
mr-jobhistory-daemon.sh start historyserver 到hadoop的sbin里启动
上面的这是查看yarn的所有日志启动的命令
发邮箱改的配置文件
set smtp=smtp://smtp.163.com:25
set smtp-auth=login
set smtp-auth-user=wanggangde1998@163.com
set smtp-auth-password=KSULFMTMYBIQDGQN
set ssl-verify=ignore
#set nss-config-dir=/etc/pki/nssdb
set from=wanggangde1998@163.com
echo "德志是个大帅逼" | mail -s "test mail" 1783683725@qq.com
监控脚本配置
#!/bin/bash
export JAVA_HOME=/opt/apps/jdk1.8.0_191/
export HADOOP_HOME=/opt/apps/hadoop-3.1.1/
export HBASE_HOME=/opt/apps/hbase-2.0.6/
num=`ps -ef | grep "org.apache.flume.node.Application" | grep -v "grep" | wc -l`
start(){
/opt/apps/apache-flume-1.9.0-bin/bin/flume-ng agent -c /opt/apps/apache-flume-1.9.0-bin/conf -f /opt/apps/apache-flume-1.9.0-bin/agentsconf/xiayou -n a1 &
}
if [ $num -eq 1 ]
then
echo "ok"
else
echo "flume进程失踪,准备重新拉起"
echo "flume进程失踪,准备重新拉起" | mail -s "flume告警邮件" 1783683725@qq.com
start
fi
写了哥sql自连接连接了三次一样的表,把三过滤出来就是只留了区,前面取了省市
CREATE TABLE area_dict
AS
SELECT
r.BD09_LAT,
r.BD09_LNG,
p.AREANAME as province,
c.AREANAME as city,
r.AREANAME as region
FROM t_md_areas r JOIN t_md_areas c ON r.PARENTID=c.ID AND r.`LEVEL`=3
JOIN t_md_areas p ON c.PARENTID=p.ID
经纬度图解
父子工程依赖详解
父工程引入的依赖会被子工程自动继承,如果子工程又加了新的版本那么子工程就会用自己新加的版本,在上面也可以定义变量后面可以复用变量所定的版本
<properties>
<fastjson.version>1.2.68</fastjson.version>
<gson.version>2.8.6</gson.version>
</properties>
<!-- 父工程中引入的依赖,子工程自动继承 -->
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
<!-- 依赖管理,管理依赖的定义 ,定义信息会被子工程继承-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 镜像库配置 -->
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<layout>default</layout>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>ali-plugin</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<!-- 指定编译java的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!-- 指定编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<!-- 把依赖jar中的用到的类,提取到自己的jar中 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<mainClass></mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<!--下面是为了使用 mvn package命令,如果不加则使用mvn assembly-->
<executions>
<execution>
<id>make-assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
git基本操作
回滚操作
拉取操作
账号绑定评分
原理是用户a用1设备登录一次就加100分,这个1设备就是a的,a经常等就经常加分,如果用户b也用设备1登录就是一个新的客户这个客户也是一次加100分,如果谁中间有1天断了分数就乘以70%,如果有匿名登录就给这个分高的,(假设一种情况这个1设备就是a的他的分很高,但是有一天设备1变更成b的了这样一天不登录a的分就会折半这样b的分数很快就会超过a,相对好一点)
import org.apache.spark.sql.SparkSession
object DeviceAccountRelationScore2 {
// -- 目标表结构
// CREATE TABLE dwd.device_account_relation
// (
// deviceid STRING,
// account STRING,
// score DOUBLE,
// first_time BIGINT,
// last_time BIGINT
// )
// PARTITIONED BY (dt STRING)
// STORED AS PARQUET
// TBLPROPERTIES('parquet.compress','snappy')
// ;
//
//
// -- 计算策略
// 基于T-1日的"关联得分表" 和 T日的行为日志,得出T日的"关联得分表"
def main(args: Array[String]): Unit = {
//这是伪装代码伪装成root获得权限
System.setProperty("HADOOP_USER_NAME","root")
if(args.length<2){
println(
"""
|
|wrong number of parameters!
|usage:
| args(0): T-1 date
| args(1): T date
|
|""".stripMargin)
}
val spark = SparkSession.builder()
.appName("设备关联得分")
// .master("local[*]")
//分区设为是
.config("spark.aql.shuffle.partitions",10)
.enableHiveSupport() //连接hive的条件
.getOrCreate()
//加载T-1日的设备账号关联评分表
val relation = spark.read.table("dwd.device_account_relation").where(s"dt='${args(0)}'")
//加载T日的日志表
val log = spark.read.table("ods.event_app_log").where(s"dt='${args(1)}'")
// relation.show(10,false)
// log.show(10,false)
log.createTempView("log_detail")
val logAggr = spark.sql(
"""
|
|select
|deviceid,
|--去空格
|if (trim(account)='',null,account) as account,
|--一天会访问多次,一次访问会产生一个会话,但一次访问会产生很多条数据但是都是一次会话只能算一次访问所以要去重.聚合后就是每天访问多少次
|cast(count(distinct sessionid)*100 as double) as score,
|min(timestamp) as time
|from log_detail
|group by deviceid,account
|
|""".stripMargin)
relation.createTempView("re")
logAggr.createTempView("log")
val joined = spark.sql(
"""
|
|select
|re.deviceid as re_deviceid,
|re.account as re_account,
|--类型转换
|cast(re.score as double) as re_score,
|re.first_time as re_firsttime,
|re.last_time as re_lasttime,
|log.deviceid as log_deviceid,
|log.account as log_account,
|cast(log.score as double) as log_score,
|log.time as time
|from re full join log
|on re.deviceid=log.deviceid and re.account=log.account
|
|""".stripMargin)
// +-----------+----------+--------+------------+-----------+------------+-----------+---------+----+
// |re_deviceid|re_account|re_score|re_firsttime|re_lasetime|log_deviceid|log_account|log_score|time|
// +-----------+----------+--------+------------+-----------+------------+-----------+---------+----+
// | d1| c1| 200.0| 1| 10| d1| c1| 200.0| 11|
// | null| null| null| null| null| d1| c5| 100.0| 11|
// | null| null| null| null| null| d6| c6| 200.0| 11|
// | d3| null| null| null| null| null| null| null|null|
// | null| null| null| null| null| d5| c5| 200.0| 11|
// | d2| c2| 800.0| 2| 8| null| null| null|null|
// | d2| c3| 600.0| 3| 7| d2| c3| 100.0| 11|
// | d4| c4| 200.0| 1| 10| null| null| null|null|
// | d5| null| null| null| null| null| null| null|null|
// | null| null| null| null| null| d7| ''| null|null|
// +-----------+----------+--------+------------+-----------+------------+-----------+---------+----+
joined.createTempView("joined")
val res = spark.sql(
"""
|
|select
|--如果昨天不为空就取昨天的,如果昨天为空就取今天的(设备id)
|nvl(re_deviceid,log_deviceid) as deviceid,
|--如果昨天不为空就取昨天的,如果昨天为空就取今天的(用户id)
|nvl(re_account,log_account) as account,
|--这相当于三元表达式如果昨天登录了今天没登录,就把分数*0.7,昨天今天都登录了就相加,之前没登录过之前的分数就是null判断一下null就加0
|if(re_deviceid is not null and log_deviceid is null,re_score*0.7,nvl(re_score,0)+log_score) score,
|--第一次登录事件如果之前有就是之前的,没有就是今天的time
|nvl(re_firsttime,time) first_time,
|--最后一次登录就是今天的时间,如果今天没登录就是null那就是昨天的最后一次登录时间
|nvl(time,re_lasttime) as last_time
|from joined
|
|""".stripMargin)
// +--------+-------+-----+----------+---------+
// |deviceid|account|score|first_time|last_time|
// +--------+-------+-----+----------+---------+
// | d1| c1|400.0| 1| 11|
// | d1| c5|100.0| 11| 11|
// | d6| c6|200.0| 11| 11|
// | d3| null| null| null| null|
// | d5| c5|200.0| 11| 11|
// | d2| c2|560.0| 2| 8|
// | d2| c3|700.0| 3| 11|
// | d4| c4|140.0| 1| 10|
// | d5| null| null| null| null|
// | d7| null| null| null| null|
// +--------+-------+-----+----------+---------+
res.where("account is null").createTempView("t_null")
res.where("account is not null").createTempView("t_you")
//将两个表全连接在一起形成一个大表主要过滤用户id为null的数据
spark.sql(
s"""
|
|insert into table dwd.device_account_relation partition(dt='${args(1)}')
|select
|nvl(t_you.deviceid,t_null.deviceid) as deviceid,
|t_you.account as account,
|t_you.score as score,
|nvl(t_you.first_time,t_null.first_time)as first_time,
|nvl(t_you.last_time,t_null.last_time) as last_time
|from t_null full join t_you on t_null.deviceid = t_you.deviceid
|
|""".stripMargin).show(10,false)
// +--------+-------+-----+----------+---------+
// |deviceid|account|score|first_time|last_time|
// +--------+-------+-----+----------+---------+
// | d2| c2|560.0| 2| 8|
// | d2| c3|700.0| 3| 11|
// | d3| null| null| null| null|
// | d1| c1|400.0| 1| 11|
// | d1| c5|100.0| 11| 11|
// | d4| c4|140.0| 1| 10|
// | d6| c6|200.0| 11| 11|
// | d7| null| null| null| null|
// | d5| c5|200.0| 11| 11|
// +--------+-------+-----+----------+---------+
spark.close()
}
}
将这个打包放入linux中运行
都是一些运行的参数,最后一行是放在linux上jar包的路径和要传进jar包的参数
bin/spark-submit \
--master yarn \
--deploy-mode cluster \
--class cn.doitedu.dwetl.DeviceAccountRelationScore2 \
--name device_account_relation \
--driver-memory 1G \
--executor-memory 1G \
--executor-cores 1 \
--queue default \
--num-executors 2 \
--conf spark.sql.shuffle.partitions=5 \
/root/dw_et.jar 2021-01-09 2021-01-10
写一个脚本放集群里执行
#!/bin/bash
######################################
#
# @author :
# @date : 2021-01-15
# @desc : 设备账号关联得分计算
# @other
######################################
export JAVA_HOME=/opt/apps/jdk1.8.0_141/
export HIVE_HOME=/opt/apps/hive-3.1.2/
export HADOOP_HOME=/opt/apps/hadoop-3.1.1/
export SPARK_HOME=/opt/apps/spark-3.0.1-bin-hadoop3.2
DT_CUR=$(date -d'-1 day' +%Y-%m-%d)
DT_PRE=$(date -d'-2 day' +%Y-%m-%d)
if [ $# -eq 2 ]
then
DT_PRE=$1
DT_CUR=$2
fi
${SPARK_HOME}/bin/spark-submit \
--master yarn \
--deploy-mode cluster \
--class cn.doitedu.dwetl.DeviceAccountRelationScore \
--driver-memory 1g \
--executor-memory 1g \
--executor-cores 1 \
--queue default \
--num-executors 2 \
--conf spark.sql.shuffle.partitions=5 \
/root/dw_et.jar ${DT_PRE} ${DT_CUR}
if [ $? -eq 0 ]
then
echo "任务执行成功"
else
echo "任务执行失败"
echo "任务执行失败: `date` : 设备账号关联得分计算, T-1日: ${DT_PRE}, T日: ${DT_CUR}" | mail -s "spark任务失败" 1783683725@qq.com
fi
处理ods到dwd层代码和解析
import java.text.SimpleDateFormat
import java.util.UUID
import ch.hsr.geohash.GeoHash
import org.apache.commons.lang3.StringUtils
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileStatus, FileSystem, Path}
import org.apache.spark.SparkFiles
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
import org.lionsoul.ip2region.{DbConfig, DbSearcher}
object EventAppLog2DwdTable2 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("ods层app端行为日志数据,处理为dwd明细表")
//.master("local[*]")
.enableHiveSupport() //连接hive条件
.getOrCreate()
import spark.implicits._
if(args.size<3){
println(
"""
|
|wrong number of parameters
|usage:
| args(0) : T-1日
| args(1) : T日
| args(2) : T+1日
|
|""".stripMargin)
}
val DT_PRE =args(0)
val DT_CUR =args(1)
val DT_NEXT =args(2)
/**
* 加载各种字典数据,并广播
*/
// 1.geohash字典
//dataframe是相当于一张二维表
//这边就是把全国所有区坐标经纬度提取出来变成guhash值为k,省市区为v,存储到map集合
val genodf: Dataset[Row] = spark.read.parquet("/zidina/geodict")
val geomap = genodf.rdd.map(row => {
val geohash = row.getAs[String]("geohash")
val province = row.getAs[String]("province")
val city = row.getAs[String]("city")
val region = row.getAs[String]("region")
(geohash, (province, city, region))
}).collectAsMap()
val bc1 = spark.sparkContext.broadcast(geomap)
// 2 ip2region字典
// 添加缓存文件
// spark.sparkContext.addFile("/zidina/ip2region/ip2region.bd")
// //在算子中使用缓存文件
// genodf.rdd.map(row => {
// val path = SparkFiles.get("ip2region.db")
// new DbSearcher(new DbConfig(),path)
// })
//自己读文件,存入一个字节数组,并广播
//因为读hdfs的文件所欲用字节流读FileSystem实例化一下括号里要Configuration就new一个传给他
val fs = FileSystem.get(new Configuration())
//new一个路径,这边就是hdfs的路径
val path = new Path("/zidina/ip2region/ip2region.bd")
//获取文件的长度(字节数)
//设变就是一个方法获取要读的文件的长度
val statuses: Array[FileStatus] = fs.listStatus(path)
val len = statuses(0).getLen
//将字典文件,以字节形式读取并缓存到一个字节buffer中
//打开路径
val in = fs.open(path)
//存在new字节数组,要长度,上面有
val buffer = new Array[Byte](len.toInt)
//读文件参数1从头读,2存到哪
in.readFully(0,buffer)
val bc2 = spark.sparkContext.broadcast(buffer)
// 3.设备账号关联评分字典
// val relation = spark.read.table("dwd.device_account_relation").where("dt='2021-01-10'")
//d01,c01,1000
//d01,c02,800
// 上面的数据,需要加工成: d01,c01 加工逻辑:求分组top1
//把设备id分组应为设备就一个可能会有好几个用户登录,为了把得分最高的设备id,对应的用户id取出,分一样就把最近一次登录时间取出
val relation = spark.sql(
"""
|
|select
|deviceid,
|account
|from
|(
| select
| deviceid,
| account,
| --编号函数row_number 设备id分组,score评分排序,最近一次登录时间
| row_number() over(partition by deviced order by score desc,last_time desc) as rn
| from dwd.device_account_relation
|) o
|where rn=1
|
|""".stripMargin)
//把得到的设备id,和用户id变成k,v 设备为k用户为v
val relationMap = relation.rdd.map(row => {
val deviceid = row.getAs[String]("deviceid")
val account = row.getAs[String]("account")
(deviceid, account)
}).collectAsMap()
val bc3 = spark.sparkContext.broadcast(relationMap)
//3.历史设备.账号标识(用户判断新老访客)
//把关联得分表读出来
val ids: Set[String] = spark.read.table("dwd.device_account_relation")
//因为分区了所以可以指定分区,这边要最新一天的
.where(s"dt='${DT_PRE}'")
//提取出用户id和设备id放array数组里,炸裂开变成一列数据有重, 起个名字叫id列
.selectExpr("explode (array(deviceid,accout)) as id")
//被每一行拿出来做映射,做id列的变成String类型,collect收集,toSet放到set集合,这个就和没重复的,就相当于去重了
.map(row=>row.getAs[String]("id")).collect().toSet
val bc4 = spark.sparkContext.broadcast(ids)
//加载T日的ODS日志表数据
//把指定时间分区的数据提取出来
val ods = spark.read.table("ods.event_app_log").where(s"dt='${DT_CUR}'")
val beanRdd = ods.rdd.map(row => {
//把这个dataformat变更成rdd才能用map算子,自己封装了一个bean每一条数据提取出来处理变成bean格式,这个里面的字段和bean是一样的
//因为取数据要row.getAs[String]("account"),这样取,所以要封装了一个方法取
Row2AppLogBean.row2AppLogBean(row)
})
//根据规则清洗过滤
// 过滤掉日志中缺少关键字段(deviceid/properties/eventid/sessionid 缺任何一个都不行)的记录!
// 4,过滤掉日志中不符合时间段的记录(由于app上报日志可能的延迟,有数据延迟到达)
//这个和map一样把每条数据拿出来处理,返回true或false,true就保留数据false就清除数据
val filtered = beanRdd.filter(bean => {
var flag = true
//deviceid/properties/eventid/sessionid
//几个字段如果有一个为空就返回false取反就是true就进去赋值flag为false就起清除了
if (!(StringUtils.isNotBlank(bean.deviceid) && bean.properties != null && StringUtils.isNoneBlank(bean.eventid) && StringUtils.isNotBlank(bean.sessionid))) flag = false
//判断数据的时间是否正确
//上面返回true了在执行这个new SimpleDateFormat定义了一个时间格式
val sdf = new SimpleDateFormat("yyyy-MM-dd")
//把传进来的时间格式化成自己上面定义的格式,在获取时间戳
val validStart = sdf.parse(s"${DT_CUR}").getTime
val validEnd = sdf.parse(s"${DT_NEXT}").getTime
//因为要取一整天的数据,把bean里的timestamp时间戳这条数据取出来,||或 有一个是对的就是对的,
//比如要13号到14号的数据,就把时间取出大于14,或小于13就会返回true就进去flag就会被赋值为true
if (bean.timestamp < validStart || bean.timestamp >= validEnd) flag = false
flag
})
//session分割,添加新的newsessionid字段
//sessionid是多次点击但没退出 点击一次就会产生一条记录数据,他们都是一个相同的sessionid
//这边自定义了一个newsessionid
//根据sessionid分组.切割亚平
val sessionSplitted: RDD[AppLogBean] = filtered.groupBy(bean => bean.sessionid).flatMapValues(iter => {
//进来一个迭代器转list,时间排序
val sortedEvents = iter.toList.sortBy(bean => bean.timestamp)
//生成最随机字符串方法
var tmpSessionId = UUID.randomUUID().toString
//每一条数据提取操作(这里是sissionid一样的数据)
for (i <- 0 until sortedEvents.size) {
//给newsessionid赋值
sortedEvents(i).newsessionid = tmpSessionId
//判断如果不是最后一条数据,且数据大于30分钟就给newsissionid赋值新的字符串
if (i < sortedEvents.size - 1 && sortedEvents(i + 1).timestamp - sortedEvents(i).timestamp > 30 * 60 * 1000) tmpSessionId = UUID.randomUUID().toString
}
sortedEvents
//是k,v类型key是sissionid不要,要v的完整数据所以是2
}).map(_._2)
// 验证切割效果
// sessionSplitted.toDF.createTempView("tmp")
// spark.sql(
// """
// |
// |select
// |sessionid,count(distinct newsessionid) as cnt
// |from tmp
// |group by sessionid
// |having count(distinct newsessionid) >1
// |
// |""".stripMargin).show(100,false)
//mappartitions对每个分区操作
val aread = sessionSplitted.mapPartitions(iter => {
//获取上面个广播的geohash是k,v类型的 k是geohaah v是省市区
val geoDict = bc1.value
//获取上面广播的ip地址获取地理位置的一个文件
val ip2RegionDb: Array[Byte] = bc2.value
val searcher = new DbSearcher(new DbConfig(), ip2RegionDb)
iter.map(bean => {
//定义零时记录变量
// 定义临时记录变量
var country: String = "UNKNOWN" //国家
var province: String = "UNKNOWN" //省
var city: String = "UNKNOWN" //市
var region: String = "UNKNOWN" //区
//查询GEO字典获取省市区信息
try {
//获取bean里的经纬度
val lat = bean.latitude
val lng = bean.longitude
//通过这个geohash的一个方法把这条数据的经纬度和要精确的长度放进来就会得到这个位置对应的geohash值
val geo: String = GeoHash.geoHashStringWithCharacterPrecision(lat, lng, 5)
//把这个数据经纬度获取到的值放进这个方法中就会返回对应的省市区在一个元组里
val area: (String, String, String) = geoDict.getOrElse(geo, ("UNKNOWN", "UNKNOWN", "UNKNOWN"))
country = "CN"
//分别取出省市区赋值给上面个的零时记录变量
province = area._1
city = area._2
region = area._3
} catch {
case e: Exception => e.printStackTrace()
}
// 如果在geo字典中查询失败,则用ip地址再查询一次
//如果在geo字典中查询失败,则用ip地址在查询一次
//这边判断如果province(省)为初始值"UNKNOWN"就代表上面的geo失败了就进这个里面处理
if ("UNKNOWN".equals(province)) {
//获取这条数据的ip地址通过这个方法就会获得地理位置 中国|0|上海|上海|电信 这种格式的
val block = searcher.memorySearch(bean.ip)
//中国|0|上海|上海|电信
try {
//把获取到的数据切割提取要的字段赋值给上面的零时记录变量
val split = block.getRegion.split("\\|")
country = split(0)
province = split(2)
city = split(3)
} catch {
case e: Exception => e.printStackTrace()
}
}
//把上面的零时记录变量拿过来赋值给这条数据(也就是bean)里面的对应的地理位置
bean.country = country
bean.province = province
bean.city = city
bean.region = region
bean
})
})
//guid绑定=生成
val guided = aread.mapPartitions(iter => {
//获取上面的广播这被上面设置成k,v格式了k是设备id(deviceid),v是账号(account)
val deviceBindAccoutDict = bc3.value
//guid绑定
iter.map(bean => {
var guid: String = null
//如果该条数据中,有登录账号,则直接用该登录账号作为这条数据的用户标识
//提取这条数据的账号account看如果不是null就用这个账号赋值给guid作为全局id
if (StringUtils.isNotBlank(bean.account)) {
guid = bean.account
}
//如果该条数数据中,没有登录账号,则用设备id如关联账号表中查找默认的账号,作为guid
else {
//如果是null就用这个方法把上面的广播的set集合拿来对比,提取这条数据里的设备id(deviceid)如果里面没有就返回null
val findedAccount = deviceBindAccoutDict.getOrElse(bean.deviceid, null)
//如果查询到的结果为不为null,则用查询到的account作为guid,否则用deviceid作为fuid
//判断上次对比查询set集合如果有就把对应的account返回给guid赋值
if (findedAccount != null) guid = findedAccount else guid = bean.deviceid
}
//把guid赋值给bean里的guid
//这个操作就是把匿名操作赋值给那个设备登录的几个账号得分最高的
bean.guid = guid
bean
})
})
//新老访客
val result: DataFrame = guided.mapPartitions(iter => {
//获取上面广播的提取出来所有访问过的设备id和账号放在的set集合
val idSet = bc4.value
iter.map(bean => {
var isnew = "1"
//把这条数据的设备id提取出来和这个set集合对比,把账号也提取出来对比
//如果有一个成立,就是老用户就赋值0代表老用户
if (idSet.contains(bean.deviceid) || idSet.contains(bean.account)) isnew = "0"
//把这个字段在赋值给bean里的isnew 0是老用户 1是新用户
bean.isnew = isnew
bean
})
//toDF就是把这个rdd转成DataFrame才可以进行sql操作
}).toDF()
//保存结果到目标表
result.createTempView("result")
spark.sql(
"""
|--保存到这个分区
|insert into table dwd.event_app_detail partition(dt='2021-01-10')
|select
|account ,
|appid ,
|appversion ,
|carrier ,
|deviceid ,
|devicetype ,
|eventid ,
|ip ,
|latitude ,
|longitude ,
|nettype ,
|osname ,
|osversion ,
|properties ,
|releasechannel ,
|resolution ,
|sessionid ,
|timestamp ,
|newsessionid ,
|country ,
|province ,
|city ,
|region ,
|guid ,
|isnew
|
|from result
|
|""".stripMargin)
spark.close()
}
}