我是Spark的新手,我正在尝试开发一个python脚本,它读取带有一些日志的csv文件:
userId,timestamp,ip,event
13,2016-12-29 16:53:44,86.20.90.121,login
43,2016-12-29 16:53:44,106.9.38.79,login
66,2016-12-29 16:53:44,204.102.78.108,logoff
101,2016-12-29 16:53:44,14.139.102.226,login
91,2016-12-29 16:53:44,23.195.2.174,logoff
并检查用户是否有某些奇怪的行为,例如,如果他连续两次“登录”而未进行“注销”。我已经将csv作为Spark dataFrame加载了,我想比较单个用户的日志行,按时间戳排序并检查两个连续事件是否属于同一类型(login-login,logoff-logoff)。我正在寻找以'map-reduce'方式进行的操作,但目前我无法弄清楚如何使用比较连续行的reduce函数。 我编写的代码有效,但性能非常糟糕。
sc = SparkContext("local","Data Check")
sqlContext = SQLContext(sc)
LOG_FILE_PATH = "hdfs://quickstart.cloudera:8020/user/cloudera/flume/events/*"
RESULTS_FILE_PATH = "hdfs://quickstart.cloudera:8020/user/cloudera/spark/script_results/prova/bad_users.csv"
N_USERS = 10*1000
dataFrame = sqlContext.read.format("com.databricks.spark.csv").load(LOG_FILE_PATH)
dataFrame = dataFrame.selectExpr("C0 as userID","C1 as timestamp","C2 as ip","C3 as event")
wrongUsers = []
for i in range(0,N_USERS):
userDataFrame = dataFrame.where(dataFrame['userId'] == i)
userDataFrame = userDataFrame.sort('timestamp')
prevEvent = ''
for row in userDataFrame.rdd.collect():
currEvent = row[3]
if(prevEvent == currEvent):
wrongUsers.append(row[0])
prevEvent = currEvent
badUsers = sqlContext.createDataFrame(wrongUsers)
badUsers.write.format("com.databricks.spark.csv").save(RESULTS_FILE_PATH)
答案 0 :(得分:1)
首先(不相关但仍然相关),请确保每个用户的条目数量不是很大,因为collect
中的for row in userDataFrame.rdd.collect():
是危险的。
其次,你不需要离开DataFrame
区域来使用经典的Python,只需坚持使用Spark。
现在,你的问题。它基本上是“我希望从前一行知道的每一行”:这属于Window
函数的概念,并且确切地说是lag
函数。以下是两篇关于Spark中Window函数的有趣文章:一篇来自Databricks,其中包含Python中的代码,另一篇来自Xinh,其中包含(我认为更容易理解)Scala中的示例。
我在Scala中有一个解决方案,但我认为你会在Python中翻译它:
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions.lag
import sqlContext.implicits._
val LOG_FILE_PATH = "hdfs://quickstart.cloudera:8020/user/cloudera/flume/events/*"
val RESULTS_FILE_PATH = "hdfs://quickstart.cloudera:8020/user/cloudera/spark/script_results/prova/bad_users.csv"
val data = sqlContext
.read
.format("com.databricks.spark.csv")
.option("inferSchema", "true")
.option("header", "true") // use the header from your csv
.load(LOG_FILE_PATH)
val wSpec = Window.partitionBy("userId").orderBy("timestamp")
val badUsers = data
.withColumn("previousEvent", lag($"event", 1).over(wSpec))
.filter($"previousEvent" === $"event")
.select("userId")
.distinct
badUsers.write.format("com.databricks.spark.csv").save(RESULTS_FILE_PATH)
基本上,您只需检索上一行中的值,并将其与当前行的值进行比较,如果匹配是错误的行为,则保留userId
。对于每个userId
的“块”行的第一行,前一个值将为null
:当与当前值进行比较时,布尔表达式将为false
所以没问题这里。