最近公司做了一些营销活动,投入资金进行了流量推广,pv、UV都做了统计。老板说,我要看下用户的区域分布的数据。
以前的文章我讲过,pv、UV如何统计?我们是基于ip进行统计的。用的ip能获取到,那通过ip查询归属地就ok了。
思维扩展下,ip 查询归属地的的场景还蛮多的,我列举一些:
这些具体的使用场景说明了IP归属地查询在网络安全、营销推广、个性化服务等方面的重要作用,能够帮助用户更好地理解用户行为和优化业务流程。
谷歌搜索了下,第三方提供的ip查询归属地服务,挺多的,但是收费、收费、收费!!!免费也有些,但是怕不稳定。
无意间找到了ip2region这个项目,一直持续维护更新,试用后,效果杠杆的。那我们怎么用的,继续往下看
ip2region - 是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的 xdb 数据生成和查询客户端实现。
xdb 支持亿级别的 IP 数据段行数,默认的 region 信息都固定了格式:国家|区域|省份|城市|ISP,缺省的地域信息默认是0。 region 信息支持完全自定义,例如:你可以在 region 中追加特定业务需求的数据,例如:GPS信息/国际统一地域信息编码/邮编等。也就是你完全可以使用 ip2region 来管理你自己的 IP 定位数据。
xdb 格式生成程序会自动去重和压缩部分数据,默认的全部 IP 数据,生成的 ip2region.xdb 数据库是 11MiB,随着数据的详细度增加数据库的大小也慢慢增大。
即使是完全基于 xdb 文件的查询,单次查询响应时间在十微秒级别,可通过如下两种方式开启内存加速查询:
Ip2region大部分主流语言都支持,支持的语言如下:
在这里,我以golang语言作为演示,其他语言,可以看下官方文档
例子:我需要查询ip为:218.63.140.248 的归属地
访问ip2region 项目,ip的库文件在data目录下,点击下载即可
go get github.com/lionsoul2014/ip2region/binding/golang
package main
import (
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"time"
)
func main() {
//dbPath写入你下载的ip2region.xdb文件的路径,我这里放在了当前目录下
var dbPath = "ip2region.xdb"
searcher, err := xdb.NewWithFileOnly(dbPath)
if err != nil {
fmt.Printf("failed to create searcher: %s\n", err.Error())
return
}
defer searcher.Close()
// 查询218.63.140.248对应的地址
var ip = "218.63.140.248"
var tStart = time.Now()
region, err := searcher.SearchByStr(ip)
if err != nil {
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
return
}
fmt.Printf("{region: %s, took: %s}\n", region, time.Since(tStart))
// 备注:并发使用,每个 goroutine 需要创建一个独立的 searcher 对象。
}
查询结果
此ip的归属地为: 中国云南省昆明市电信
可以预先加载整个 ip2region.xdb 到内存,完全基于内存查询,类似于之前的 memory search 查询。
package main
import (
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"time"
)
func main() {
//dbPath写入你下载的ip2region.xdb文件的路径,我这里放在了当前目录下
var dbPath = "ip2region.xdb"
// 1、从 dbPath 加载整个 xdb 到内存
cBuff, err := xdb.LoadContentFromFile(dbPath)
if err != nil {
fmt.Printf("failed to load content from `%s`: %s\n", dbPath, err)
return
}
// 2、用全局的 cBuff 创建完全基于内存的查询对象。
searcher, err := xdb.NewWithBuffer(cBuff)
if err != nil {
fmt.Printf("failed to create searcher with vector index: %s\n", err)
return
}
defer searcher.Close()
// 查询218.63.140.248对应的地址
var ip = "218.63.140.248"
var tStart = time.Now()
region, err := searcher.SearchByStr(ip)
if err != nil {
fmt.Printf("failed to SearchIP(%s): %s\n", ip, err)
return
}
fmt.Printf("{region: %s, took: %s}\n", region, time.Since(tStart))
// 备注:并发使用,每个 goroutine 需要创建一个独立的 searcher 对象。
}
查询结果:
生成环境使用建议使用方式为:基于缓存的查询
以上的演示,只是个demo,如果要放在线上如何使用呢?
sdk接入的方式,用到的业务线都需要对接一次,ip2region.xdb如果有更新,所有用到的项目都要自己去更新升级db文件,维护成本太高。如果你的项目比较单一,sdk接入也是不错的
我们的方案:因为我业务线相对太多,如果各个业务线自己接,维护的成本太高。我们决定构建IP查询归属地公共服务,往外提供查询的能力。后续服务的升级、维护等,统一在公共服务里面来做。