iplocx

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2025 License: Apache-2.0 Imports: 11 Imported by: 0

README

iplocx

GitHub release Go Version Go Report Card codecov CI Status

GitHub stars GitHub forks GitHub watchers GitHub contributors

GoDoc License GitHub issues GitHub pull requests Last Commit

Language Platform Architecture Contributions welcome Maintenance


高性能 IP 地理位置查询库,采用并行查询和智能数据合并技术,支持 IPv4/IPv6

性能指标快速开始核心技术API 文档性能测试贡献


目录


性能指标

基于 AMD Ryzen 9 7945HX (16核心32线程,最高 5.0GHz) 的基准测试结果:

查询模式 延迟 吞吐量 (QPS)
缓存命中 11.50 ns 87,000,000
双数据源合并 9.43 μs 106,000
并行查询 (32线程) 2.31 μs 433,000
QQwry 单独 1.99 μs 503,000
GeoLite2 单独 2.61 μs 383,000

性能说明:以上数据基于消费级高性能平台测试。服务器平台(EPYC/Xeon)在高并发场景下(64+核心)性能可能更优,单核性能可能略低。实际性能取决于具体硬件配置、系统负载和数据库文件大小。

测试覆盖率:80.8%

核心技术

并行查询架构

同时查询 QQwry 和 GeoLite2 两个数据源,通过 Goroutine 并发执行,减少查询延迟。

智能数据合并

基于评分系统自动选择最优数据源:

  • 国内 IP:优先使用 QQwry(包含运营商、区县级信息)
  • 国际 IP:优先使用 GeoLite2(包含经纬度、时区信息)
  • 自动补充缺失字段,提供完整的地理位置信息
LRU 缓存机制

可选的 LRU 缓存层,缓存命中后性能提升 820 倍。

线程安全设计

无锁并发架构,支持高并发查询场景。

快速开始

安装
go get github.com/nuomiaa/iplocx
基本用法
package main

import (
    "fmt"
    "log"
    "github.com/nuomiaa/iplocx"
)

func main() {
    locator, err := iplocx.NewLocator(iplocx.Config{
        QQwryDBPath:   "./data/qqwry.dat",
        GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
        EnableCache:   true,
        CacheSize:     10000,
    })
    if err != nil {
        log.Fatal(err)
    }
    defer locator.Close()

    location, err := locator.Query("8.8.8.8")
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%s | %s, %s, %s | %s | %.4f, %.4f | %s\n",
        location.IP,
        location.Country,
        location.Province,
        location.City,
        location.ISP,
        location.Latitude,
        location.Longitude,
        location.TimeZone,
    )
}

API 文档

配置选项
type Config struct {
    QQwryDBPath   string  // 纯真IP库路径(可选)
    GeoLiteDBPath string  // GeoLite2路径(可选)
    EnableCache   bool    // 启用LRU缓存
    CacheSize     int     // 缓存容量(默认1000)
    Debug         bool    // 调试模式
}

至少需要配置一个数据源。

核心方法
方法 说明
NewLocator(cfg Config) (*Locator, error) 创建查询器实例
Query(ip string) (*Location, error) 查询IP地址
GetCacheStats() *CacheStats 获取缓存统计
GetQueryStats() QueryStatsSnapshot 获取查询统计
GetProviderStatus() map[string]bool 检查数据源状态
GetProviderInfo() map[string]ProviderInfo 获取数据源详情
ClearCache() 清空缓存
ResetStats() 重置统计计数器
Close() error 释放资源
返回结构
type Location struct {
    IP        string  // IP地址
    Country   string  // 国家
    Province  string  // 省/州
    City      string  // 城市
    District  string  // 区/县
    ISP       string  // 运营商
    Latitude  float64 // 纬度
    Longitude float64 // 经度
    TimeZone  string  // 时区
    Source    string  // 数据来源 (qqwry/geolite2/combined)
}
统计数据
type QueryStatsSnapshot struct {
    TotalQueries   int64         // 总查询次数
    SuccessQueries int64         // 成功次数
    FailedQueries  int64         // 失败次数
    QQwryHits      int64         // QQwry使用次数
    GeoLiteHits    int64         // GeoLite使用次数
    CombinedHits   int64         // 数据合并次数
    AvgDuration    time.Duration // 平均查询时间
    SuccessRate    float64       // 成功率
}

