目标
针对播放日志引入spark,提升问题分析效率。
spark概述
Apache Spark是用于大规模数据处理的统一分析引擎。它提供Java,Scala,Python和R的高级API,以及支持常规执行图的优化引擎。它还支持一组丰富的更高级别的工具,包括星火SQL用于SQL和结构化数据的处理,MLlib机器学习,GraphX用于图形处理,以及结构化流的增量计算和流处理。
官方文档:https://spark.apache.org/docs/latest/
RDD(Resilient Distributed Dataset)弹性分布式数据集
RDD支持两种类型的操作:转换(从现有操作创建新的数据集)和动作(操作)。
详细文档:https://www.cnblogs.com/qingyunzong/p/8899715.html
DF(DataFrame)数据帧
api文档:https://spark.apache.org/docs/latest/api/python/pyspark.sql.html#pyspark.sql.DataFrame
步骤
1、与spark连接
(1)spark程序
首先创建SparkContext对象,spark如何访问集群,下面例子master传’local’以本地模式运行spark
from pyspark import SparkContext, SparkConf
# conf = SparkConf().setAppName(appName).setMaster(master)
# sc = SparkContext(conf=conf)
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
# 先获取,没有则创建
sc=SparkContext.getOrCreate(conf)
(2)pyspark shell启动
pyspark启动会自动创建两个对象spark(SparkSession)和sc(SparkContext),并自动引入相关模块。
2、创建RDD
创建RDD的方法有两种:并行化 驱动程序中的现有集合,或引用外部存储系统中的数据集。根据我们分析日志的需求,主要使用外部数据源创建。
PySpark可以从Hadoop支持的任何存储源创建分布式数据集,包括本地文件系统,HDFS,Cassandra,HBase,Amazon S3等。Spark支持文本文件,SequenceFiles和任何其他Hadoop InputFormat。
可以使用SparkContext的textFile方法创建文本文件RDD 。此方法需要一个URI的文件(本地路径的机器上,或一个hdfs://,s3a://等URI),并读取其作为行的集合。
Spark的所有基于文件的输入法(包括textFile)都支持在目录,压缩文件和通配符上运行。例如,你可以使用textFile("/my/directory"),textFile("/my/directory/*.txt")和textFile("/my/directory/*.gz")。
rdd = sc.textFile(path)
3、RDD转换
此时的rdd只有一列,每一行的数据对应日志里的每一行,我们需要对rdd的每一行进行解析,转换成多列存储,以便使用sql查询。
主要用到个三个方法filter()、map()和flatMap(),通过这三个方法可以过滤出想要的日志行,并将过滤出的每一行日志转换成json格式,key作为列名,value作为行值。
https://www.cnblogs.com/qingyunzong/p/8899715.html
import json
import copy
from pyspark import SparkContext, SparkConf
def parse(line):
i = line.find('{')
j = line.rfind('}')
s = {}
if i < 0 or j < 0 or i > j:
return s
try:
s = json.loads(line[i:j+1])
e = line[:i].split(',')
if len(e) > 1:
s['client_ip'] = e[1]
except Exception as excp:
print("json load excp: ", excp)
return s
def parse_list(line):
s = parse(line)
its = s.get('list', None)
if not its:
return
for it in its:
res = copy.deepcopy(s)
res['video_action'] = it
if 'video_info' in it:
res['video_action'].pop('video_info')
res['video_action'] = json.dumps(res['video_action'])
res.pop('list')
ls = it.get('video_loading_info', None)
if not ls:
yield res
continue
try:
ls = json.loads(ls)
except:
yield res
continue
for load in ls:
res['video_loading_info'] = json.dumps(load)
yield res
return
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
sc=SparkContext.getOrCreate(conf)
rdd = sc.textFile(path)
parseRdd = rdd.filter(lambda s: 'list' in s).flatMap(parse_list)
4、RDD转成DF
因为RDD支持的查询操作有限,为了使查询更方便,需要将RDD转成DF,DF可调用的方法更加丰富。
from pyspark import SparkContext, SparkConf
from pyspark.sql import SQLContext
schema = StructType([
StructField("android_id", StringType(), True),
StructField("client_ip", StringType(), True),
StructField("gps_adid", StringType(), True),
StructField("h_app", StringType(), True),
StructField("h_av", StringType(), True),
StructField("h_ch", StringType(), True),
StructField("h_cnd_speed", LongType(), True),
StructField("h_did", StringType(), True),
StructField("h_dt", LongType(), True),
StructField("h_imsi", StringType(), True),
StructField("h_lang", StringType(), True),
StructField("h_m", LongType(), True),
StructField("h_mf", StringType(), True),
StructField("h_model", StringType(), True),
StructField("h_nt", LongType(), True),
StructField("h_os", LongType(), True),
StructField("h_pkg", StringType(), True),
StructField("h_ts", LongType(), True),
StructField("isp_srv", StringType(), True),
StructField("token", StringType(), True),
StructField("video_action", StringType(), True),
StructField("video_loading_info", StringType(), True)
])
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
sc=SparkContext.getOrCreate(conf)
rdd = sc.textFile(path)
parseRdd = rdd.filter(lambda s: 'list' in s).flatMap(parse_list)
sqlContext = SQLContext(sc)
# schema可以自己指定,也可不传(其会自动识别)
df = sqlContext.createDataFrame(parseRdd, schema=schema)
5、创建基于DF的临时视图
如果希望像mysql一样使用SQL语句查询,可以创建临时视图。
使用**get_json_object()**可以解析json字符串,获得value。
# 创建临时视图
df.createTempView(table_name)
r3 = spark.sql('select * from list where h_did in (select distinct(h_did) from table_name where get_json_object(video_action, "$.play_result")<>1)')
# 以集合形式输出
r3.collect()
6、存储DF
可以存储为csv、json、parquet格式。
path如果没有指定文本格式,就会存储为目录,将数据分散存储在多个文件里。
header=True,存储表头。
df.write.csv(path=file, header=True)
df.coalesce(1).write.csv(‘did_result.csv’, header=True)
coalesce指定分区数,即可保存到一个文件里,注意:如果 sql没有聚合操作 且 数据量较大,应适当增大分区数。
7、下次读取DF
header=True,读取表头。
df = spark.read.csv(path=file, header=True)
8、解析video log的源码
pyspark shell启动后,可以引入该模块,调用封装好的方法解析日志,创建df和tempview。
#! /usr/bin/python
import json
import copy
from pyspark import SparkContext
from pyspark import SparkConf
from pyspark.sql.types import *
from pyspark.sql.functions import *
from pyspark.sql import SQLContext
def parse(line):
i = line.find('{')
j = line.rfind('}')
s = {}
if i < 0 or j < 0 or i > j:
return s
try:
s = json.loads(line[i:j+1])
e = line[:i].split(',')
if len(e) > 1:
s['client_ip'] = e[1]
except Exception as excp:
print("json load excp: ", excp)
return s
def parse_video(line):
s = parse(line)
its = s.get('list', None)
if not its:
return
s.pop('list')
for it in its:
res = s.copy()
res['video_action'] = it
infos = it.get('video_info', None)
if not infos:
continue
res['video_action'].pop('video_info')
res['video_action'] = json.dumps(res['video_action'])
infos = json.loads(infos)
for info in infos:
res['video_info'] = json.dumps(info)
ls = it.get('video_loading_info', None)
if not ls:
yield res
continue
res2 = res.copy()
try:
ls = json.loads(ls)
except:
yield res
continue
for load in ls:
res2['video_loading_info'] = json.dumps(load)
yield res2
return
def parse_list(line):
s = parse(line)
its = s.get('list', None)
if not its:
return
for it in its:
res = copy.deepcopy(s)
res['video_action'] = it
if 'video_info' in it:
res['video_action'].pop('video_info')
res['video_action'] = json.dumps(res['video_action'])
res.pop('list')
ls = it.get('video_loading_info', None)
if not ls:
yield res
continue
try:
ls = json.loads(ls)
except:
yield res
continue
for load in ls:
res['video_loading_info'] = json.dumps(load)
yield res
return
schema = StructType([
StructField("android_id", StringType(), True),
StructField("client_ip", StringType(), True),
StructField("gps_adid", StringType(), True),
StructField("h_app", StringType(), True),
StructField("h_av", StringType(), True),
StructField("h_ch", StringType(), True),
StructField("h_cnd_speed", LongType(), True),
StructField("h_did", StringType(), True),
StructField("h_dt", LongType(), True),
StructField("h_imsi", StringType(), True),
StructField("h_lang", StringType(), True),
StructField("h_m", LongType(), True),
StructField("h_mf", StringType(), True),
StructField("h_model", StringType(), True),
StructField("h_nt", LongType(), True),
StructField("h_os", LongType(), True),
StructField("h_pkg", StringType(), True),
StructField("h_ts", LongType(), True),
StructField("isp_srv", StringType(), True),
StructField("token", StringType(), True),
StructField("video_action", StringType(), True),
StructField("video_loading_info", StringType(), True)
])
# sc: spark context
# path: log path
# tname: tempview name
def readTextByInfo(path, tname, sc=None):
if not sc:
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
sc=SparkContext.getOrCreate(conf)
rdd = sc.textFile(path)
parseRdd = rdd.filter(lambda s: 'list' in s).flatMap(parse_video)
sqlContext = SQLContext(sc)
df = sqlContext.createDataFrame(parseRdd, schema.add("video_info", StringType()))
df.createTempView(tname)
return df
# sc: spark context
# path: log path
# tname: tempview name
def readTextByAction(path, tname, sc=None):
if not sc:
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
sc=SparkContext.getOrCreate(conf)
rdd = sc.textFile(path)
parseRdd = rdd.filter(lambda s: 'list' in s).flatMap(parse_list)
sqlContext = SQLContext(sc)
df = sqlContext.createDataFrame(parseRdd, schema=schema)
df.createTempView(tname)
return df
def readTextByMonitor(path, tname, sc=None):
if not sc:
conf=SparkConf().setAppName("spark.master").setMaster("local[*]")
sc=SparkContext.getOrCreate(conf)
rdd = sc.textFile(path)
parseRdd = rdd.filter(lambda s: 'cdn_monitor' in s).map(parse)
sqlContext = SQLContext(sc)
df = sqlContext.createDataFrame(parseRdd)
df.createTempView(tname)
return df
9、触发并行计算的方式
[Stage 19:> (0 + 4) / 10]
0是目前完成的task数,4是正在并行执行的task数,10是总共需要执行的task数。
spark默认的并行计算任务数是4。
a:如果sql中有使用聚合运算,那么spark会使用并行计算。
b:如果sql中没有聚合运算,却指定了分区数,即使用了coalesce()和repartition()方法,如果指定分区数小于4,则使用指定分区数;如果指定分区数大于等于4,则使用4。
10、shell运行case
pyspark
>>> import video_log_parse
>>> df = video_log_parse.readTextByInfo('/data/scribe_gateway/gw_diagnosis-video/gw_diagnosis-video-2020-12-13_006[3-5]*.gz', 'raw')
>>> df.first()
Row(android_id='6bafd9e263a570d', client_ip='181.0.246.216', gps_adid='b64c02c-cc3d-49ce-8d2e-8a9eccf15a32', h_app='omtg', h_av='1.59.0', h_ch='google', h_lat=-6.61447552, h_lng=106.72962759, h_cnd_speed=745, h_did='6afd9e263a5770d', h_dt=0, h_imsi=None, h_lang='id', h_m=19502854, h_mf='vivo', h_model='vivo 1727', h_nt=4, h_os=28, h_pkg='id', h_ts=1607868602444, isp_srv='Telkomsel', token='T7KbNU0omCagbhVFSODNh62F0OfV9cujEboZQegWePeHc5uBC_ME19LmOF5oJ4RISJF', video_action='{"vid": "51745233212325", "pid": 193123022999474, "url": "http://akvideo02.icos.com/omgvd/fe/3d/7648-3bc9-11eb-a43a-00163e027d11?h265=1", "switched": 0, "play_result": 1, "offset": 707, "http_code": 200, "firstpacket_ts": 137, "firstframe_ts": 443, "download_speed": 745, "downloaded_bytes": 309339, "ct": 1607868586644, "connect_ts": 129, "connect_state": 0, "video_type": "h265", "video_time_out": 14000, "video_load_type": 0, "video_hijack_switch_type": 0, "video_duration": 14, "is_buffering": false, "consume_duration": 15786, "video_width": 240, "video_height": 300, "report_ver": 1, "video_start_time": 160786856643, "video_end_time": 1607868602430, "is_proxy": false, "is_h265": true, "cache_type": 1, "connect_timeout": 14, "read_timeout": 14, "use_ram": false}', video_loading_info=None, video_info='{"http_code": 200, "connect_state": 0, "connect_ts": 129, "consume_duration": 15786, "dns_code": -2, "error_reason": "", "firstframe_ts": 443, "firstpacket_ts": 137, "is_cronet": false, "is_ip_url": 0, "is_quic": false, "net_speed": -1, "offset": 707, "download_speed": 745.89844, "downloaded_bytes": 309339, "url": "http://akvideo02.icocofun.com/omgvd/fe/3d/7648-3bc9-11eb-a43a-00163e027d11?h265=1", "video_height": 300, "video_width": 240}')
>>> r = spark.sql('select client_ip, h_model, h_os, h_av, if(get_json_object(video_info,"$.error_reason")="", 1, 0) as succ, if(get_json_object(video_info,"$.error_reason")<>"", 1, 0) as fail from raw where (left(client_ip, 6) in ("114.4.","114.5.") or left(client_ip, 8)="120.188.")')
>>> r.coalesce(4).write.csv('detail',header=True)