【问题标题】:Best choice for in memory data structure for IP address filter in JavaJava中IP地址过滤器内存数据结构的最佳选择
【发布时间】:2012-01-09 02:48:54
【问题描述】:

我有像 192.168.1.0/24 这样的 CIDR 格式的文件,它被转换成这两列结构

3232236030 3232235777

每个字符串 IP 地址转换都使用以下代码进行:

String subnet = "192.168.1.0/24";
SubnetUtils utils = new SubnetUtils(subnet);

Inet4Address a = (Inet4Address) InetAddress.getByName(utils.getInfo().getHighAddress());
long high = bytesToLong(a.getAddress());
Inet4Address b = (Inet4Address) InetAddress.getByName(utils.getInfo().getLowAddress());
long low = bytesToLong(b.getAddress());

private static long bytesToLong(byte[] address) {
   long ipnum = 0;
   for (int i = 0; i < 4; ++i) {
       long y = address[i];
       if (y < 0) {
           y += 256;
       }
       ipnum += y << ((3 - i) * 8);
   }
   return ipnum;
}

考虑到(low high : 3232236030 3232235777) 有超过 500 万个条目。
还会有相交,因此 IP 可以来自多个范围。仅第一个就可以了。
数据是只读的。
找到ipToBefiltered 所属范围的最快方法是什么?该结构将完全在内存中,因此无需进行数据库查找。

更新:

我发现了这个Peerblock 项目(它的下载量超过百万,所以我认为它必须有一些快速算法): http://code.google.com/p/peerblock/source/browse/trunk/src/pbfilter/filter_wfp.c

有谁知道该项目使用什么技术来创建范围列表而不是搜索它们?

【问题讨论】:

  • "该结构将完全在内存中,因此没有数据库查找。" - 为什么没有内存数据库?
  • find the range the ipToBefiltered belongs to? 您想知道给定 IP 的范围,而不仅仅是 T/F 是否在 一些 定义的范围内?
  • @Mat 范围内是否有重叠?

标签: java filter ip in-memory


【解决方案1】:

我会使用一个排序的 int 数组(基地址)和另一个大小相同的数组(结束地址)。这将使用 5M * 8 = 40 MB。第一个 IP 是基地址,第二个 IP 是范围内的最后一个地址。您需要删除交叉点。

要查找地址是否被过滤到二进制搜索 O(log N),如果不是完全匹配,请检查它是否小于(或等于)上限。