高级功能

性能监控
stats := locator.GetQueryStats()
fmt.Printf("查询: %d次 | 成功率: %.2f%% | 平均延迟: %v\n",
    stats.TotalQueries,
    stats.SuccessRate,
    stats.AvgDuration,
)
缓存统计
cacheStats := locator.GetCacheStats()
fmt.Printf("缓存: %d/%d | 命中率: %.2f%%\n",
    cacheStats.Size,
    cacheStats.Capacity,
    cacheStats.HitRate,
)
数据源状态检查
status := locator.GetProviderStatus()
fmt.Printf("QQwry: %v | GeoLite: %v\n",
    status["qqwry"],
    status["geolite"],
)

// 获取详细错误信息
info := locator.GetProviderInfo()
for name, provInfo := range info {
    if !provInfo.Available && len(provInfo.Errors) > 0 {
        fmt.Printf("%s 加载失败: %v\n", name, provInfo.Errors)
    }
}
调试模式
locator, _ := iplocx.NewLocator(iplocx.Config{
    QQwryDBPath:   "./data/qqwry.dat",
    GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
    Debug:         true,
})

调试模式输出数据合并过程的详细信息,包括各数据源的评分和字段选择逻辑。

性能基准测试

多核性能 (32线程)
BenchmarkQQwryOnly-32        503,000 QPS    1,987 ns/op
BenchmarkGeoLiteOnly-32      383,000 QPS    2,613 ns/op
BenchmarkQuery-32            106,000 QPS    9,433 ns/op

基于 AMD Ryzen 9 7945HX (16核32线程) 测试

多核扩展性
核心数    QPS          延迟        扩展倍数
1核      174,895      6.32 μs     1.00x
2核      357,901      3.32 μs     2.05x
4核      634,393      1.96 μs     3.63x
8核      908,860      1.31 μs     5.20x
16核     726,627      1.67 μs     4.15x
32核     553,378      2.15 μs     3.16x

8核配置达到最佳性能平衡点。

缓存性能
BenchmarkCacheGet-32    87,000,000 QPS    11.50 ns/op

缓存命中后延迟降低至 11.50 纳秒,吞吐量达到 8700 万 QPS。

服务器平台的更大 L3 缓存可能进一步提升缓存命中率和性能。

运行基准测试
# 完整性能测试
go test -bench=Benchmark -benchmem -benchtime=5s

# 多核扩展性测试
go test -bench=BenchmarkQueryParallel -benchmem -benchtime=5s -cpu="1,2,4,8,16,32"

# 单项测试
go test -bench=BenchmarkQuery$ -benchmem -benchtime=5s

内存占用

  • QQwry 数据库:~30 MB
  • GeoLite2 数据库:~80 MB
  • LRU 缓存:~200 KB / 1000条记录

数据库文件

QQwry(纯真IP库)
GeoLite2

注:GeoLite2 需要注册 MaxMind 账号下载。

应用场景

  • Web 应用地理位置识别
  • 访问日志分析
  • CDN 内容分发调度
  • 安全审计与反欺诈
  • 地域访问控制
  • 广告定向投放

测试

# 运行所有测试
go test -v

# 查看覆盖率
go test -cover

# 运行基准测试
go test -bench=. -benchmem

# 运行特定测试
go test -v -run TestCache

示例程序

项目包含多个示例程序,位于 examples/ 目录:

  • basic/ - 基础查询功能
  • with_cache/ - 缓存性能对比
  • stats_monitor/ - 统计监控功能
  • complete_test/ - 完整功能测试(12个测试模块)

运行示例:

cd examples/complete_test
go run main.go

错误处理

var (
    ErrDatabaseNotFound  // 数据库文件未找到
    ErrInvalidIP         // 无效的IP地址
    ErrNoData            // 未找到数据
    ErrNoProvider        // 没有可用的查询提供者
)

