ip地址查询
1. 需求分析
在互联网中,我们经常会见到城市热点图这样的报表数据,例如在百度统计中,会统计今年的热门旅游城市、热门报考学校等,会将这样的信息显示在热点图中。
因此,我们需要通过日志信息(运行商或者网站自己生成)和城市ip段信息来判断用户的ip段,统计热点经纬度。
2. 技术调研
因为我们的需求是完成一张报表信息,所以对程序的实时性没有要求,所以可以选择内存计算spark来实现上述功能。
3. 架构设计
搭建spark集群
4. 开发流程
4.1. 数据准备
ip日志信息
在ip日志信息中,我们只需要关心ip这一个维度就可以了,其他的不做介绍
城市ip段信息
4.2. 代码开发
思路
1、 加载日志数据,获取ip信息
2、 加载ip段信息,获取ip起始和结束数字,城市信息
3、 将日志的ip分割出来,转换为数字,和ip段比较
4、 比较的时候采用二分法查找
5、 过滤出相应的值
6、存到mysql node03数据库
7、之后就可以从mysql中取数据 做热点图啦
实现
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
package cn.itcast.rdd
import java.sql.{Connection, DriverManager, PreparedStatement}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
*需求:
* 通过日志信息(运行商或者网站自己生成)和城市ip段信息来判断用户的ip段,统计热点经纬度。
*
*
* 思路
* 1、 加载日志数据,获取ip信息
* 2、 加载ip段信息,获取ip起始和结束数字,城市信息
* 3、 将日志的ip分割出来,转换为数字,和ip段比较
* 4、 比较的时候采用二分法查找
* 5、 过滤出相应的值
*
* 需添加一个mysql驱动依赖包
*
*
* 需先建好数据库表
* create table `iplocation`( `longitude` varchar(20), `latitude` VARCHAR(20), `total_count` bigint )
*/
//todo:利用spark来实现IP地址查询
object IpLocation {
//实现把String类型的ip转换成Long 192.168.200.100
def ip2Long(ip:String):Long={
val ips: Array[String] = ip.split("\\.")
var ipNum:Long=0L //初始
//遍历
for(i <- ips){
ipNum = i.toLong | ipNum << 8L // 或异 左移8位 //没有很懂
}
ipNum
}
//二分查找法
def binarySearch(ipNum:Long,broadcastValue:Array[(String,String,String,String)]):Int={
//定义开始下标
var start = 0 //可变
//定义结束下标
var end = broadcastValue.length - 1
//遍历
while(start<end){
//获取中间下标
val middle=(start+end)/2
if(ipNum >= broadcastValue(middle)._1.toLong && ipNum <= broadcastValue(middle)._2.toLong){ //表示在这个区间里面
return middle
}
if(ipNum < broadcastValue(middle)._1.toLong){
end = middle
}
if(ipNum>broadcastValue(middle)._2.toLong){
start=middle
}
}
-1 //如果没有找到这个下标 直接返回-1
}
//定义一个方法 把结果数据写入到mysql数据库中
def data2mysql(iter: Iterator[((String, String), Int)]) : Unit = { //ctrl+N后还是改过的
/**
* 每次以分区为单位 建立mysal连接
*/
//定义数据库连接
var conn:Connection=null
//定义PrepareStatement
var ps:PreparedStatement=null
//定义sql语句
val sql = "insert into iplocation(longitude,latitude,total_count) values(?,?,?)"
conn = DriverManager.getConnection("jdbc:mysql://192.168.239.133:3306/spark","root","123456")
ps=conn.prepareStatement(sql)
//遍历iter
// ctrl+alt+t try-catch-finally
try {
iter.foreach(line => {
//给每一个问号赋值
ps.setString(1, line._1._1) //元组的第一位还是个元组 再取这个元组的第一位
ps.setString(2, line._1._1)
ps.setLong(3, line._2)
//执行
ps.execute()
})
} catch {
//抛异常
case e:Exception => println(e)
} finally {
//关闭连接
if(ps != null) {
ps.close()
}
if(conn != null){
conn.close()
}
}
}
def main(args: Array[String]): Unit = {
//1.创建SparkConf
val sparkConf: SparkConf = new SparkConf().setAppName("IpLocation").setMaster("local[2]")
//2.创建SparkContent
val sc = new SparkContext(sparkConf)
sc.setLogLevel("warn")
//3.读取城市的ip段信息 获取(ip开始数字、ip结束数字、经度、维度)
val city_ip_rdd: RDD[(String, String, String, String)] = sc.textFile("F:\\AAAA_HM大数据\\00-课件\\18_spark\\spark\\day02\\资料\\服务器访问日志根据ip地址查找区域\\ip.txt").map(_.split("\\|")).map(x=>(x(2),x(3),x(x.length-2),x(x.length-1)))
//利用广播变量把数据下发到每个worker节点
val cityIpBroadcast: Broadcast[Array[(String, String, String, String)]] = sc.broadcast(city_ip_rdd.collect())
//4.读取运营商日志数据,获取所有的ip地址
val ips: RDD[String] = sc.textFile("F:\\AAAA_HM大数据\\00-课件\\18_spark\\spark\\day02\\资料\\服务器访问日志根据ip地址查找区域\\20090121000132.394251.http.format").map(_.split("\\|")(1))
//5.遍历ips 获取每一个ip地址,然后把ip地址转换成Long数字,进行比较,获取这个ip对应的数字在数组中的下标
val result: RDD[((String, String), Int)] = ips.mapPartitions(iter => {
/**
* 先写的中间这块 最后ctrl+alt+v 用result来接收
*/
//获取广播变量值
val boradcastValue: Array[(String, String, String, String)] = cityIpBroadcast.value
//遍历迭代器 获取每一个ip地址
iter.map(ip => {
//将ip地址转换成数字Long类型
val ipNum: Long = ip2Long(ip) //117.101.201.237
//拿到ipNum去广播变量的值中匹配,获取对应的下标(二分查找法)
val index: Int = binarySearch(ipNum, boradcastValue)
//按照下标获取经度纬度
val value: (String, String, String, String) = boradcastValue(index)
//返回((经度,纬度),1)
((value._3, value._4), 1)
})
})
//6.相同经度和纬度出现的1累加
val finalResult: RDD[((String, String), Int)] = result.reduceByKey(_+_)
//7.打印输出
finalResult.foreach(println) //是个action
//把结果数据写入到mysql中
finalResult.foreachPartition(data2mysql)
//8.关闭
sc.stop()
}
}
/*
打印输出
((经纬度区间),ip数)
((108.948024,34.263161),1824)
((107.7601,29.32548),85)
((106.504962,29.533155),400)
((107.39007,29.70292),47)
((106.56347,29.52311),3)
((116.405285,39.904989),1535)
((106.27633,29.97227),36)
((114.502461,38.045474),383)
((107.08166,29.85359),29)
((102.712251,25.040609),126)
((106.57434,29.60658),177)
((106.51107,29.50197),91)
*/
test
import cn.itcast.rdd.IpLocation
/**
* 1.0.1.0|1.0.3.255|16777472|16778239|亚洲|中国|福建|福州||电信|350100|China|CN|119.306239|26.075302
*/
object IPTest {
def main(args: Array[String]): Unit = {
val ipNum: Long = IpLocation.ip2Long("1.0.3.255")
println(ipNum)
}
}
/*
16778239
*/