spark graphx 实现二跳邻居统计——使用pregel

本文是对二跳邻居统计的实战,因为用到了pregel,需要对pregel模型有一些大致的了解,例如各个参数的意义,各个函数的作用,以及大致的流程。最核心的应该就是消息发送函数这个部分,注释中有对两轮迭代的过程有解释。

def main(args: Array[String]): Unit = {
    case class Person(id: String, tel: String, relatel: String, fritel: String, birthplace: String, homeaddress: String)
    case class Relation(ty: Int)

    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)
    @transient
    val conf = new SparkConf().setAppName("RelatianNet")
    conf.setMaster("local")

    @transient
    val sc = new SparkContext(conf)
    val vertices = sc.textFile("vertex1.txt")//节点包含用户的id等信息
    val id_node_table = new scala.collection.mutable.HashMap[String, Int]()//存放节点id和对应编号的键值对
    var nodeid = 0
    for (line <- Source.fromFile("vertex.txt", "GBK").getLines) {
      val row = line.split("\t")
      id_node_table += (row(0) -> nodeid)//给每个节点赋予编号
      nodeid += 1
    }
    val verticesRDD: RDD[(VertexId, Person)] = vertices map { line
    =>
      val row = line.split("\t")
      (id_node_table(row(0)), Person(row(0), row(1), row(2), row(3), row(4), row(5)))
    }
    println(id_node_table.size)
    val edges = sc.textFile(path + "edges1.txt")
    val edgesRDD: RDD[Edge[Relation]] = edges map { line
    =>
      val row = line.split("\t")
      Edge(id_node_table(row(0)), id_node_table(row(1)), Relation(row(2).toInt)) //EDGE表由客户->联系人以及客户->第二联系人两张边表组成
    }
    val relationNet: Graph[Person, Relation] = Graph(verticesRDD, edgesRDD)
    val result = relationNet.degrees.filter(x => x._2 >= 2).count()
    println(result + "个人的关系人多于2人")


    //pregel实现
    type VMap = Map[VertexId, Int] //定义每个节点存放的数据类型,为若干个(节点编号,一个整数)构成的map,当然发送的消息也得遵守这个类型

    /**
      * 节点数据的更新 就是集合的union
      */
    def vprog(vid: VertexId, vdata: VMap, message: VMap) //每轮迭代后都会用此函数来更新节点的数据(利用消息更新本身),vdata为本身数据,message为消息数据
    : Map[VertexId, Int] = addMaps(vdata, message)

    /**
      * 节点数据的更新 就是集合的union
      */
    def sendMsg(e: EdgeTriplet[VMap, _]) = {
    //取两个集合的差集  然后将生命值减1
	val srcMap = (e.dstAttr.keySet -- e.srcAttr.keySet).map { k => k -> (e.dstAttr(k) - 1) }.toMap
	val dstMap = (e.srcAttr.keySet -- e.dstAttr.keySet).map { k => k -> (e.srcAttr(k) - 1) }.toMap       
	if (srcMap.size == 0 && dstMap.size == 0)         
		Iterator.empty       
	else         
		Iterator((e.dstId, dstMap), (e.srcId, srcMap))//发送消息的内容    
    }    
		
    /**      
      * 消息的合并      
    */    
    def addMaps(spmap1: VMap, spmap2: VMap): VMap =      
	(spmap1.keySet ++ spmap2.keySet).map { //合并两个map,求并集        
		k => k -> math.min(spmap1.getOrElse(k, Int.MaxValue), spmap2.getOrElse(k, Int.MaxValue)) //对于交集的点的处理,取spmap1和spmap2中最小的值      
	}.toMap   
	
    val two = 2    //这里是二跳邻居 所以只需要定义为2即可    
    val newG = relationNet.mapVertices((vid, _) => Map[VertexId, Int](vid -> two)) //每个节点存储的数据由一个Map组成,开始的时候只存储了 (该节点编号,2)这一个键值对      
	.pregel(Map[VertexId, Int](), two, EdgeDirection.Out)(vprog, sendMsg, addMaps)    
    //pregel参数    
    //第一个参数 Map[VertexId, Int]() ,是初始消息,面向所有节点,使用一次vprog来更新节点的值,由于Map[VertexId, Int]()是一个空map类型,所以相当于初始消息什么都没做    
    //第二个参数 two,是迭代次数,此时two=2,代表迭代两次(进行两轮的active节点发送消息),第一轮所有节点都是active节点,第二轮收到消息的节点才是active节点。    
    //第三个参数 EdgeDirection.Out,是消息发送方向,out代表源节点-》目标节点 这个方向    //pregel 函数参数    //第一个函数 vprog,是用户更新节点数据的程序,此时vprog又调用了addMaps    
    //第二个函数 sendMsg,是发送消息的函数,此时用目标节点的map与源节点的map做差,将差集的数据减一;然后同样用源节点的map与目标节点的map做差,同样差集的数据减一        
    //第一轮迭代,由于所有节点都只存着自己和2这个键值对,所以对于两个用户之间存在变关系的一对点,都会收到对方的一条消息,内容是(本节点,1)和(对方节点,1)这两个键值对        
    //第二轮迭代,收到消息的节点会再一次的沿着边发送消息,此时消息的内容变成了(自己的朋友,0)    //第三个函数 addMaps, 是合并消息,将map合并(相当于求个并集),不过如果有交集(key相同),那么,交集中的key取值(value)为最小的值。    
	
    //过滤得到二跳邻居 就是value=0 的顶点    
    val twoJumpFirends = newG.vertices      
	.mapValues(_.filter(_._2 == 0).keys) //由于在第二轮迭代,源节点会将自己的邻居(非目标节点)推荐给目标节点——各个邻居就是目标节点的二跳邻居,并将邻居对应的值减为0,    
    //twoJumpFirends.collect().foreach(println(_))    
    twoJumpFirends.filter(x => x._2 != Set()).foreach(println(_)) //把二跳邻居集合非空的(点,{二跳邻居集合})打印出来
} 

 






  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值