ip2region 完整指南:3 步搞定离线 IP 定位到城市级
【免费下载链接】ip2regionIp2region is an offline IP-to-Region localization library and IP data management framework with both IPv4 and IPv6 supports, 10-microsecond level query efficiency, xdb search client for many programming languages项目地址: https://gitcode.com/GitHub_Trending/ip/ip2region
你有没有过这种经历:系统里存了一堆用户 IP,想看看他们来自哪里,结果每次都要调第三方在线接口——网络一抖就超时,调用量一大还要计费,更麻烦的是服务一停,日志里的位置信息就再也没法补查。ip2region 是一款免费的离线 IP 定位库,把全国乃至全球的城市级 IP 数据压缩进一个本地文件,查询不需要联网,单次响应在微秒级。这篇文章带你从零走到落地。
它到底是什么
把 ip2region 理解成一本"随身携带的电话簿":在线查询服务像是打电话给 114 查号,每次都要等对方接;而 ip2region 是把整本电话簿复印在你桌上,翻到哪页自己查,不用问任何人。
它干两件事:一是提供ip2region_v4.xdb和ip2region_v6.xdb两个数据文件,直接查 IP 归属的"国家|省份|城市|运营商";二是提供一整套数据管理工具,让你能改数据、重新打包,把 GPS 坐标、邮编这类自定义字段塞进查询结果里。查询客户端覆盖了 Golang、Java、Python、C、PHP、Rust、C#、Lua、Erlang、Nginx 扩展等十几种语言,在 binding/ 目录下各有一版实现和文档。
第一次查询:3 步手动跑通
先亲手查一个 IP,感受一下它快不快。
第一步,拿到代码和数据:
git clone https://gitcode.com/GitHub_Trending/ip/ip2region第二步,用仓库里现成的 Python 查询测试程序启动交互查询(Python 版在 binding/python/):
cd ip2region/binding/python python search_test.py --db ../../data/ip2region_v4.xdb第三步,在提示符后面输入一个 IP,比如113.92.157.29,回车。你会看到类似这样的输出:
{region: 中国|广东省|深圳市|中国联通, ioCount: 3, took: 87 μs}took这一列就是单次查询耗时,这里的单位是微秒。输入quit退出。如果你更常用 Golang,binding/golang/main.go 是一个功能一样的交互程序,go run main.go search即可启动,输入框同时支持 IPv4 和 IPv6。
数据文件放在 data/ 目录:v4 文件约 11MB,v6 约 37MB,源数据ipv4_source.txt里有 51 万多个 IP 段,ipv6_source.txt约 67 万个。
它凭什么这么快
你可能会问:五百万多条 IP 段,就算全装进内存,线性扫一遍也不止微秒级。答案在它的查询结构里。
xdb 文件的设计可以类比你查字典:你不可能从第一页翻起找"省"这个字,你会先翻到目录,按首字母定位到大概页码,再在几页里找到它。xdb 内部同样存了一张"目录"——向量索引,它按 IP 数值切分成定长块,查询时先用这个索引算出目标 IP 落在哪个块,再在那个块内做一次二分定位,所以典型查询只需要两三次随机读。
正因为有这层索引,ip2region 给了你三档缓存策略,你可以按需选择:
file:完全不缓存,每次查询都读文件,仍然在百微秒级,适合内存紧张或查询量小的场景;vectorIndex:只把那张"目录"(512KiB 内存)装进内存,省掉一次磁盘定位,平均查询压到 100 微秒内,是默认推荐档;content:把整个 xdb 文件读进内存,查询稳定在 10 微秒级,代价是内存占用等于文件大小(v4 约 11MB,很划算)。
切换策略只是一个参数的事,比如 Python 测试程序的--cache-policy就接受这三个值,Golang 里对应service.NoCache/service.VIndexCache/service.BufferCache。
一个端到端案例:给访问日志补上城市信息
假设你的 Nginx 访问日志长这样:
2026-09-07 10:15:32 218.17.37.165 GET /index.html 200你想在日志报表里看到每个访客来自哪个城市。链路其实很短:
- 日志进来后提取出点分十进制的 IP 字段;
- 把它交给查询服务;Golang 的并发安全查询服务几行就能建起来(摘自 binding/golang/ 文档):
v4Config, _ := service.NewV4Config(service.VIndexCache, "ip2region_v4.xdb", 20) v6Config, _ := service.NewV6Config(service.VIndexCache, "ip2region_v6.xdb", 20) ip2region, _ := service.NewIp2Region(v4Config, v6Config) region, err := ip2region.Search("218.17.37.165")Search返回中国|北京市|北京市|中国移动这样的字符串,你的代码按|切分,把省份和城市拼回日志行即可。
这个案例里有几个细节值得注意。返回值是"国家|省份|城市|运营商|ISO 国家码"的固定格式,国内数据用中文、国外数据用英文,解析时按位置取而不是按内容匹配会更稳。Search对 IPv4 和 IPv6 都能处理,服务内部自动识别版本,你不需要写两遍逻辑。查不到该 IP 时返回空字符串,记得在报表里留个"未知"分支。
常见坑与性能取舍
并发问题。单个基于文件的查询对象不是并发安全的,多线程共用一个会出脏数据。两种正规解法:用前面那种带查询池的Ip2Region查询服务,按你的并发数设置池大小(比如 20);或者用content策略的内存查询器,它本身就并发安全。别图省事自己加锁包一个,池化方案吞吐更高。
别在热路径上验文件。各语言 API 都提供Verify校验 xdb 文件完整性,但它有开销,官方注释明确建议只在部署时校验一次,不要每次查询前都调。
缓存策略别一步到位。内存有限就先跑vectorIndex,512KiB 的代价换来比纯文件查询更稳定的延迟;等 QPS 真的高到磁盘 IO 成为瓶颈,再升content。升级路径是平滑的,只改一个参数。
数据会过期。仓库里的源数据不定期更新,对准确性要求高的业务建议接入 data/README_zh.md 里说明的更新渠道,或者用 maker/ 下的生成工具自己维护数据:源数据是纯文本(起始IP|结束IP|国家|省份|城市|ISP|国家码),改完重新打包成 xdb 即可,Golang、Java、Python、C#、Rust 都有打包器,它还会自动合并相邻的相同 IP 段、去重压缩,所以你的自定义数据不用手动整理格式。
下一步去哪
- 想看懂 xdb 文件长什么样:查一下 v4 文件头,或直接读各语言查询客户端的源码,结构都在 binding/ 里;
- 想生成或修改数据:看 maker/golang/ 的 README,里面有编辑器和打包流程的完整说明;
- 想给 Nginx 直接加
geo指令级的定位能力:binding/nginx/ 提供了模块实现和测试; - 想给自己的项目挑客户端:翻 binding/ 下对应语言的 README,每份都带完整 API 说明和测试程序,照着抄就行。
装好数据、选对缓存策略,你的项目就获得了一个不依赖网络、微秒级响应的定位能力。剩下的只是把日志、风控、报表这些入口接上去的事。
【免费下载链接】ip2regionIp2region is an offline IP-to-Region localization library and IP data management framework with both IPv4 and IPv6 supports, 10-microsecond level query efficiency, xdb search client for many programming languages项目地址: https://gitcode.com/GitHub_Trending/ip/ip2region
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考