批量地理定位数百万个 IP

Posted

技术标签:

【中文标题】批量地理定位数百万个 IP【英文标题】:Batch geo locate millions of IPs 【发布时间】:2012-08-21 20:39:14 【问题描述】:

我获得了 200 万个 IP 地址和 2500 万个 IP 范围,其中起始 IP、结束 IP 和地理位置存储在 PostgreSQL 中。有没有一种有效的方法可以从 2500 万个数据库中查找这 200 万个 IP 的地理位置?我所做的是比较一个IP地址是否在Start IP和End IP之间,并查找相应的位置。然而,这似乎需要永远。大概这更像是从一组范围中查找一堆整数,例如从以下位置搜索 7, 13, 31, 42:

Start End Loc
1     10  US
11    20  US
21    26  CN
29    32  SE
33    45  CA

然后返回:

7  US
13 US
31 SE
42 CA

请注意,范围可能不一定相连,大小也可能不同。谢谢!

编辑

作为一个具体的例子,这是我正在处理的数据:

     start_ip     |      end_ip      | country |  region   |   city    | 
------------------+------------------+---------+-----------+-----------+-
 1.33.254.73/32   | 1.33.254.73/32   | jpn     | 33        | kurashiki | 
 1.39.1.0/32      | 1.39.4.255/32    | ind     | mh        | mumbai    | 
 1.40.144.0/32    | 1.40.145.255/32  | aus     | ns        | fairfield | 
 1.40.235.0/32    | 1.40.242.255/32  | aus     | ns        | sydney    | 
 1.44.28.0/32     | 1.44.29.255/32   | aus     | vi        | melbourne | 
 1.44.82.0/32     | 1.44.83.255/32   | aus     | vi        | melbourne | 
 1.44.92.0/32     | 1.44.93.255/32   | aus     | vi        | melbourne | 
 1.44.128.0/32    | 1.44.129.255/32  | aus     | vi        | melbourne | 
 1.44.220.0/32    | 1.44.221.255/32  | aus     | vi        | melbourne | 
 ......
 ......

查询类似于:

 75.149.219.61/32
 68.239.61.29/32
 96.41.50.165/32
 183.62.126.7/32
 ......

【问题讨论】:

IP是用inet格式存储的,还是怎么存储的? 如果 start、end 和 ip 实际上存储为整数,我建议将 start 和 end 分解为单独的行,如下所示:asktom.oracle.com/pls/asktom/…,对结果进行排序并对其进行查找 包括您当前获取的查询计划以及随之而来的查询。 【参考方案1】:

我认为最好和更优雅的解决方案是存储 IP 和范围 作为inet格式。无论如何,IP 范围通常以网络/掩码格式发布, 不像开始/结束。这允许编写基于 JOIN 的

ON (ip.addr << geoloc.range)

当然,ip 表应该由 addr 和 geoloc 索引(范围,位置), 如果您没有 CIDR 格式并且需要从 Start/End 构建它,那可能会很昂贵(但是,以后使用该表会更容易)。

http://www.postgresql.org/docs/9.0/static/functions-net.html

编辑:不幸的是,这些开始/结束值看起来像“优化”的 CIDR 范围。换句话说,例如

1.40.235.0     1.40.242.255

实际上是四个独立的连续范围的合并:

11101011   235.0-235.255
    11101100   236.0-239.255
    11101111   
    11110000   240.0-241.255   
    11110001
11110010   242.0-242.255

因此将行分解为 CIDR 操作所需的四行是不切实际的。

Start/End 在 cidr 数据类型中查找,因此将它们转换为 inet(它们都是 /32 无论如何......)并将查询值保持在 inet 数据类型中,索引 Start,End,应该 给出合理的结果:

 SELECT query.ip, geoloc.country, geoloc.region, geoloc.city
     FROM query JOIN geoloc
     ON (query.ip >= geoloc.start_ip AND query.ip <= geoloc.end_ip);

另一种选择,不是很优雅实际上是一个 hack),将基于例如“爆炸”ip 和 geoloc 表。 addr 和 range 的第一个字节,放入单独的子表中(我不希望您的 IP 范围具有不同的第一个字节)。

 SELECT * FROM geoloc
     WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
     INTO TABLE geoloc_5;

 SELECT * FROM query
     WHERE start_ip >= inet '5.0.0.0' and end_ip <= inet '5.255.255.255'
     INTO TABLE query_5;

 Remember to CREATE INDEX on geoloc_5 start_ip, end_ip

