一、开源项目简介
Ip2region – 准确率99.9%的离线IP地址定位库 , 0.0x毫秒级查询 , ip2region.db数据库只有数MB , 提供了java,php,c,python,nodejs,golang,c#等查询绑定和Binary,B树,内存三种查询算法 。
二、开源协议
使用Apache-2.0开源协议
三、界面展示
文章插图
四、功能概述
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算法更快 。
五、技术选型
多查询客户端的支持
已经集成的客户端有:java、C#、php、c、python、nodejs、php扩展(php5和php7)、golang、rust、lua、lua_c, nginx 。
binding
描述
开发状态
binary查询耗时
b-tree查询耗时
memory查询耗时
c
ANSC c binding
已完成
0.0x毫秒
0.0x毫秒
0.00x毫秒
c#
c# binding
已完成
0.x毫秒
0.x毫秒
0.1x毫秒
golang
golang binding
已完成
0.x毫秒
0.x毫秒
0.1x毫秒
java
java binding
已完成
0.x毫秒
0.x毫秒
0.1x毫秒
lua
lua实现的binding
已完成
0.x毫秒
0.x毫秒
0.x毫秒
lua_c
lua的c扩展
已完成
0.0x毫秒
0.0x毫秒
0.00x毫秒
nginx
nginx的c扩展
已完成
0.0x毫秒
0.0x毫秒
0.00x毫秒
nodejs
nodejs
已完成
0.x毫秒
0.x毫秒
0.1x毫秒
php
php实现的binding
已完成
0.x毫秒
0.1x毫秒
0.1x毫秒
php5_ext
php5的c扩展
已完成
0.0x毫秒
0.0x毫秒
0.00x毫秒
php7_ext
php7的c扩展
已完成
0.0毫秒
0.0x毫秒
0.00x毫秒
python
python bindng
已完成
0.x毫秒
0.x毫秒
0.x毫秒
rust
rust binding
已完成
0.x毫秒
0.x毫秒
0.x毫秒
ip2region快速测试
请参考每个binding下的README说明去运行cli测试程序 , 例如C语言的demo运行如下:
cd binding/c/ gcc -g -O2testSearcher.cip2region.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.572163|中国|华南|广东省|深圳市|鹏博士 in0.02295 millseconds 输入IP地址开始测试 , 第一次会稍微有点慢 , 在运行命令后面接入binary,memory来尝试其他算法 , 建议使用b-tree算法 , 速度和并发需求的可以使用memory算法 , 具体集成请参考不同binding下的测试源码 。
ip2region安装
具体请参考每个binding下的README文档和测试demo , 以下是一些可用的快捷安装方式:
maven仓库地址
<dependency><groupId>org.lionsoul</groupId><artifactId>ip2region</artifactId><version>1.7.2</version></dependency> nodejs
npm installnode-ip2region --save nuget安装
Install-PackageIP2Region php composer
# 插件来自:https://github.com/zoujingli/ip2regioncomposer requirezoujingli/ip2region ip2region 并发使用
- 全部binding的各个search接口都不是线程安全的实现 , 不同线程可以通过创建不同的查询对象来使用 , 并发量很大的情况下 , binary和b-tree算法可能会打开文件数过多的错误 , 请修改内核的最大允许打开文件数(fs.file-max=一个更高的值) , 或者使用持久化的memory算法 。
- memorySearch接口 , 在发布对象前进行一次预查询(本质上是把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 , 然后运行如下命令:
- 获取生成的ip2region.db文件覆盖原来的ip2region.db文件即可
- 默认的ip2region.db文件生成命令:
layout
title
date
categories
tags
status
type
published
author
post
Ip2region 数据库文件结构及原理
2016-08-18
tool
ip定位
ip2region
publish
post
true
login
display_name
slayer
dongyado@gmail.com
slayer
ip2region 是一个准确率99.9%的ip地址定位库 。0.0x毫秒级查询 , 数据库文件大小只有1.5M , 提供了java, php, c, python查询客户端和Binary,B树,内存三种查询算法 。
本文将分三个部分:
- 源数据转变成ip2region db 文件的过程
- ip2region 的结构
- 搜索方法
1. 源数据来源与结构
ip2region 的ip数据来自纯真和淘宝的ip数据库 , 每次抓取完成之后会生成 ip.merge.txt , 再通过程序根据这个源文件生成ip2region.db 文件 。
ip.merge.txt 中每一行对应一条完整记录 , 每一条记录由ip段和数据组成 , 格式如下:
0.0.0.0|0.255.255.255|未分配或者内网IP|0|0|0|01.0.0.0|1.0.0.255|澳大利亚|0|0|0|01.0.1.0|1.0.3.255|中国|华东|福建省|福州市|电信 1.0.4.0|1.0.7.255|澳大利亚|0|0|0|01.0.8.0|1.0.15.255|中国|华南|广东省|广州市|电信 1.0.16.0|1.0.31.255|日本|0|0|0|01.0.32.0|1.0.63.255|中国|华南|广东省|广州市|电信 1.0.64.0|1.0.127.255|日本|0|0|0|01.0.128.0|1.0.255.255|泰国|0|0|0|01.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毫秒级别 。
根据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 。
具体功能:
- 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 的后面四个字节 , 分别得到数据长度和数据地址
- 从数据地址读取拿到的所得长度的字节 , 即是搜索结果
<?phpfseek($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); // 数据位置returnarray( '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 的后面四个字节 , 分别得到数据长度和数据地址
- 从数据地址读取拿到的所得长度的字节 , 即是搜索结果
六、源码地址
【准确率99.9%的IP地址定位库 高精度ip地址定位查询免费】 访问一飞开源:https://code.exmay.com/
推荐阅读
- 股票603开头是什么股
- 京的古代意思,汉字「京」的本义是什么?
- 网络用语up是什么意思 网络用语up的意思
- 这3种隔夜菜最好不要吃 这八种隔夜菜最好不要吃
- 比较适合青少年阅读的书籍
- 住酒店是新开的好还是老酒店好 什么样的酒店才是好酒店
- 温江哪里有好吃的啊
- 什么样的手表好啊
- 动量守恒定律条件,系统动量守恒的条件是什么
- 江苏教育版一到六年级的古今贤文