查看原文
其他

仅数MB,准确率99.9%的离线IP地址定位库,0.0x毫秒级查询

点击关注 👉 顶级架构师 2023-09-18
推荐关注
顶级架构师后台回复 1024 有特别礼包


来源:网络

上一篇:微服务架构及设计模式


大家好,我是顶级架构师。


今天,推荐一个离线IP地址定位库系统项目。我第一次使用就有点上头,爱不释手,必须要推荐给大家。

上次是谁要的离线IP地址定位库系统项目啊,我帮你找到了。

这是我目前见过最好的离线IP地址定位库系统项目。功能完整,代码结构清晰。值得推荐。

📚 项目介绍

最近我在逛网站的时候发现一个不错的开源项目,这个项目目前收获了 6K Star,我觉得不错,值得拿出来和大家分享下。

本项目是准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,数据库文件大小只有1.5M,提供了java,php,c,python,nodejs,golang,c#等查询绑定和Binary,B树,内存三种查询算法。扩展:接私活儿

一、功能概述

Ip2region特性

99.9%准确率

数据聚合了一些知名ip到地名查询提供商的数据,这些是他们官方的的准确率,经测试着实比经典的纯真IP定位准确一些。
ip2region的数据聚合自以下服务商的开放API或者数据(升级程序每秒请求次数2到4次):
01, >80%, 淘宝IP地址库
02, ≈10%, GeoIP
03, ≈2%, 纯真IP库
备注:如果上述开放API或者数据都不给开放数据时ip2region将停止数据的更新服务。

标准化的数据格式

每条ip数据段都固定了格式:

_城市Id|国家|区域|省份|城市|ISP_

只有中国的数据精确到了城市,其他国家有部分数据只能定位到国家,后前的选项全部是0,已经包含了全部你能查到的大大小小的国家(请忽略前面的城市Id,个人项目需求)。

体积小

包含了全部的IP,生成的数据库文件ip2region.db只有几MB,最小的版本只有1.5MB,随着数据的详细度增加数据库的大小也慢慢增大,目前还没超过8MB。

查询速度快

全部的查询客户端单次查询都在0.x毫秒级别,内置了三种查询算法

  • memory算法:整个数据库全部载入内存,单次查询都在0.1x毫秒内,C语言的客户端单次查询在0.00x毫秒级别。

  • binary算法:基于二分查找,基于ip2region.db文件,不需要载入内存,单次查询在0.x毫秒级别。

  • b-tree算法:基于btree算法,基于ip2region.db文件,不需要载入内存,单词查询在0.x毫秒级别,比binary算法更快。

任何客户端b-tree都比binary算法快,当然memory算法固然是最快的!

二、技术选型

多查询客户端的支持

已经集成的客户端有:java、C#、php、c、python、nodejs、php扩展(php5和php7)、golang、rust、lua、lua_c, nginx。

ip2region快速测试

请参考每个binding下的README说明去运行cli测试程序,例如C语言的demo运行如下:

cd binding/c/
gcc -g -O2 testSearcher.c ip2region.c
./a.out ../../data/ip2region.db

会看到如下cli界面:

initializing B-tree ...
+----------------------------------+
| ip2region test script |
| Author: chenxin619315@gmail.com |
| Type 'quit' to exit program |
+----------------------------------+
p2region>> 101.105.35.57
2163|中国|华南|广东省|深圳市|鹏博士 in 0.02295 millseconds

输入IP地址开始测试,第一次会稍微有点慢,在运行命令后面接入binary,memory来尝试其他算法,建议使用b-tree算法,速度和并发需求的可以使用memory算法,具体集成请参考不同binding下的测试源码。

项目源码,怎么领取?

源码获取

牛逼啊!接私活必备的 N 个开源项目!

扫码下方二维码,后台回复【定位】即可获取所有系统

ip2region安装

具体请参考每个binding下的README文档和测试demo,以下是一些可用的快捷安装方式:

maven仓库地址

<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>

nodejs

npm install node-ip2region --save

nuget安装

Install-Package IP2Region

php composer

# 插件来自:https://github.com/zoujingli/ip2region
composer require zoujingli/ip2region