这种方法确实在几年前适用于一个大的 PostgreSQL 批处理,但我希望从那时起,一个更聪明的索引管理器 - 连同专用数据类型 - 将发展到超越匹配这个DIY分区。因此,如果不能使用

也就是说,假设两张表都有一个平坦的分布(只是为了得到一个大概的数字)。

然后,在 2M x 25M 的记录上,您运行 256 个 2M/256 的 SELECT,而不是一个 SELECT 25M/256。所以不是 1 x 2M x 25M = 50 T,而是 256 x 2M/256 x 25M/256 = 192G 比较,这应该比直接 JOIN 快 200 倍左右。

但我再说一遍,我希望 PostgreSQL 在看到正确索引的 CIDR 字段后,将不再真正执行“直接”JOIN,而是使用这个技巧(然后是一些)。

【讨论】:

我的示例查询可能不会产生正确的结果,除非您将其缩小到仅包括最近的范围。说 a.0.0.0/8 -> 美国,而 a.b.0.0/16 -> TX。您只需要 a.b.1.14 的 TX。【参考方案2】:

如果您要查询Loc 列,则应向其add an index。此外,由于这是一个 3 列的表,最好将 StartIPEndIP 组合起来,将其用作键,并使用 Geolocation 作为值,然后从键值中读取所有内容存储如Redis 或Memcached。 NoSQL/无表数据存储专为此类事情而设计,您在其中读取数百万个数据点。

编辑:在阅读了一些 cmets 之后,我想到另一个解决方案是通过 MapReduce 之类的东西来并行化您的搜索。在 Map 步骤中分配线程以查询一系列 IP(例如 Thread1:1-10、Thread2:11-20 等),然后在 Reduce 步骤中分配线程以将碎片化查询减少为一个结果。您显然需要一种单独的编程语言来编写脚本,但并发性将有助于减少您的整体加载时间,尽管缺点是对数据库的多次查询。

【讨论】:

我不同意为了获得良好的响应时间,您必须跳出关系数据库并转向 NoSQL 解决方案。无论系统如何,返回 200 万行都是一个庞大的结果集。 然后他实际上需要将 StartIP-EndIP 分解为一系列值,否则我看不到密钥库如何找到,例如,当实际存储的密钥是​​ 192.168 时,192.168.1.17。 1.0-192.168.1.255。并且爆炸 geoloc 表...看起来很可疑,就像试图将 Internet 存储在密钥库中一样 :-)【参考方案3】:

您必须提供您的查询和查询计划,以便对此进行有意义的输入。例如:

explain select hits.ip, locations.loc
 from hits left outer join locations
   on (hits.ip >= locations.start and hits.ip <= locations.stop);
                                  QUERY PLAN                                   
-------------------------------------------------------------------------------
 Nested Loop Left Join  (cost=0.00..245.06 rows=2400 width=36)
   Join Filter: ((hits.ip >= locations.start) AND (hits.ip <= locations.stop))
   ->  Seq Scan on hits  (cost=0.00..34.00 rows=2400 width=4)
   ->  Materialize  (cost=0.00..1.07 rows=5 width=40)
         ->  Seq Scan on locations  (cost=0.00..1.05 rows=5 width=40)
(5 rows)

我不确定您是否要像其他答案之一所建议的那样将位置数据添加到您的索引中。那只是死数据增加了膨胀,对查找行没有好处。

即使您使用支持仅索引扫描的 pg 版本(9.2,仍处于测试阶段),较小的精简索引可能仍会通过每行额外的元组查找提供更快的结果。

【讨论】:

以上是关于批量地理定位数百万个 IP的主要内容,如果未能解决你的问题,请参考以下文章

存在哪些基于 IP 对人员进行地理定位的服务? [关闭]

vue 中获取客户端所在ip及ip所属的地理位置

PHP自动定位用户地理位置

基于 ip 的地理定位精度的时间依赖性

地理定位

组合地理定位框架