最近项目中有一个需求统计访客数据,为了保证效率。前端尽量轻量化,仅将访客原始请求信息不作任何处理直接写入消息队列。后端计划任务服务器监听消息队列,解析 user agent, ip 地址,处理结果写入 ES 供报表使用,其中 IP地址处理需要跟据 IP地址获取国家,本文主要研究解决PHP语言跟据IP获取国家的单一需求。
首先解决数据源
1 纯真IP库
缺点:主要为国内IP库,如果仅需要国家信息,则冗余信息多,解析效率低
2 高用第三方接口(如:ip138)解析。
缺点:延时高,稳定性差
排除以上数据源方案。
当前比较可靠的数据源:GEOIP 库,商业化运作,更新频率很高,有提供免费的IP库供下载
官网:
https://geoip.com/
https://www.maxmind.com/
这两个网站是同一家公司。
PHP官方 支持 geoip 扩展,相关方法见官方文档:
https://www.php.net/manual/zh/book.geoip.php
更多的时候,项目不方便安装扩展,可以使用纯 php 的 composer 安装包
官方开发的 PHP composer 安装包:
在 composer 包管理网站 packagist.org 搜 geoip
排名靠前的. geoip2/geoip2,maxmind-db/reader, geoip/geoip 是官方提供的安装包,其中 "geoip/geoip". Abandoned,转到了 geo2/geoip2,
下载库文件:
composer requre geoip2/geoip2
编写代码前,首先需要下载最新版的 IP数据库, 官网下载地址:
https://www.maxmind.com/en/geoip2-country-database
默认是商业版,下载需要一定的费用。支持一次性/按月/按年三种付费方式
同时,官方提供了免费版本,首先在官网注册一个账号,成功后使用账号登录,在用户中心 点 "download files" 下载相关文件
程序示例:
<?php
// 指定 IP数据库,在调用代码中引用该文件:
$reader = new \GeoIp2\Database\Reader('/path/tp/your/GeoLite2-Country.mmdb');
$record = $reader->country('128.101.101.101');
print($record->country->isoCode . "\n"); // 'US'
print($record->country->name . "\n"); // 'United States'
print($record->country->names['zh-CN'] . "\n"); // '美国'
?>
简单测试下效率:
<?php
$reader = new \GeoIp2\Database\Reader( '/path/tp/your/GeoLite2-Country.mmdb');
$success = 0;
$t0 = microtime(1);
for ($i = 0; $i<2000; $i++) {
try {
$ip = rand(1,255) . '.' . rand(1,255) . '.' .rand(1,255) . '.' . rand(1,255);
$record = $reader->country($ip);
// echo $ip . ':' . $record->country->isoCode . "<br>\r\n";
$success++;
} catch (\Throwable $t) {
}
}
$t1 = microtime(1);
echo '用时:' . number_format($t1 - $t0, 3, '.', '') . '秒 <br/>';
echo '有效IP: ' . $success . ' 个';
?>
18款MAC笔记本电脑(4核16G 固态硬盘) docker 环境运行结果:
用时:0.834秒
有效IP: 1723 个
随机生成的IP有一部分无效,相当于1秒执行了 2000+ 随机IP查询,已经相当高了,这种高IO 的查询操作固态硬盘是主要因素
如果有更高的效率需求,可以将 IP数据库转入 REDIS
跟据CSV版本的IP数据,当前版本的 IPv4(IPv6 应该需求不多) 数据库仅有 37万多条记录(3711110),
GeoLite2-Country-Blocks-IPv4.csv 内容示例:
network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider
1.0.0.0/24,2077456,2077456,,0,0
1.0.1.0/24,1814991,1814991,,0,0
1.0.2.0/23,1814991,1814991,,0,0
1.0.4.0/22,2077456,2077456,,0,0
1.0.8.0/21,1814991,1814991,,0,0
...
223.255.244.0/22,1269750,1269750,,0,0
223.255.248.0/22,1819730,1819730,,0,0
223.255.252.0/23,1814991,1814991,,0,0
223.255.254.0/24,1880251,1880251,,0,0
223.255.255.0/24,2077456,2077456,,0,0
其中第一列 network 为网段/子网掩码,geoname_id 等列关联了另一个 csv 中的国家信息元数据。
可以以网段为键名(可IP2Long 转INT),子网掩码+对应的国家二字码为键值 存入 redis, 以内存换取更快的IO操作,相信查询效率会有更大提升。
国为当前项目是在计划任务中执行,有消息队列缓冲,可多台计划任务服务器并行处理。官方 composer 库效率也足够高,暂时不折腾了,以后有机会了再尝试 REDIS 方案
本文原始网址:PHP 跟据用户IP获取所在国家高效解决方案(GEOIP),转载请保留出处