ip2region 并发使用

  • 全部binding的各个search接口都不是线程安全的实现,不同线程可以通过创建不同的查询对象来使用,并发量很大的情况下,binary和b-tree算法可能会打开文件数过多的错误,请修改内核的最大允许打开文件数(fs.file-max=一个更高的值),或者使用持久化的memory算法。

  • memorySearch接口,在发布对象前进行一次预查询(本质上是把ip2region.db文件加载到内存),可以安全用于多线程环境。

ip2region.db的生成

从1.8版本开始,ip2region开源了ip2region.db生成程序的java实现,提供了ant编译支持,编译后会得到以下提到的dbMaker-{version}.jar,对于需要研究生成程序的或者更改自定义生成配置的请参考${ip2region_root}/maker/java内的java源码。

从ip2region 1.2.2版本开始里面提交了一个dbMaker-{version}.jar的可以执行jar文件,用它来完成这个工作:

  • 确保你安装好了java环境(不玩Java的童鞋就自己谷歌找找拉,临时用一用,几分钟的事情)

  • cd到${ip2region_root}/maker/java,然后运行如下命令:

java -jar dbMaker-{version}.jar -src 文本数据文件 -region 地域csv文件 [-dst 生成的ip2region.db文件的目录]

# 文本数据文件:db文件的原始文本数据文件路径,自带的ip2region.db文件就是/data/ip.merge.txt生成而来的,你可以换成自己的或者更改/data/ip.merge.txt重新生成
# 地域csv文件:该文件目的是方便配置ip2region进行数据关系的存储,得到的数据包含一个city_id,这个直接使用/data/origin/global_region.csv文件即可
# ip2region.db文件的目录:是可选参数,没有指定的话会在当前目录生成一份./data/ip2region.db文件
  • 获取生成的ip2region.db文件覆盖原来的ip2region.db文件即可

  • 默认的ip2region.db文件生成命令:

cd ${ip2region_root}/java/
java -jar dbMaker-1.2.2.jar -src ./data/ip.merge.txt -region ./data/global_region.csv

# 会看到一大片的输出

架构原理

layout

title

date

categories

tags

status

type

published

author

post

Ip2region 数据库文件结构及原理

2016-08-18

tool

ip定位

ip2region

publish

post

true

login

email

display_name

slayer

dongyado@gmail.com

slayer

ip2region 是一个准确率99.9%的ip地址定位库。0.0x毫秒级查询,数据库文件大小只有1.5M,提供了java, php, c, python查询客户端和Binary,B树,内存三种查询算法。

本文将分三个部分:

  • 源数据转变成ip2region db 文件的过程

  • ip2region 的结构

  • 搜索方法

(一). 源数据如何存储到ip2region.db

1. 源数据来源与结构

ip2region 的ip数据来自纯真和淘宝的ip数据库,每次抓取完成之后会生成 ip.merge.txt, 再通过程序根据这个源文件生成ip2region.db 文件。

牛逼啊!接私活必备的 N 个开源项目!赶快收藏吧

ip.merge.txt 中每一行对应一条完整记录,每一条记录由ip段和数据组成,格式如下:

0.0.0.0|0.255.255.255|未分配或者内网IP|0|0|0|0
1.0.0.0|1.0.0.255|澳大利亚|0|0|0|0
1.0.1.0|1.0.3.255|中国|华东|福建省|福州市|电信
1.0.4.0|1.0.7.255|澳大利亚|0|0|0|0
1.0.8.0|1.0.15.255|中国|华南|广东省|广州市|电信
1.0.16.0|1.0.31.255|日本|0|0|0|0
1.0.32.0|1.0.63.255|中国|华南|广东省|广州市|电信
1.0.64.0|1.0.127.255|日本|0|0|0|0
1.0.128.0|1.0.255.255|泰国|0|0|0|0
1.1.0.0|1.1.0.255|中国|华东|福建省|福州市|电信

从左到右分别表示:起始ip,结束ip,国家,区域,省份,市,运营商。无数据区域默认为0。

最新的ip.merge.txt 有122474条记录,并且根据开始ip地址升序排列。

2. 如何生成ip2region.db

给定一个ip,如何快速从ip.merge.txt中找到该ip所属记录?最简单的办法就是顺序遍历,当该ip在某条记录起始和结束ip之间时,即命中。

这是低效的做法,如何提高查询性能?用过mysql和其他数据库的的都知道,使用索引。所以ip2region.db使用了内建索引,直接将性能提升到0.0x毫秒级别。另外,搜索公众号编程技术圈后台回复“Java”,获取一份惊喜礼包。

