【初學spark】pyspark進行json日誌結構化

如題,磨蹭了好幾天總算把這個需求整明白了,寫篇筆記整理一下自己的思路,也希望能給大家帶來幫助。

 

第一次看到json日誌數據的我內心是崩潰的,但是不用擔心,json日誌每一條記錄的存儲都是遵循一定的結構規則,只要你是從生產化的hdfs上獲取數據,相信也是這樣的。

一上來就直接整代碼不是一種良好的學習方式,因此在正式講解如何將這種日誌數據結構化之前,要先理解兩種spark中的數據結構:RDD和DataFrame

       由上圖,明顯DataFrame的可讀性比RDD好很多,RDD的結構是若干條Row,每一條Row是一條記錄,而DataFrame就很接近我們平時能夠見到的各種表格了,並且DataFrame還能夠輸出每個字段的結構與屬性信息,這些都包含在它的scheme中。

在性能方面,DataFrame屬於RDD的進化版,按照我目前的理解(參考其他人的帖子,還沒看過源碼,以後爭取),性能比RDD更好, 具體好在哪,請讀者自行百度,我就不復制粘貼別人的原創文字了。

瞭解了這些以後,選擇dataframe結構作爲數據結構化的儲存形式,這裏先推薦一個帖子,把dataframe的各項操作都說的很細了,當然,首推官方API。

DataFrame官方API:http://spark.apache.org/docs/2.1.0/api/python/pyspark.sql.html#pyspark.sql.DataFrame

推薦帖子:https://blog.csdn.net/sinat_26917383/article/details/80500349

開始結構化部分,結構化方案:

'''
日誌文件結構化的步驟
1、讀取原始日誌
2、抽取字段、修改列名、規範json的取值
3、生成log_in和log_out
4、log_in join log_out
5、輸出
'''
  • 讀取原始日誌,這裏建議先了解一下json格式的讀取和解析,因爲上面看到的原始日誌,就是妥妥的json格式文件
#讀取日誌文件
log_data = spark.read.format("json").load("E:/Asiainfo/Hadoop/a01")
#打印讀取形成的數據類型
print("type of jsons",type(log_data))
output:
type of jsons <class 'pyspark.sql.dataframe.DataFrame'>
#打印數據的目錄樹
log_data.printSchema()
output:
root
 |-- fields.Call_id: string (nullable = true)
 |-- fields.Class_name: string (nullable = true)
 |-- fields.Client_ip: string (nullable = true)
 |-- fields.Timestamp: string (nullable = true)
 |-- fields.Trace_id: string (nullable = true)
 |-- fields.in_param: struct (nullable = true)
 |    |-- ROOT.BODY.ACT_ID: string (nullable = true)
 |    |-- ROOT.BODY.APP_ID: string (nullable = true)
 |    |-- ROOT.BODY.APP_NAME: string (nullable = true)
 |    |-- ROOT.BODY.ATTR_ID: string (nullable = true)
 |    |-- ROOT.BODY.TRANSPRCID: string (nullable = true)
 |    |-- ROOT.BODY.TYPE_CODE: string (nullable = true)
 |    |-- ROOT.BODY.UPDATE_ACCEPT: string (nullable = true)
 |-- fields.jcfParam: string (nullable = true)
 |-- fields.redundant_field: string (nullable = true)

我的文件目錄樹非常長,我刪掉了一部分,所以你接下來如果看到我提取的字段不在上面的目錄樹中,不要驚訝,這只是目錄樹的一部分。 

  • 抽取字段、修改列名、規範json的取值

這個時候你已經可以看到字段結構化的顯示了

