1: 问题描述:
图谱spark任务离线构图,发现原本有关联的点被分在多个连通图,且在一个连通图出现大量离散点,连通图构成出现错乱。
2: 问题初步排查及背景:
初步判断是spark提供的生成点唯一id算有问题。之前生成全图连通图时,原本提供的dense_rank函数在大数据量时,会有id重复的问题。所以换了一套更优秀的spark生成唯一id的方法,并且用在了离线团伙构建上,之前离线团伙构建并无问题,所以判断问题还是在vid生成算法。
3: 问题排查历程:
1: 切回原算法,发现构图无问题,可以生成正确的连通图,确定了问题就出现在vid算法部分,先保证线上不再出错。
2: 开发新代码,专门用作解决此问题,在一笔自建生产任务中,循环使用点/边数据,生成连通图,观察情况。
3: 将各种中间转换状态落odps表,分析中间转换过程,发现原始边是对的,给string生成的long型点id也唯一,但是转换为long边的时候,和原始边并不能对应。问题排查范围进一步缩小
此时觉得问题就出现join上,查资料并无发现有任何资料显示spark join结果会出错,而且原始算法也用的这一套逻辑,马上否定了自己。但为了确认,还是用原始算法又跑了一遍,发现构图也出错了,但看到odps点/边还是对的,跑了一笔生产的,还是对的。意识到应该是重复跑一笔任务导致的。查看临时写文件的地方是overwirte,没有错。想到应该是es本身数据没覆盖完全。初始化es数据
在kibana初始化es数据。再用原始算法跑,没问题,验证了join肯定是不会错的。
此时困惑中,回头看代码中不一样的地方
在离线构图任务使用的是getEncoderDs,全图逻辑是getVidEncoder,想到是不是创建Dataset时,字段必须对应起来? 本地测试十条没发现问题,测试十万条发现也能对应。这两个方法并无区别。
中间试了一下使用和全图构图一样的逻辑,使用getVidEncoder传入点的tagDs,发现可以正常构图。
想到是应该是悬挂边的问题?因为在转换时,使用了inner join,通过点可以过滤悬挂边,但是通过边的两点,无法过滤。
但觉得还不是真正的原因,因为全图构图过滤了悬挂边,还是有这个问题,说明真正的原因还是在隐藏中。再仔细查看这两个方法的实现,除了生成点id的算法不一致,好像并无差别。还是得回到生成点id算法中。
再仔细看这一段,发现果然是有点问题,encoderDs 使用了三次,考虑到spark机制,如果内存不足,会被gc,ds会重新触发计算,想到应该是这里的问题。
加了一个cache,果然发现问题解决,真正问题的原因还是因为encoderDs发生了重复计算,两次生成的唯一id不一致。
至于为什么原算法没问题。是因为原算法是一个窗口排序函数,根据vid排序,不管计算多少次,点的顺序总是唯一的,对应的long型 id也是唯一的。
4:问题结论
spark生成唯一点id的dataset,因为gc导致了重复计算,两次生成id不一致,导致了从string边转long边的转换中间过程出错,从全图构图到离线构图一直都是这个问题,至此才真正发现问题原因。
5: 问题解决
1: 给生成的encoderDs 加cache,但可能会导致oom.
2: 将生成的encoderDs写入hdfs,以后每次用从hdfs里取出来,这样即使重复计算也不会发生变化。