导语: 本文主要讲述如何将客户端提供的IPv6数据聚合,从而应用于有IPv6查询需求的业务。
数据来源
本文计算所用的数据来自于客户端提供的IPv6-IPv4的双栈数据源,上报的一条日志记录包括一个IPv6和IPv4地址,根据IPv4地址进行查询,可以得到对应的IPv6地址的国家、省份、城市、运营商等重要信息,根据这些地理位置属性,便可以将属性相同的IPv6地址聚合成段。
理论基础
IPv6有128位,其中后64位是接口id,只有前64位参与网络分配。故在IPv6聚合数据时,可以忽略掉后64位,这样可以简化IPv6的数据结构表示,减少后续计算的麻烦。
一般在IP库中,存储的不是单个的ip,而是属性相同的ip段。在IPv4的地址库里,IPv4地址段的基本单位是一个C段,同一个C段内的是地理位置以及运营商信息相同的C类地址,一个IPv4地址段中的IPv4地址前24位是相同的。因此,IPv4的地址库可以通过C段的表示方式,在合并相邻的C段后,可以将四十多亿的IPv4压缩到几百万条的IP段。
对于IPv6来说,因为它采用了类似于CIDR(无分类编址)的实现,所以没有办法按照固定的前缀长度对IPv6地址段进行定义。尽管如此,本文所说IPv6地址的聚合还是基于“同一个IP段的前缀是相同”这个理论,只不过IP段的公共前缀长度不是统一的,我参考了IPv6地址规划与分配 这篇文章,一般用来的分配的IPv6地址段的前缀长度在40位与64位之间,IP段的公共前缀长度越长,IP段的粒度也越细,IP段数量越多,但聚合效果也越差;但如果公共前缀过短,IP段粒度过粗,又无法保证该IP段的精度。对于本文的聚合运算来说,需要结合各种粒度的运算才能得到一些精确的ip段,例如,省份已知的ip段粒度就较粗,可以用较短的公共前缀表示;城市和区县已知的ip段粒度就较细,可以用长一点的公共前缀表示。
整体流程
原始数据存放在hive表中,数据周期为一周;IPv6聚合计算是采用scala编写的spark程序,每周进行一次计算。目前该计算有两种维度,一个是根据(省份+运营商)进行聚合,另外一种是根据(城市+运营商)进行聚合。以下展示的是省份级别的聚合。
在省份级别的聚合中,对于省份已知的IPv6地址,我从N=40开始聚合,即是将前40位前缀相同的IPv6地址归类在一起,得到一个/40的IP段,选出其中出现次数最多的省份,以及该省份内出现次数最多的运营商,用来定义最终这个IP段的省份和运营商。若该省份和运营商在该段内的数据占比越大,说明该属性就越能代表该IP段,该IP段划分得越准确,IP段的精度越高;若该段内夹杂的其他省份和运营商比较多,则说明不能直接定义该IP段,还需要再细分,于是我继续重复以上计算,增大IP段的公共前缀长度N,尝试将前48位前缀相同的ip聚合在一起,得到一系列48/的IP段,继续判断这些段的精度是否够高。。以此类推,从粗粒度往细粒度一步步聚合。通过这样的划分,就能得到一系列的前缀长度不同一的IP段。这些IP段的精度都必须够高,目前我设定的阈值是0.9,即是IP段内90%以上的IP需要有相同的省份和运营商。
接下来,查找有哪些IP段是相邻比较近的,这些段可以尽量合并,从而减少IP段的数量。然后,再把计算得到的不同粒度的IP段合并到一个数组中;最后,再把当次计算的IP段与历史数据中的IP段合并,就能得到最终的IPv6地址段。剩下那些未能归入某个IP段内的单个IPv6地址,会放入累计池中,参与下一周期的计算。
细节处理
1. IP段的具体定义
class IPv6Section { var startIp:String = "" //ip段起点ip(16进制字符串) var endIp:String = "" //ip段结束ip var ipinfo:IPInfo = new IPInfo() //存放IP段的地理位置、运营商信息 var update_time = 0l //更新时间 var level = 0 //IP段的地理级别,0,1,2代表省份,城市,区县 var accuracy = 0d //精度 var pointNum = 0l //ip段内的点数量}
2. IP段的合并
由于IP库客户端查询ip是采用二分查找的方式,在一个ip段数组中查询ip所处的段,因此ipv6的段也需要“铺平”为数组的形式,在计算之后将不同粒度的ip段合并起来。合并过程中会遇到ip段重叠的情况,这时候就需要根据一些指标,保留一些段和删除一些段。首先,新的ip段优先覆盖旧的ip段,例如本周期计算的ip段会覆盖上一个周期计算得到的ip段;其次,地理级别越高的ip段,会覆盖级别低的ip段,例如深圳市已知的ip段会优先覆盖仅广东省已知的ip段;最后,若地理级别相同,则会比较ip段的精度,精度越高,说明该ip段越可靠,能够优先覆盖精度低的ip段。
3. 数据过期淘汰
实际计算中发现,ipv6的数据比想象中要稀疏,大概有30%~40%左右的ip段只是包含了一条ip记录,换言之仅仅是根据一条ip记录就能得到该ip段,这样无法排除偶然性。为了清洗一些可能不可靠的ip段,每次计算时,都会统计有哪些ip段是有新数据“命中”的,若长时间没有数据记录落到这个ip段中,则说明该段不可靠或者是已经“过期”了,该数据则会被清洗掉。
数据的准确性
目前IPv6地址库共有ip段96万个,省份的精确度在95%左右,城市的精确度在75%左右。这里的精确度指的是:每次计算前,会用当前的ip库查询数据源的每条记录,若一条ipv6-ipv4的记录中,ipv6的查询结果与ipv4的查询结果是一致的,则可以判断是准确的。目前城市级别的精确度不高,是因为按照城市粒度划分的ipv6数据过于稀疏,无法像省份已知的数据那样快速地聚合成一个大段,这方面会在以后进一步改进。