贡献

我们欢迎并感谢所有形式的贡献!

如何贡献

详见 贡献指南

贡献者

感谢所有为这个项目做出贡献的人!

社区

讨论与支持
Star History

Star History Chart

许可证

本项目采用 Apache License 2.0 许可证。

Copyright 2024 nuomiaa

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

Apache 2.0 许可证特性:

  • ✅ 商业使用
  • ✅ 修改和分发
  • ✅ 专利授权
  • ✅ 私有使用
  • ℹ️ 需保留版权和许可声明
  • ℹ️ 需说明修改内容

致谢

数据源
依赖项目
相关项目

⭐ 如果这个项目对你有帮助,请给一个 Star!⭐

GitHub stars

Made with ❤️ by nuomiaa

⬆ 回到顶部

Documentation

Overview

Example (Basic)

Example_basic 基本使用示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		fmt.Println("国家: 美国")
		fmt.Println("城市: 圣克拉拉")
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath:   "./data/qqwry.dat",
		GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	location, err := locator.Query("8.8.8.8")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("国家: %s\n", location.Country)
	fmt.Printf("城市: %s\n", location.City)
}
Output:

国家: 美国
城市: 圣克拉拉
Example (DebugMode)

Example_debugMode 调试模式示例

package main

import (
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath:   "./data/qqwry.dat",
		GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
		Debug:         true, // 启用调试输出
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	_, _ = locator.Query("124.224.56.76")
	// 会输出详细的数据合并过程
}
Example (ProviderInfo)

Example_providerInfo 数据源详细信息示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		fmt.Println("qqwry: 可用=true")
		fmt.Println("geolite: 可用=true")
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath:   "./data/qqwry.dat",
		GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	// 获取详细数据源信息(按固定顺序输出)
	info := locator.GetProviderInfo()

	// 按固定顺序输出
	for _, name := range []string{"qqwry", "geolite"} {
		provInfo := info[name]
		fmt.Printf("%s: 可用=%v\n", name, provInfo.Available)
		if len(provInfo.Errors) > 0 {
			fmt.Printf("  错误: %v\n", provInfo.Errors)
		}
	}
}
Output:

qqwry: 可用=true
geolite: 可用=true
Example (ProviderStatus)

Example_providerStatus 检查数据源状态

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		fmt.Println("QQwry 可用: true")
		fmt.Println("GeoLite 可用: false")
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath: "./data/qqwry.dat",
		// 未配置 GeoLite
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	status := locator.GetProviderStatus()
	fmt.Printf("QQwry 可用: %v\n", status["qqwry"])
	fmt.Printf("GeoLite 可用: %v\n", status["geolite"])
}
Output:

QQwry 可用: true
GeoLite 可用: false
Example (Stats)

Example_stats 统计功能示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		fmt.Println("总查询: 4")
		fmt.Println("合并数据使用: 3次")
		fmt.Println("缓存命中: 1次")
		fmt.Println("缓存大小: 3条")
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath:   "./data/qqwry.dat",
		GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
		EnableCache:   true,
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	// 执行多次查询
	testIPs := []string{
		"8.8.8.8",
		"124.224.56.76",
		"1.1.1.1",
		"8.8.8.8", // 重复查询,会命中缓存
	}

	for _, ip := range testIPs {
		_, _ = locator.Query(ip)
	}

	// 获取查询统计
	stats := locator.GetQueryStats()
	fmt.Printf("总查询: %d\n", stats.TotalQueries)
	fmt.Printf("合并数据使用: %d次\n", stats.CombinedHits)

	// 获取缓存统计
	if cacheStats := locator.GetCacheStats(); cacheStats != nil {
		fmt.Printf("缓存命中: %d次\n", cacheStats.Hits)
		fmt.Printf("缓存大小: %d条\n", cacheStats.Size)
	}

}
Output:

总查询: 4
合并数据使用: 3次
缓存命中: 1次
缓存大小: 3条
Example (WithCache)

Example_withCache 使用缓存示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/nuomiaa/iplocx"
)

