spark学习

本文介绍了如何使用Apache Spark进行数据分析,重点在于如何通过Spark处理播放日志,提升问题分析效率。内容涵盖Spark概述、RDD和DataFrame的概念及操作,以及如何创建、转换、存储数据,并解析日志数据创建DataFrame进行SQL查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标

针对播放日志引入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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dlian丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值