Golang中的IPSET内存数据库实现探讨
Golang中的IPSET内存数据库实现探讨 我想实现一个类似于Linux内核ipset的快速IP查询数据库。 该数据库应存储用于DNS/HTTP RBL服务的IP黑名单。 DNS是一种成熟的二进制协议,RBL在全球许多地方都有使用。 简单的PTR检查可以返回一个反映IP是否被列入黑名单的虚假域名,如果是,还可以显示对应的列表名称。
主要问题在于IP黑名单需要存储在内存中。 当内存中存储大量IP地址时,可能会消耗大量内存,如果实现不当还会导致性能缓慢。 我不确定自己是否充分理解了Go语言的内存使用和管理,无法确定是使用简单的net.IP对象结构更好,还是使用整数或大整数的节点列表更好。
我发现了以下库,它似乎是一个不错的起点:
ip2location/ip2location-go
使用IP2Location地理定位数据库通过IP2Location Go包查询地理定位信息。它可以用于确定国家、地区、城市、坐标、邮政编码、时区、ISP、域名…
但我对其实现方式感到困惑。 他们是如何遍历数据文件的? 将数据存储在某种结构体数组中会更明智吗? 或者使用节点列表?
更多关于Golang中的IPSET内存数据库实现探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中的IPSET内存数据库实现探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go语言中实现高效的IP黑名单内存数据库,可以采用基于位图(bitmap)和前缀树(trie)的混合方案。以下是具体实现:
核心数据结构
package main
import (
"encoding/binary"
"net"
"sync"
)
// IPv4黑名单使用位图存储
type IPv4Blacklist struct {
mu sync.RWMutex
bitmap []uint64
}
// IPv6黑名单使用前缀树存储
type IPv6TrieNode struct {
children [2]*IPv6TrieNode // 0和1分支
isBlack bool
}
type IPv6Blacklist struct {
mu sync.RWMutex
root *IPv6TrieNode
}
type IPSetDB struct {
ipv4 *IPv4Blacklist
ipv6 *IPv6Blacklist
}
IPv4位图实现
func NewIPv4Blacklist() *IPv4Blacklist {
// IPv4地址空间:2^32个地址,使用位图需要2^32/8 = 512MB
bitmapSize := (1 << 32) / 64 // 每个uint64存储64位
return &IPv4Blacklist{
bitmap: make([]uint64, bitmapSize),
}
}
func (b *IPv4Blacklist) Add(ip net.IP) {
if ip.To4() == nil {
return
}
b.mu.Lock()
defer b.mu.Unlock()
ipInt := binary.BigEndian.Uint32(ip.To4())
index := ipInt / 64
bit := ipInt % 64
b.bitmap[index] |= (1 << bit)
}
func (b *IPv4Blacklist) Contains(ip net.IP) bool {
if ip.To4() == nil {
return false
}
b.mu.RLock()
defer b.mu.RUnlock()
ipInt := binary.BigEndian.Uint32(ip.To4())
index := ipInt / 64
bit := ipInt % 64
return (b.bitmap[index] & (1 << bit)) != 0
}
IPv6前缀树实现
func NewIPv6Blacklist() *IPv6Blacklist {
return &IPv6Blacklist{
root: &IPv6TrieNode{},
}
}
func (b *IPv6Blacklist) Add(ip net.IP) {
if ip.To16() == nil {
return
}
b.mu.Lock()
defer b.mu.Unlock()
node := b.root
ipBytes := ip.To16()
// 遍历IPv6的128位
for i := 0; i < 16; i++ {
for j := 7; j >= 0; j-- {
bit := (ipBytes[i] >> uint(j)) & 1
if node.children[bit] == nil {
node.children[bit] = &IPv6TrieNode{}
}
node = node.children[bit]
}
}
node.isBlack = true
}
func (b *IPv6Blacklist) Contains(ip net.IP) bool {
if ip.To16() == nil {
return false
}
b.mu.RLock()
defer b.mu.RUnlock()
node := b.root
ipBytes := ip.To16()
for i := 0; i < 16; i++ {
for j := 7; j >= 0; j-- {
bit := (ipBytes[i] >> uint(j)) & 1
if node.children[bit] == nil {
return false
}
node = node.children[bit]
}
}
return node.isBlack
}
完整IPSet数据库
func NewIPSetDB() *IPSetDB {
return &IPSetDB{
ipv4: NewIPv4Blacklist(),
ipv6: NewIPv6Blacklist(),
}
}
func (db *IPSetDB) AddIP(ipStr string) error {
ip := net.ParseIP(ipStr)
if ip == nil {
return net.InvalidAddrError("invalid IP address")
}
if ip.To4() != nil {
db.ipv4.Add(ip)
} else {
db.ipv6.Add(ip)
}
return nil
}
func (db *IPSetDB) CheckIP(ipStr string) (bool, error) {
ip := net.ParseIP(ipStr)
if ip == nil {
return false, net.InvalidAddrError("invalid IP address")
}
if ip.To4() != nil {
return db.ipv4.Contains(ip), nil
} else {
return db.ipv6.Contains(ip), nil
}
}
使用示例
func main() {
db := NewIPSetDB()
// 添加黑名单IP
db.AddIP("192.168.1.1")
db.AddIP("2001:db8::1")
// 检查IP
blacklisted, _ := db.CheckIP("192.168.1.1")
println("192.168.1.1 blacklisted:", blacklisted) // true
blacklisted, _ = db.CheckIP("192.168.1.2")
println("192.168.1.2 blacklisted:", blacklisted) // false
blacklisted, _ = db.CheckIP("2001:db8::1")
println("2001:db8::1 blacklisted:", blacklisted) // true
}
性能优化说明
这种实现方案的优势:
- IPv4使用位图:O(1)查询时间,固定512MB内存占用
- IPv6使用前缀树:O(128)查询时间,内存占用与黑名单大小成正比
- 读写锁保证并发安全
- 避免大量小对象分配,减少GC压力
对于RBL服务,这种实现可以支持每秒百万级别的查询,内存使用可预测且高效。

