需求
在互联网中,我们经常会见到城市热点图这样的报表数据,例如在百度统计中,会统计今年的热门旅游城市、热门报考学校等,会将这样的信息显示在热点图中。
因此,我们需要通过日志信息(运行商或者网站自己生成)和城市ip段信息来判断用户的ip段,统计热点经纬度。
日志信息介绍
涉及到两个文件ip.txt 和 20090121000132.394251.http.format
- ip.txt 字段介绍
- ip段起始位置 和 ip段结束位置
- 起始ip地址hash 和 结束ip地址hash
- 地址
- 通信商
- 邮编
- 国家 和 国家缩写
- 经纬度
- 字段介绍
- id
- ip地址
- 网址
- 浏览器信息
思路
- 加载城市ip段信息,获取ip起始数字和结束数字,经度,纬度
- 加载日志数据,获取ip信息,然后转换为数字,和ip段比较
- 比较的时候采用二分法查找,找到对应的经度和纬度
- 对相同的经度和纬度做累计求和
代码
#导入环境
import os
JAVA_HOME = '/root/bigdata/jdk'
PYSPARK_PYTHON = "/miniconda2/envs/py365/bin/python"
os.environ["PYSPARK_PYTHON"]= PYSPARK_PYTHON
os.environ["PYSPARK_DRIVER_PYTHON "]= PYSPARK_PYTHON
os.environ['JAVA_HOME'] = JAVA_HOME
from pyspark import SparkContext
#将ip转换为特殊的数字形式 223.243.0.0|223.243.191.255| 255 2^8
#11011111
#00000000
#1101111100000000
# 11110011
#11011111111100110000000000000000
def get_ip_hash(ip):
ans = 0
ip_list = ip.split('.')
for ip_num in ip_list:
ans = int(ip_num) | ans<<8
return ans
#二分法查找ip对呀的行索引
def binary_search(ip_num,cvb):
start = 0
end = len(cvb)-1
while(start <= end):
mid = int((start+end)/2)
if(ip_num >= int(cvb[mid][0]) && ip_num <= int(cvb[mid][1])):
return mid
else if(ip_num< int(cvb[mid][0])):
end = mid
else
start = mid
if __name__ == '__main__':
#创建spark context 第一个参数master的位置 第二个参数为名称
sc = SparkContext('loacl[2]','ipTopN')
city_id_rdd = sc.textFile('file:///root/tmp/ip.txt').\
map(lambda x:x.spilt('|')).\
map(lambda x:x[2],x[3],x[13],x[14])
dest_data = sc.textFile('file:///root/tmp/20090121000132.394251.http.format').\
map(lambda x:x.spilt('|')[1])
temp = city_id_rdd.collect()
#创建广播变量,避免数据反复复制
city_broadcast = sc.broadcast(temp)
def get_pos(x):
cvb = city_broadcast.value
def get_result(ip):
ip_num = get_ip_hash(ip)#取到IP地址hash
index = binary_search(ip_num,cvb)#匹配IP库找到索引
return ((cvb[index][2],cvb[index][3]),1) #返回经纬度
result = map(tuple,[get_result(ip) for ip in x])
return result
# 用mappartition一部分一部分传给函数 减少连接资源的使用 反正频繁连接断开
data_rdd = dest_data.mappartition(lambda x:get_pos(x))#得到((经度,纬度),1)的集合
# 相同经度纬度的合并并排序
result_rdd = dest_data.reduceByKey(lambda a,b:a+b).sortby(lambda x:x[1],ascending=False)
print(result_rdd.collect())
关键点
- 广播变量的使用
- 要统计IP所对应的经纬度, 每一条数据都会去查询ip表
- 每一个task 都需要这一个ip表, 默认情况下, 所有task都会去复制ip表(ip.txt)
- 实际上 每一个Worker上会有多个task, 数据也是只需要进行查询操作的, 所以这份数据可以共享,没必要每个task复制一份
- 可以通过广播变量, 通知当前worker上所有的task, 来共享这个数据,避免数据的多次复制,可以大大降低内存的开销
- sparkContext.broadcast(要共享的数据)
- mapPartitions
- transformation操作
- 类似map 但是map是一条一条传给里面函数的 mapPartitions 数据是一部分一部分传给函数的
- 应用场景 数据处理的时候 需要连接其它资源 如果一条一条处理 会处理一条连一次, 一份一份处理可以很多条数据连一次其它资源 可以提高效率