Python 由于Spark'导致结果不一致;s懒惰评估

Python 由于Spark'导致结果不一致;s懒惰评估,python,apache-spark,pyspark,Python,Apache Spark,Pyspark,我有一个简单的pyspark代码: l = [ {'userId': 'u1', 'itemId': 'a1', 'click': 1}, {'userId': 'u1', 'itemId': 'a2', 'click': 0}, {'userId': 'u2', 'itemId': 'b1', 'click': 1}, {'userId': 'u2', 'itemId': 'b2', 'click': 1}, ] d = sc.parallelize(l)

我有一个简单的
pyspark
代码:

l = [
    {'userId': 'u1', 'itemId': 'a1', 'click': 1},
    {'userId': 'u1', 'itemId': 'a2', 'click': 0},
    {'userId': 'u2', 'itemId': 'b1', 'click': 1},
    {'userId': 'u2', 'itemId': 'b2', 'click': 1},
]

d = sc.parallelize(l)
基本上,第一个用户单击两个项目中的一个,而第二个用户同时单击两个项目

让我们按
userId
对事件进行分组,并在函数中处理这些事件

def fun((user_id, events)):
    events = list(events)
    user_id = events[0]['userId']

    clicked = set()
    not_clicked = set()

    for event in events:
        item_id = event['itemId']
        if event['click']==1:
            clicked.add(item_id)
        else:
            not_clicked.add(item_id)

    ret = {'userId': user_id, 'click': 1}
    for item_id in clicked:
        ret['itemId'] = item_id
        yield ret

    ret['click'] = 0
    for item_id in not_clicked:
        ret['itemId'] = item_id
        yield ret

d1 = d\
    .map(lambda obj: (obj['userId'], obj))\
    .groupByKey()\
    .flatMap(fun)

d1.collect()
这就是我得到的:

[{'click': 1, 'itemId': 'a1', 'userId': 'u1'},
 {'click': 0, 'itemId': 'a2', 'userId': 'u1'},
 {'click': 1, 'itemId': 'b1', 'userId': 'u2'},
 {'click': 0, 'itemId': 'b2', 'userId': 'u2'}]
用户
u2
的结果不正确

有人能解释为什么会发生这种情况,以及预防这种情况的最佳做法是什么


谢谢。

您所看到的与Spark评估模型关系不大。你的代码有问题。在本地执行时,很容易看到这一点:

key = 'u2'

values = [
    {'click': 1, 'itemId': 'b1', 'userId': 'u2'},
    {'click': 1, 'itemId': 'b2', 'userId': 'u2'}
]

list(fun((key, values)))
[{'click':0,'itemId':'b2','userId':'u2},
{'click':0,'itemId':'b2','userId':'u2'}]
正如你们所看到的,这比你们从Spark得到的东西更有意义。问题是在不应该使用可变数据的情况下使用可变数据。由于修改相同的
dict
后,所有
生成的
返回完全相同的对象:

(d1, d2) = list(fun((key, values)))
d1 is d2
True
我认为与Spark相比的差异与批处理序列化有关,在函数退出之前,第一项在不同批中序列化,有效顺序大致如下:

import pickle
from itertools import islice, chain 

gen = fun((key, values))

# The first batch is serialized
b1 = [pickle.dumps(x) for x in list(islice(gen, 0, 1))]

# Window is adjusted and the second batch is serialized
# fun exits with StopIteration when we try to take 
# the second element in the batch
# element so code proceeds to ret['click'] = 0
b2 = [
    pickle.dumps(x) for x in
    # Use list to eagerly take a whole batch before pickling
    list(islice(gen, 0, 2))  
] 

[pickle.loads(x) for x in chain(*[b1, b2])]
[{'click':1,'itemId':'b1','userId':'u2},
{'click':0,'itemId':'b2','userId':'u2'}]
但如果您想要一个最终的确认,您必须自己检查它(用一个等待所有数据的序列化程序替换成批序列化程序)

如何解决?只是不要用同一本字典。而是在循环中初始化一个新的循环:

for item_id in clicked:
    yield {'userId': user_id, 'click': 1, 'item_id': item_id}

for item_id in not_clicked:
    yield {'userId': user_id, 'click': 0, 'item_id': item_id}