刚看到你的问题,你问的很久以前,我有一个使用Redis的解决方案。
让我们首先尝试用一些基本数字(而不是 IP)对问题进行建模,看看如何解决它:
范围到国家/地区查找
Lookup | Range | Country
--------|------------+------------------
| 5 | begin:Country A
L1 >>> |
| 10 | end:Country A
| |
L2 >>> |
| |
L2.1>>> 15 | begin:Country B
| |
| 20 | end:Country B
L3 >>> |
| |
查找L1
:
[6,10]
在(此处包括范围)之间进行数字查找。在这种情况下,结果将是end:Country A
=> IP Address 属于Country A。我们开始的原因6
在L2
.
查找L2
:
查找范围 [11, 15] (包括范围)中的数字。结果将是begin:Country B
=>
查找L3
:
结果将是Empty List or Set
=> 错误:IP 不属于任何已知范围
插入更棘手!
插入范围必须小心,因为新插入的范围可能会破坏现有范围。以下是插入的情况:
Insert | Range | Country
--------|------------+------------------
| 5 | begin:Country A
| |
I1 >>> 8,9 | !!! Country C !!!
| |
| 10 | end:Country A
| |
| |
I2 >>> 12,14 | Country E
| |
| |
| 15 | begin:Country B
| |
I3 >>> 17,21 | !!! Country D !!!
| |
| 20 | end:Country B
| |
I4 >>> 22,27 | Country F
| |
插入I1
:
使带有 IP6
和7
(介于5
和之间8
)的地址无效。=> 有效Country A
范围缩小到单个 IP 地址10
。
插入I2
:
好的,没有范围交叉点
插入I3
:
渲染B 国的开头无效 + 渲染D 国的开头(17
.. 20
)无效
插入I4
:
好的
注意:在某些情况下,您可能需要引入范围分割逻辑。
基于 Redis 的解决方案
我建议为此目的使用 Redis ZSET。以下是观察结果:
除了点分十进制字符串表示之外,每个 IPv4 地址都可以表示为 32 位整数。
Redis ZSET 通过额外使用分数对存储的成员进行排序来保证存储成员的唯一性
ZRANGEBYSCORE
我们可以通过使用分数范围,即命令来搜索ZSET成员。
如果我们使用数字 IP 作为 ZSET 分数,我们就完成了。一个国家的唯一性是通过为特定范围添加前缀来begin:
强制执行的。end:
在现实生活中,一个国家/地区可能有多个 IP 地址范围,因此您最终可能会将范围编号编码为国家名称,例如begin:r1:Country A
and end:r1:Country A
。您可以对此进行规范化并引入间接性。但是为了保持低查找次数,您需要对其进行非规范化并在单个数据库访问中拥有尽可能多的信息。这是因为引入新范围的频率远低于进行查找,因此增加查找次数会降低性能。
Lookup | Score | Country
--------|------------+------------------
| 5 | begin:Country A
L1 >>> |
| 10 | end:Country A
| |
L2 >>> |
| |
L2.1>>> 15 | begin:Country B
| |
| 20 | end:Country B
L3 >>> |
| |
使用什么 Redis 命令
这里只是简单的命令,没有你的逻辑来检查插入等期间的错误情况。
添加新范围
> ZADD ip-to-country 3232235777 "begin:Country A" 3232235876 "end:Country A"
注意: 3232235777
IPv4 是否192.168.1.1
表示为 unsigned int,同样适用于192.168.1.100
.
检查特定 IP 属于哪个范围
> ZRANGEBYSCORE ip-to-country 3232235778 +inf WITHSCORES LIMIT 0 1
注意: 3232235778
IPv4是用 unsigned int 表示的吗?我们从后面(ie )开始192.168.1.2
查找一个元素( ie )。LIMIT 0 1
192.168.1.8
+inf
检查Lookup 2.1
查找的 IP 开始新的范围
> ZSCORE ip-to-country "begin:Country A"
注意:结果将是3232235777
复杂性分析
空间复杂性:如果在最坏的情况下我们最终得到每个 IP 代表范围的开始和结束,我们将需要O(2*N)
空间,其中 N 是2^32
。但在现实生活中,这个数字会小得多。在一些算法书籍中,您会看到它2^32
被认为是一个常数因子,因此将减少为O(1)
.
运行时复杂性: Redis 声明这ZRANGEBYSCORE
是一个O(log(N)+M)
操作,其中M
是 中的元素数量LIMIT
,即这里只有 1。如果我们2*2^32
在最坏的情况下最多得分,而不是log2(8billion)
在33
Redis 集合实现中进行比较。但实际上我认为不会超过 2 或 3000 个范围,这是围绕11
比较。Redis 状态为KEYS
命令:
在入门级笔记本电脑上运行的 Redis 可以在 40 毫秒内扫描 100 万个关键数据库。
总而言之,您的查找速度将非常快!