#使用select()函數
log_data.select("`fields.Login_no`","`fields.Timestamp`","`fields.Op_code`","`fields.tid`","`fields.jcfParam`","`fields.Trace_id`","`ulmp.inkey`").show(20)
output:
+---------------+--------------------+--------------+-----------+---------------+--------------------+----------+
|fields.Login_no|    fields.Timestamp|fields.Op_code| fields.tid|fields.jcfParam|     fields.Trace_id|ulmp.inkey|
+---------------+--------------------+--------------+-----------+---------------+--------------------+----------+
|      A1BXHD001|2019-09-23 16:54:...|          J702|  662425848|           pout|11*20190923164551...|       a01|
|      B0A030339|2019-09-23 16:54:...|              |  850454376|           pout|11*20190923165437...|       a01|
|      A8AZYX004|2019-09-23 16:54:...|          5730| -531230181|           pout|11*20190923165609...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|              | 1647345168|            pin|                    |       a01|
|      N0BZFW001|2019-09-23 16:55:...|          1000|  398358364|            pin|11*20190923165203...|       a01|
|      N0BZFW001|2019-09-23 16:55:...|          1000|  398358364|           pout|11*20190923165203...|       a01|
|      ASBDHX002|2019-09-23 16:55:...|          1000| 1765858292|           pout|11*20190923164948...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|          4317|-2140972746|           pout|11*20190923165459...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|          4317| -384410219|           pout|11*20190923165459...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|          4317| -384410219|           pout|11*20190923165459...|       a01|
|      K3BEJ0001|2019-09-23 16:55:...|          1000|-1000205510|            pin|11*20190923165310...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|          4317| 1591331815|            pin|11*20190923165459...|       a01|
|      M5BMEG001|2019-09-23 16:55:...|          4317| 1092476648|           pout|11*20190923165459...|       a01|
|      ASAND0001|2019-09-23 16:55:...|              |  778707156|            pin|11*20190923144609...|       a01|
|      N0BZFW001|2019-09-23 16:55:...|          1000| 1331934291|            pin|11*20190923165203...|       a01|
|      M3BBEE502|2019-09-23 16:55:...|          1487|-2086653415|           pout|11*20190923164732...|       a01|
|      N0BZFW001|2019-09-23 16:55:...|          1000| 1480993097|            pin|11*20190923165203...|       a01|
|      A1BXHD001|2019-09-23 16:55:...|              |  147034812|            pin|11*20190923164551...|       a01|
|      ASAND0001|2019-09-23 16:55:...|          4317|-1396355690|           pout|11*20190923170313...|       a01|
|      C0B118002|2019-09-23 16:55:...|          8089|  -88052277|           pout|11*20190923165502...|       a01|
+---------------+--------------------+--------------+-----------+---------------+--------------------+----------+
only showing top 20 rows

你已經知道了你要選擇哪些字段以後,抽出這些字段,形成一個新的dataframe:

'''
.alias()修改列名的方法
.substring_index()截取某字段中的一部分的方法
'''
log_structured=log_data.select(log_data["`fields.Login_no`"].alias("login_no"),
                           log_data["`fields.Timestamp`"].alias("time"),
                           log_data["`fields.Op_code`"].alias("op_code"),
                           log_data["`fields.SpanName`"].alias("service_name"),
                           log_data["`ulmp.inkey`"].alias("source"),
                           log_data["`fields.Trace_id`"].alias("trace_id"),
                           log_data["`fields.jcfParam`"].alias("param_type"),
                           log_data["`fields.tid`"].alias("tid"),
                            #使用substring_index函數抽取log.message的字段
                           substring_index(substring_index(log_data["`log.message`"],'~!~', -1),'~$~',1).alias("service_json"),
                          )

這裏詳細說明一下抽取字段時的一些問題:

【問題一】:如果路徑名中帶有點”.”的話,如果直接使用點的話,會報錯。從報錯裏看你可能會疑惑,明明裏面有爲什麼報取不出來,比如我這邊取列的時候就遇到了這個問題,我折騰了好久才定位到原因;

