【问题标题】:Amazon ElastiCache Memcached/Redis: Map IP range to countryAmazon ElastiCache Memcached/Redis:将 IP 范围映射到国家/地区
【发布时间】:2014-12-04 11:00:57
【问题描述】:

我有一个 MySQL 数据库,它有一个 ip 范围(开始和结束,所以两列)和一个国家代码(1 列)。该数据库用于根据 IP 地址查找国家/地区。它有效,但我想加快速度。一个想法是使用例如将数据存储在 Amazon ElastiCache 上。 Redis 或 Memcache。我遇到的问题是如何采用这种方法? Redis 和 Memcache 使用键值,在我看来,很难存储 IP 范围和国家代码。对于使用 ElastiCache Memcache 或 Redis,您有什么建议?

国家/地区范围类似于:

  • 192.168.1.1 - 192.168.1.100(A 国)
  • 192.168.2.1 - 192.168.2.50(B 国)
  • 192.168.1.150 - 192.168.1.200(A 国)

现在我得到了 IP 地址,例如192.168.1.160,我需要尽快查一下,在这种情况下返回 A 国。

期待您的想法。

马克

【问题讨论】:

    标签: php amazon-web-services redis memcached amazon-elasticache


    【解决方案1】:

    如果您有每个开始/结束范围的键(例如“80-255”)和国家代码的值,您可以使用 Memcached 或 Redis。

    如果您想要更少的键,您可以在 Redis 中使用排序集,其中键是开始范围,分数是结束范围,值是国家/地区代码(也可以节省您的内存,因为 Redis 存储它的效率更高东西)。

    【讨论】:

    • 我用一个实际例子更新了最初的问题。你能看看吗?我喜欢你提出的 memcached 想法,但我不知道在这种情况下是否可行。
    【解决方案2】:

    刚看到你的问题,你问的很久以前,我有一个使用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 地址属于 A 国。为什么我们以6 开头将在L2 中显而易见。

    查找L2:

    查找范围 [11, 15] 中的数字(此处包含范围)。结果将是begin:Country B =>

    • IF 查找L2.1
      => 查找数字指向范围开始,即begin:Country B
      => OK: iff IP 直接属于 begin:Country B

    • ELSE 错误:IP 不属于任何已知范围

    查找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:

    使 IP 地址为 67(在 58 之间)的地址无效。 => 实际上Country A 范围缩小到单个IP 地址10

    插入I2:

    好的,没有范围相交

    插入I3:

    国家 B 的开始渲染无效 + 国家 D 的开始渲染(17..20)无效

    插入I4:

    好的

    注意:在某些情况下,您可能需要引入范围分割逻辑。

    基于 Redis 的解决方案

    我建议为此目的使用 Redis ZSET。以下是观察结果:

    1. 除了点分十进制字符串表示之外,每个 IPv4 地址都可以表示为 32 位整数。

    2. Redis ZSET 通过对存储成员进行额外排序来保证存储成员的唯一性

    3. 我们可以使用分数范围搜索ZSET成员,即ZRANGEBYSCORE命令。

    如果我们使用数字 IP 作为 ZSET 分数,我们就完成了。通过为特定范围添加 begin:end: 前缀来强制执行国家/地区的唯一性。在现实生活中,一个国家可能有多个 IP 地址范围,因此您最终可能会将范围编号编码为国家名称,例如 begin:r1:Country Aend: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 192.168.1.2,表示为 unsigned int,我们从192.168.1.8 开始(即+inf)查找一个元素(即LIMIT 0 1)。

    • 检查 Lookup 2.1 查找的 IP 开始新范围

       > ZSCORE ip-to-country "begin:Country A"
      

      注意:结果将是3232235777

    复杂性分析

    空间复杂性:如果在最坏的情况下我们最终得到每个 IP 代表范围的开始和结束,我们将需要 O(2*N) 空间,其中 N 是 2^32。但在现实生活中,这个数字会小得多。在一些算法书籍中,您会看到2^32 被认为是一个常数因子,因此将简化为O(1)

    运行时复杂度: Redis 声明ZRANGEBYSCOREO(log(N)+M) 操作,其中MLIMIT 中的元素个数,即这里只有1。如果我们最多有2*2^32 在最坏情况下的得分比 log2(8billion) 大约是 Redis 集合实现中的 33 比较。但实际上我认为不会超过 2 或 3000 个范围,大约是 11 比较。 Redis 状态为KEYS command

    在入门级笔记本电脑上运行的 Redis 可以在 40 毫秒内扫描 100 万个密钥数据库。

    总而言之,您的查找速度将非常快!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-12
      • 2012-02-04
      • 1970-01-01
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      相关资源
      最近更新 更多