【讨论】:

    【解决方案2】:

    这里是一个答案的开始,我有空再回来

    设置:

    1. 按起始编号对范围进行排序。
    2. 由于这些是 IP 地址,我假设没有一个范围重叠。如果有重叠,您可能应该运行列表合并范围并修剪不必要的范围(例如,如果您的范围为 1 - 10,则可以修剪范围 5 - 7)。
      1. 要合并或修剪,请执行以下操作(假设范围 a 紧接在范围 b 之前):
        1. 如果 b.end
        2. 如果 b.start a.end 那么你可以合并范围 a 和 b。设置 a.end = b.end 然后删除范围 b。

    【讨论】:

      【解决方案3】:

      归根结底,我只需要知道 IP 是否存在于 5M 范围内。

      我会考虑一个 n-ary 树,其中 n=256,并使用点分地址而不是转换后的整数。

      顶层是一个包含 256 个对象的数组。 null 条目表示“否”,没有包含地址的范围,因此给定您的示例 192.168.1.0/24 array[192] 将包含一个对象,但 array[100] 可能为空,因为没有为任何 100 定义范围.xxx/n

      存储的对象包含一个(引用)另一个数组[256]和一个范围说明符,两者中只有一个会被设置,所以192.0.0.0/8最终会得到一个范围说明符,指示该范围内的所有地址都将被过滤。这将允许像 192.255.0.0/10 这样的地址的前 10 位是重要的 1100 0000 11xx xxxx - 否则您需要检查第二级数组中的下一个八位字节。

      最初将重叠范围(如果有)合并为更大的范围...例如3 .. 107 .. 16 变为 3 .. 16 ... 允许这样做,因为您不需要将给定的 IP 与 which 定义的范围相关联。

      这应该需要不超过 8 次比较。每个八位字节最初直接用作索引,然后是 null 的比较,终端节点的比较(是范围还是指向下一层树的指针)

      如果每个 IP 地址都在过滤范围内,理论上最坏情况的内存消耗为 4 GB (256 ^ 4),但当然这会合并为一个范围,因此实际上只有 1 个范围对象。更现实的最坏情况可能更像(256 ^ 3) 或 16.7 MB。现实世界的使用可能会使每个级别的大多数 array[256] 节点为空。

      这本质上类似于霍夫曼/前缀编码。最短的不同前缀可以在找到答案(范围)后立即终止,因此您通常会有 &lt; 4 比较的平均值。

      【讨论】:

        【解决方案4】:

        我在Vuze (aka azureus) 项目中发现了这种二进制斩波算法:

        public IpRange isInRange(long address_long) {
            checkRebuild();
        
            if (mergedRanges.length == 0) {
                return (null);
            }
        
            // assisted binary chop
        
            int bottom = 0;
            int top = mergedRanges.length - 1;
            int current = -1;
        
            while (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
        
                current = (bottom + top) / 2;
        
                IpRange e = mergedRanges[current];
        
                long this_start = e.getStartIpLong();
                long this_end = e.getMergedEndLong();
        
                if (address_long == this_start) {
                    break;
                } else if (address_long > this_start) {
        
                    if (address_long <= this_end) {
                        break;
                    }
        
                    // lies to the right of this entry
        
                    bottom = current + 1;
        
                } else if (address_long == this_end) {
                    break;
                } else {
                    // < this_end
        
                    if (address_long >= this_start) {
                        break;
                    }
                    top = current - 1;
                }
            }
        
            if (top >= 0 && bottom < mergedRanges.length && bottom <= top) {
        
                IpRange e = mergedRanges[current];
        
                if (address_long <= e.getEndIpLong()) {
                    return (e);
                }
        
                IpRange[] merged = e.getMergedEntries();
        
                if (merged == null) {
                    //inconsistent merged details - no entries
                    return (null);
                }
        
                for (IpRange me : merged) {
                    if (me.getStartIpLong() <= address_long && me.getEndIpLong() >= address_long) {
                        return (me);
                    }
                }
            }
            return (null);
        }
        

        似乎表现不错。如果您知道更快的事情,请告诉我。

        【讨论】:

          【解决方案5】:

          如果您只有一个 CIDR 地址(或它们的列表)并且您想检查某个 ipAddress 是否在该 CIDR(或 CIDR 的列表)的范围内,只需定义一组 SubnetUtils 对象。

          除非您过滤非常大的 N 个地址,否则这都是字符串比较,并且执行速度非常快。您不需要基于高/低位以及所有复杂的 Jazz 构建二叉树。

          String subnet = "192.168.1.0/24";
          SubnetUtils utils = new SubnetUtils(subnet);
          //...
          //for each subnet, create a SubnetUtils object
          Set<SubnetUtils> subnets = getAllSubnets();
          //...
          

          使用 Guava 谓词过滤不在您的子网集范围内的 ipAddress:

             Set<String> ipAddresses = getIpAddressesToFilter();
             Set<String> ipAddressesInRange = 
                 Sets.filter(ipAddresses, filterIpsBySubnet(subnets))
          
          
             Predicate<String> filterIpsBySubnet(final Set<SubnetUtils> subnets){
                 return new Predicate<String>() {
                      @Override
                      public boolean apply(String ipAddress) {
                          for (SubnetUtils subnet : subnets) {
                              if (subnet.getInfo().isInRange(ipAddress)) {
                                  return true;
                              }
                          }
                          return false;
                      }
                  };
             }
          

          现在,如果 IP 在任何子网中,您就有了一个不错的简单过滤器,并且您不必构建必须进行单元测试的数据结构。如果这还不够性能,则进行优化。不要过早优化:)

          【讨论】:

            猜你喜欢
            • 2014-05-16
            • 2014-09-13
            • 1970-01-01
            • 2016-12-04
            • 2022-12-02
            • 1970-01-01
            • 2020-12-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多