根据ip.merge.txt,为所有数据生成一份索引,并和数据地址组成一个索引项(index block), 然后按起始ip升序排列组成索引,并存储到数据文件的末尾,最终生成的ip2region.db文件大小只有3.5M。

此时的数据库文件中的每一条索引都指向一条对应的数据,也就是说如

|中国|华南|广东省|广州市|电信

这样的数据在文件中被重复存储了很多次,再经过去重优化之后,ip2region.db只有1.5M了,此时把数据库文件全部读取到内存再查找都是非常可行的。

(二). ip2region.db 结构

生成的ip2region.db文件包含以下四个部分:

1, SUPER BLOCK
2, HEADER INDEX
3, DATA
4, INDEX

生成 ip2region.db 的时候,首先会在首部预留 8 bytes 的SUPER BLOCK 和 8k 的 HEADER INDEX。

再根据ip.merge.txt,依据每一条记录的起始ip, 结束ip和数据,生成一个index block, 前四个字节存储起始ip, 中间四个字节存储结束ip, 后四个字节存储已经计算出的数据地址,并暂存到INDEX区。

当 INDEX 索引区和 DATA 数据区确定下来之后,再把 INDEX 的起始位置存储到 SUPER BLOCK 的前四个字节,结束位置存储到 SUPER BLOCK 的后四个字节。

再把 INDEX 分成大小为 4K 的索引分区,把每个分区起始位置的索引的起始ip和该索引的位置存入一个 header index block, 组成 HEADER INDEX 区域, 最后写入ip2region.db。另外,搜索公众号Java架构师技术后台回复“Spring”,获取一份惊喜礼包。

具体功能:

  • INDEX

  • 索引区域,索引元素为 index block (12 字节), 分成三个部分,起始ip, 结束ip, 数据信息, 每一条 index block 对应 ip.merge.txt 中的一条记录。

  • 数据信息:前三个字节保存数据地址(DATA中),后一个字节保存数据长度。

  • 每个index block 表示一个ip段的索引。当指定ip 在某个 index block 的起始ip和结束ip中间,即表示命中索引。

  • 再通过 index block 中的数据地址和数据长度,就能从ip2region.db读取对应的地址。

  • SUPER BLOCK

  • 用来保存 INDEX 的起始地址和结束地址,first index ptr 指向INDEX起始位置的index block, last index ptr 指向最后一个index block的地址。这样查询的时候直接读取superblock 8个字节,就能快速获取 INDEX 索引区域的地址。

  • HEADER INDEX

  • HEADER INDEX 区是对 INDEX 区的二级索引, INDEX总长度除以 4K 就是 HEADER INDEX 的实际索引数。

  • 该区域长度为8k, 由 8 bytes 的 header index block 组成。

  • header index block 前四个字节存储每个4K分区起始位置的index block 的起始ip值,后四个字节指向该index block的地址。

  • 把HEADER INDEX 区定义为8k,可以通过一次磁盘读取读取整个HEADER INDEX 区,然后在内存中进行查询,查询的结果可以确定该ip在INDEX区的某个4k分区内,然后再根据地址一次读取4k index 到内存,再在内存中查询,从而减少磁盘读取的次数。

  • DATA

  • 保存的数据,数据格式如下:

  •  2163|中国|华南|广东省|深圳市|鹏博士

  • 分别表示 城市ip,国家,区域,省份,城市,运营商

(三). 搜索方法

binary搜索

二分法就不多介绍了,步骤:

  • 把ip值通过ip2long方法转为长整型

  • 通过 SUPER BLOCK 拿到INDEX的起始位置和结束位置

  • 相减+1得出index block 总数

  • 采用二分法直接求解,比较 index block 和当前ip的大小,即可找到该ip属于的 index block

  • 拿到该 index block 的后面四个字节, 分别得到数据长度和数据地址

  • 从数据地址读取拿到的所得长度的字节,即是搜索结果

以php客户端作为例子注释:

<?php
fseek($this->dbFileHandler, 0);
$superBlock = fread($this->dbFileHandler, 8); // 从文件0位置往后读取8字节,即 super block
$this->firstIndexPtr = self::getLong($superBlock, 0); // 获取INDEX起始位置
$this->lastIndexPtr = self::getLong($superBlock, 4); // 获取INDEX结束位置
$this->totalBlocks = ($this->lastIndexPtr-$this->firstIndexPtr)/INDEX_BLOCK_LENGTH + 1; // 计算总索引数,即 index block 总数

// 二分法搜索
$l = 0; // 低位
$h = $this->totalBlocks; // 高位
$dataPtr = 0;
while ( $l <= $h ) {
$m = (($l + $h) >> 1); // 中位
$p = $m * INDEX_BLOCK_LENGTH;
fseek($this->dbFileHandler, $this->firstIndexPtr + $p); // 移动读取位置
$buffer = fread($this->dbFileHandler, INDEX_BLOCK_LENGTH); // 读取 INDEX_BLOCK_LENGTH 个字节 (12 字节), 即读取一个index block
$sip = self::getLong($buffer, 0); // 获取开始 ip

// 进行比较
if ( $ip < $sip ) {
$h = $m - 1; // 比中位index block 开始ip小
} else {
$eip = self::getLong($buffer, 4);
if ( $ip > $eip ) {
$l = $m + 1; // 比中位index block 的结束ip小
} else { // 命中数据
$dataPtr = self::getLong($buffer, 8); // getLong 函数将字节的顺序反过来了
break;
}
}
}

// 下面这段代码看起来似乎是,第一个字节存储的长度,后三个字节存储的数据位置
// 其实是上文的 getLong 函数在获取数据的时候对字节顺序做了一下反转,具体参考 getLong 函数的代码
// 读取数据

$dataLen = (($dataPtr >> 24) & 0xFF); // 数据长度
$dataPtr = ($dataPtr & 0x00FFFFFF); // 数据位置

return array(
'city_id' => self::getLong($this->dbBinStr, $dataPtr), // 获取城市id
'region' => substr($this->dbBinStr, $dataPtr + 4, $dataLen - 4) // 获取其他数据
);
?>

源码请查阅 ip2region php client 的 binarySearch 方法。

b-tree 搜索

b-tree 搜索用到了 HEADER INDEX,第一步先在 HEADER INDEX 中搜索,再定位到 INDEX 中的某个 4k index分区搜索。

步骤:

  • 把ip值通过ip2long 转为长整型

  • 使用二分法在 HEADER INDEX 中搜索,比较得到对应的 header index block

  • header index block 指向 INDEX 中的一个 4K 分区,所以直接把搜索范围降低到 4K

  • 采用二分法在获取到的 4K 分区搜索,得到对应的 index block

  • 拿到该 index block 的后面四个字节, 分别得到数据长度和数据地址

  • 从数据地址读取拿到的所得长度的字节,即是搜索结果

具体源码请查阅 ip2region php client 中的 btreeSearch 方法。

最后,想学习这个项目的可以查看项目地址:

项目源码,怎么领取?

源码获取

牛逼啊!接私活必备的 N 个开源项目!

扫码下方二维码,后台回复【定位】即可获取所有系统

欢迎有需要的同学试试,如果本文对您有帮助,也请帮忙点个 赞 + 在看 啦!❤️

在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!


欢迎大家进行观点的探讨和碰撞,各抒己见。如果你有疑问,也可以找我沟通和交流。扩展:接私活儿


最后给读者整理了一份BAT大厂面试真题,需要的可扫码回复“面试题”即可获取。


公众号后台回复 架构 或者 架构整洁 有惊喜礼包!顶级架构师交流群

 「顶级架构师」建立了读者架构师交流群,大家可以添加小编微信进行加群。欢迎有想法、乐于分享的朋友们一起交流学习。

扫描添加好友邀你进架构师群,加我时注明姓名+公司+职位】


版权申明:内容来源网络,版权归原作者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

猜你还想看

推荐一套开源通用后台管理系统(附源码)

看看人家那 IM 即时通讯系统,那叫一个优雅(附源码)

面试官:生成订单30分钟未支付,则自动取消,该怎么实现?

阿里技术专家:一文教你高效画出技术架构图

16个 Redis 常见使用场景,面试有内容聊啦

面试官问:MySQL的自增 ID 用完了,怎么办?

知名国产论坛,凉了!!!!

架构设计的真谛:系统与子系统、模块与组件、框架与架构

Istio 可以代替 Spring Cloud 吗?

图文详解CDC技术

Docker 入门终极指南,详细版!别再说不会用 Docker 了!

牛逼!这个前后端分离的后台管理系统开源了!

SpringBoot + QueryDSL 大大简化复杂查询操作

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存