【解決方案】:在路徑名中帶有點”.”的情況下,我們要使用反引號”`”將一個完整名字包裹起來,讓Spark SQL認爲這是一個完整的整體而不是兩層路徑,就像我上面的代碼;

這裏有一篇博客把這個問題講得很仔細,貼出供參考:https://blog.csdn.net/wang_wbq/article/details/79675522

【問題二】:有一個值很長的字段,你只想取其一部分,怎麼做呢?

【解決方案】:使用substring_index函數,先用分隔符分開,再取分隔符前後的值,有點類似於Python中的split("")[]函數,官方API展示了這個函數的使用方法:

>>> df = spark.createDataFrame([('a.b.c.d',)], ['s'])
>>> df.select(substring_index(df.s, '.', 2).alias('s')).collect()
[Row(s=u'a.b')]
>>> df.select(substring_index(df.s, '.', -3).alias('s')).collect()
[Row(s=u'b.c.d')]

看一下我們抽取字段後形成的數據

log_structured.show(5)

output
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
| login_no|                time|op_code|service_name|source|            trace_id|param_type|       tid|        service_json|
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
|A1BXHD001|2019-09-23 16:54:...|   J702|  sICertScan|   a01|11*20190923164551...|      pout| 662425848|{"ROOT":{"RETURN_...|
|B0A030339|2019-09-23 16:54:...|       | sGBM_authen|   a01|11*20190923165437...|      pout| 850454376|{"ROOT":{"RETURN_...|
|A8AZYX004|2019-09-23 16:54:...|   5730|     sDynSvc|   a01|11*20190923165609...|      pout|-531230181|{"ROOT":{"RETURN_...|
|M5BMEG001|2019-09-23 16:55:...|       | sGBM_authen|   a01|                    |       pin|1647345168|{"ROOT":{"REGION_...|
|N0BZFW001|2019-09-23 16:55:...|   1000|  sPwdNotice|   a01|11*20190923165203...|       pin| 398358364|{"ROOT":{"REQUEST...|
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
only showing top 5 rows

接下來我們需要根據param_type這一列的值將數據分成log_out和log_in,這裏使用filter()方法。

'''
filter()過濾取值
withColumnRenamed()對取出的數據修改列名
'''
log_in=log_structured.filter(log_structured.param_type == "pin").withColumnRenamed("time", "time_in").withColumnRenamed("service_json", "json_in")
log_out=log_structured.filter(log_structured.param_type == "pout").withColumnRenamed("time", "time_out").withColumnRenamed("service_json", "json_out")
print("length of RDD:log_in",log_in.count())
print("RDD:log_in",log_in.show(5))
print("length of RDD:log_out",log_out.count())
print("RDD:log_out",log_out.show(5))

output:
length of RDD:log_in 47
+---------+--------------------+-------+---------------+------+--------------------+----------+-----------+--------------------+
| login_no|             time_in|op_code|   service_name|source|            trace_id|param_type|        tid|             json_in|
+---------+--------------------+-------+---------------+------+--------------------+----------+-----------+--------------------+
|M5BMEG001|2019-09-23 16:55:...|       |    sGBM_authen|   a01|                    |       pin| 1647345168|{"ROOT":{"REGION_...|
|N0BZFW001|2019-09-23 16:55:...|   1000|     sPwdNotice|   a01|11*20190923165203...|       pin|  398358364|{"ROOT":{"REQUEST...|
|K3BEJ0001|2019-09-23 16:55:...|   1000|  s1000GetAutNo|   a01|11*20190923165310...|       pin|-1000205510|{"ROOT":{"REQUEST...|
|M5BMEG001|2019-09-23 16:55:...|   4317|sGOQ_MobInfoQry|   a01|11*20190923165459...|       pin| 1591331815|{"ROOT":{"COMMON_...|
|ASAND0001|2019-09-23 16:55:...|       |   sUserBasInfo|   a01|11*20190923144609...|       pin|  778707156|{"ROOT":{"OPR_INF...|
+---------+--------------------+-------+---------------+------+--------------------+----------+-----------+--------------------+
only showing top 5 rows

RDD:log_in None
length of RDD:log_out 53
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
| login_no|            time_out|op_code|service_name|source|            trace_id|param_type|       tid|            json_out|
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
|A1BXHD001|2019-09-23 16:54:...|   J702|  sICertScan|   a01|11*20190923164551...|      pout| 662425848|{"ROOT":{"RETURN_...|
|B0A030339|2019-09-23 16:54:...|       | sGBM_authen|   a01|11*20190923165437...|      pout| 850454376|{"ROOT":{"RETURN_...|
|A8AZYX004|2019-09-23 16:54:...|   5730|     sDynSvc|   a01|11*20190923165609...|      pout|-531230181|{"ROOT":{"RETURN_...|
|N0BZFW001|2019-09-23 16:55:...|   1000|  sPwdNotice|   a01|11*20190923165203...|      pout| 398358364|{"ROOT":{"RETURN_...|
|ASBDHX002|2019-09-23 16:55:...|   1000|     sDynSvc|   a01|11*20190923164948...|      pout|1765858292|{"ROOT":{"RETURN_...|
+---------+--------------------+-------+------------+------+--------------------+----------+----------+--------------------+
only showing top 5 rows

最後將log_in和log_out數據通過tid字段的值join起來

log_join=log_in.join(log_out,log_in.tid == log_out.tid,"left").select(log_in.login_no,log_in.time_in,log_out.time_out,log_in.op_code,
                                                                      log_in.service_name,log_in.source,log_in.trace_id,log_in.tid,
                                                                     log_in.json_in,log_out.json_out).orderBy(log_in.time_in)

這樣就完成了我們hdfs上的json數據結構化的初步操作,在後面的使用中我們還需要根據自己數據的特點和集羣分配的資源進行相應的調優工作。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章