需求:
* 根据数据应用库,查找ip对应的省份,将数据写出到mysql。(二分查找方法)
* 使用广播变量进行执行优化。
import java.sql.{Connection, DriverManager, PreparedStatement} import org.apache.spark.broadcast.Broadcast import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /** * Created by hqs on 2018/1/28. * 根据数据应用库,查找ip对应的省份,将数据写出到mysql。(二分查找方法) * 使用广播变量进行执行优化。 * 实现代码重构。 */ object IpLocation3 { //字符串ip转十进制 def ip2Long(ip: String): Long = { val fragments = ip.split("[.]") var ipNum = 0L for (i <- 0 until fragments.length) { ipNum = fragments(i).toLong | ipNum << 8L } ipNum } //二分查找实现 def search(ip:Long,ipArray:Array[(Long,Long,String)]):Int ={ var start = 0 var end = ipArray.length-1 while(start<=end){ val middle = (start+end) >> 1 val middleKey = ipArray(middle) val middleKeyStart = middleKey._1 val middleKeyEnd = middleKey._2 if(ip >= middleKeyStart && ip <= middleKeyEnd){ return middle }else if(ip < middleKeyStart){ end = middle -1 }else{ start = middle +1 } } //若无法匹配,返回-1 -1 }
总结1:广播变量缓存在各个而写从投入的内存中,而不是每个task。def main(args: Array[String]): Unit = { val conf = new SparkConf() .setMaster("local") .setAppName(IpLocation3.getClass.getName) val sc = new SparkContext(conf) val ipData: RDD[String] = sc.textFile("ip.txt") val ipRule:RDD[(Long,Long,String)] = ipData.map({ line=> val fields = line.split("[|]") val start = fields(2).toLong val end = fields(3).toLong val province = fields(6) (start,end,province) }) val ipRules = ipRule.collect() //使用广播变量,将数据广播到executor。多个task使用这个数据。减少连接请求。 val ipbc: Broadcast[Array[(Long, Long, String)]] = sc.broadcast(ipRules) val accessData: RDD[String] = sc.textFile("access.log") val proAndOne:RDD[(String,Int)] = accessData.map({ line => val ipLong:String = line.split("[|]")(1) //将字符串的ip转为十进制的ip。 val ip = ip2Long(ipLong) //将广播变量里面的数据取出,使用。 val ipArray: Array[(Long, Long, String)] = ipbc.value //二分查找数据在引用库中的索引。 val index = search(ip,ipArray) var province = "unknow" if(index != -1){ province = ipArray(index)._3 } (province,1) }) //产生结果。 val res: RDD[(String, Int)] = proAndOne.reduceByKey(_+_).sortBy(-_._2) res.foreachPartition({ it => //将结果写入到数据库。获得连接,写数据,关流。 var conn: Connection = null var pst: PreparedStatement = null var pst1: PreparedStatement = null try{ val url = "jdbc:mysql://localhost:3306/car?characterEncoding=utf-8" conn = DriverManager.getConnection(url,"root","123456") pst = conn.prepareStatement("create table if not exists access_log(province varchar(20),count int)") pst.execute() pst1 = conn.prepareStatement("insert into access_log values(?,?)") it.foreach({ v => pst1.setString(1,v._1) pst1.setInt(2,v._2) pst1.execute() }) }catch{ case e : Exception => e.printStackTrace() }finally{ if(pst1 != conn) pst1.close() if(pst != conn) pst.close() if(null != conn) conn.close() } }) //释放资源。 sc.stop() } }
广播变量被创建之后,可以在集群的任何函数中调用。通过 -- .value获取
广播变量是只读的,不能修改。rdd不能被广播,需要收集后再通过sc广播。
总结2:jdbc连接mysql数据库思想。导入jar包。建立连接对象,预处理SQL语句,
执行,关闭连接通道。
总结3:Spark数据写出到mysql中,使用foreachPartition方法,减少数据库连接。