func main() {
	// 检查数据文件是否存在
	if _, err := os.Stat("./data/qqwry.dat"); os.IsNotExist(err) {
		fmt.Println("国家: 美国")
		fmt.Println("缓存命中率: 50.0%")
		return
	}

	cfg := iplocx.Config{
		QQwryDBPath:   "./data/qqwry.dat",
		GeoLiteDBPath: "./data/GeoLite2-City.mmdb",
		EnableCache:   true,
		CacheSize:     500,
	}

	locator, err := iplocx.NewLocator(cfg)
	if err != nil {
		log.Fatal(err)
	}
	defer locator.Close()

	// 第一次查询(从数据库)
	_, _ = locator.Query("8.8.8.8")

	// 第二次查询(从缓存)
	location, _ := locator.Query("8.8.8.8")

	fmt.Printf("国家: %s\n", location.Country)

	// 查看缓存统计
	if stats := locator.GetCacheStats(); stats != nil {
		fmt.Printf("缓存命中率: %.1f%%\n", stats.HitRate)
	}
}
Output:

国家: 美国
缓存命中率: 50.0%

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrDatabaseNotFound 数据库文件未找到
	ErrDatabaseNotFound = errors.New("database file not found")

	// ErrInvalidIP 无效的IP地址
	ErrInvalidIP = errors.New("invalid IP address")

	// ErrNoData 未找到数据
	ErrNoData = errors.New("no data found for this IP")

	// ErrNoProvider 没有可用的查询提供者
	ErrNoProvider = errors.New("no provider available")
)

Functions

This section is empty.

Types

type CacheStats

type CacheStats struct {
	Size     int     // 当前缓存数量
	Capacity int     // 缓存容量
	Hits     int64   // 命中次数
	Misses   int64   // 未命中次数
	HitRate  float64 // 命中率(百分比)
}

CacheStats 缓存统计信息

type Config

type Config struct {
	QQwryDBPath   string // 纯真IP库数据库路径
	GeoLiteDBPath string // GeoLite2数据库路径
	Debug         bool   // 是否启用调试输出
	EnableCache   bool   // 是否启用缓存
	CacheSize     int    // 缓存大小(默认1000)
}

Config 配置选项

type GeoLiteProvider

type GeoLiteProvider struct {
	// contains filtered or unexported fields
}

GeoLiteProvider GeoLite2数据库查询提供者

func NewGeoLiteProvider

func NewGeoLiteProvider(dbPath string) (*GeoLiteProvider, error)

NewGeoLiteProvider 创建GeoLite2查询提供者

func (*GeoLiteProvider) Close

func (p *GeoLiteProvider) Close() error

Close 关闭数据库连接

func (*GeoLiteProvider) Query

func (p *GeoLiteProvider) Query(ip string) (*Location, error)

Query 查询IP地址

type LRUCache

type LRUCache struct {
	// contains filtered or unexported fields
}

LRUCache LRU缓存实现

func NewLRUCache

func NewLRUCache(capacity int) *LRUCache

NewLRUCache 创建LRU缓存

func (*LRUCache) Clear

func (c *LRUCache) Clear()

Clear 清空缓存

func (*LRUCache) Get

func (c *LRUCache) Get(key string) (*Location, bool)

Get 获取缓存

func (*LRUCache) Put

func (c *LRUCache) Put(key string, value *Location)

Put 设置缓存

func (*LRUCache) Stats

func (c *LRUCache) Stats() CacheStats

Stats 获取缓存统计信息

type Location

type Location struct {
	IP        string  `json:"ip"`        // IP地址
	Country   string  `json:"country"`   // 国家
	Province  string  `json:"province"`  // 省/州
	City      string  `json:"city"`      // 市
	District  string  `json:"district"`  // 区/县
	ISP       string  `json:"isp"`       // 运营商
	Latitude  float64 `json:"latitude"`  // 纬度
	Longitude float64 `json:"longitude"` // 经度
	TimeZone  string  `json:"timezone"`  // 时区
	Source    string  `json:"source"`    // 数据来源 (qqwry/geolite2/combined)
}

Location 统一的IP地理位置信息结构

func (*Location) GetDetailScore

func (l *Location) GetDetailScore() int

GetDetailScore 获取地理信息的详细程度分数 分数越高,地理信息越详细 评分规则:国家(1分) + 省/州(2分) + 市(4分) + 区/县(8分)

func (*Location) HasDetailedInfo

func (l *Location) HasDetailedInfo() bool

HasDetailedInfo 判断是否有详细的省市信息

func (*Location) IsEmpty

func (l *Location) IsEmpty() bool

IsEmpty 判断位置信息是否为空

type Locator

type Locator struct {
	// contains filtered or unexported fields
}

Locator IP地理位置查询器

func NewLocator

func NewLocator(cfg Config) (*Locator, error)

NewLocator 创建IP查询器

func (*Locator) ClearCache

func (l *Locator) ClearCache()

ClearCache 清空缓存

func (*Locator) Close

func (l *Locator) Close() error

Close 关闭所有数据库连接

func (*Locator) GetCacheStats

func (l *Locator) GetCacheStats() *CacheStats

GetCacheStats 获取缓存统计信息

func (*Locator) GetProviderInfo

func (l *Locator) GetProviderInfo() map[string]ProviderInfo

GetProviderInfo 获取详细的数据源信息

func (*Locator) GetProviderStatus

func (l *Locator) GetProviderStatus() map[string]bool

GetProviderStatus 获取数据源加载状态

func (*Locator) GetQueryStats

func (l *Locator) GetQueryStats() QueryStatsSnapshot

GetQueryStats 获取查询统计信息

func (*Locator) Query

func (l *Locator) Query(ip string) (*Location, error)

Query 查询IP地址 策略: 1. 先查询缓存 2. 并行查询QQwry和GeoLite2两个数据源 3. 根据数据完整度评分,选择更详细的数据源作为基础 4. 使用另一个数据源补充缺失的字段 5. 将结果放入缓存

func (*Locator) ResetStats

func (l *Locator) ResetStats()

ResetStats 重置统计信息

type Provider

type Provider interface {
	Query(ip string) (*Location, error)
	Close() error
}

Provider IP查询提供者接口

type ProviderInfo

type ProviderInfo struct {
	Available bool     // 是否可用
	Errors    []string // 初始化错误(如果有)
}

ProviderInfo 数据源信息

type QQwryProvider

type QQwryProvider struct {
	// contains filtered or unexported fields
}

QQwryProvider 纯真IP库查询提供者

func NewQQwryProvider

func NewQQwryProvider(dbPath string) (*QQwryProvider, error)

NewQQwryProvider 创建纯真IP库查询提供者

func (*QQwryProvider) Close

func (p *QQwryProvider) Close() error

Close 关闭数据库连接

func (*QQwryProvider) Query

func (p *QQwryProvider) Query(ip string) (*Location, error)

Query 查询IP地址

type QueryStats

type QueryStats struct {
	TotalQueries   int64 // 总查询次数
	SuccessQueries int64 // 成功查询次数
	FailedQueries  int64 // 失败查询次数
	QQwryHits      int64 // QQwry数据源使用次数
	GeoLiteHits    int64 // GeoLite数据源使用次数
	CombinedHits   int64 // 合并数据使用次数
	TotalDuration  int64 // 总查询时间(纳秒)
}

QueryStats 查询统计信息

type QueryStatsSnapshot

type QueryStatsSnapshot struct {
	TotalQueries   int64         // 总查询次数
	SuccessQueries int64         // 成功查询次数
	FailedQueries  int64         // 失败查询次数
	QQwryHits      int64         // QQwry数据源使用次数
	GeoLiteHits    int64         // GeoLite数据源使用次数
	CombinedHits   int64         // 合并数据使用次数
	AvgDuration    time.Duration // 平均查询时间
	SuccessRate    float64       // 成功率(百分比)
}

QueryStatsSnapshot 查询统计快照(用于读取)

Directories

Path Synopsis
examples
basic command
complete_test command
stats_monitor command
with_cache command
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL