【问题标题】:Most Efficient way of implementing a BlackList实施黑名单的最有效方式
【发布时间】:2011-10-26 23:28:50
【问题描述】:

我正在开发一个 Ip 过滤器,并且正在猜测如何使用任何类型的 esque 数据结构来开发一个非常高效和快速的黑名单过滤器。

我想做的很简单,每个传入/传出连接我都必须检查被阻止的 IP 列表。

IP分散,内存使用应该是线性的(不依赖于阻止列表的数量,因为我想在有限的系统(自制路由器)上使用)。

我有时间,可以从零开始创造任何东西。难度对我来说并不重要。 如果你可以使用任何东西,你应该怎么做?

【问题讨论】:

  • 如果您变得更具体,这将非常有帮助,例如描述一些性能很重要的场景。如果例如,它将完全采用不同的实现。您针对的是整个 IP 范围,而不是针对“分散”的单个 IP。
  • 谢谢乔恩的提示,我指定了更多!
  • 如果内存使用量是恒定的,显然这将对黑名单中的最大 IP 数量施加限制。
  • 对不起Oli,我表达错了

标签: c performance algorithm


【解决方案1】:

提高此类系统性能的一种方法是使用布隆过滤器。这是一种概率数据结构,占用的内存很少,其中可能出现误报,但不会出现误报。

当您要查找 IP 地址时,首先检查 Bloom Filter。如果有错过,您可以立即允许交通。如果命中,您需要检查您的权威数据结构(例如哈希表或前缀树)。

您还可以创建一个“在 Bloom Filter 中但实际允许的命中”地址的小型缓存,在 Bloom Filter 之后但在权威数据结构之前进行检查。

基本上,这个想法是以慢速路径(IP 地址被拒绝)为代价来加速快速路径(允许 IP 地址)。

【讨论】:

  • 谢谢你,这就是我要找的。​​span>
【解决方案2】:

“最有效”是一个难以量化的术语。显然,如果您有无限的内存,您将为每个 IP 地址创建一个 bin,并且可以立即对其进行索引。

一个常见的权衡是使用 B-tree 类型的数据结构。可以为 IP 地址的前 8 位预设第一级 bin,它可以存储指向包含所有当前被阻止的 IP 地址的列表的指针和列表的大小。第二个列表将被填充以防止不必要的memmove() 调用并可能进行排序。 (将列表的大小和长度保存在内存中可以在插入时间稍微昂贵的情况下对列表进行就地二进制搜索。)

例如:

127.0.0.1 =insert=> { 127 :: 1 }
127.0.1.0 =insert=> { 127 :: 1, 256 }
12.0.2.30 =insert=> { 12 : 542; 127 :: 1, 256 }

这种数据结构的开销最小,并且总存储大小是固定的。显然,最坏的情况是大量 IP 地址具有相同的最高位。

【讨论】:

    【解决方案3】:

    哈希表是要走的路。 它们的查找、插入和删除平均复杂度为 O(1)! 它们往往比树占用更多的内存,但速度要快得多。

    由于您只是使用 32 位整数(您当然可以将 IP 转换为 32 位整数),因此事情会非常简单和快速。

    您可以只使用排序数组。插入和删除成本是 O(n),但查找是 O(log n),尤其是每个 ip 的内存只有 4 个字节。 实现很简单,可能太多了:D

    二叉树的查找、插入和删除复杂度为 O(log n)。 一个简单的二叉树是不够的,但是您需要一个 AVL 树或一个红黑树,这可能非常烦人且难以实现。 AVL 和 RBT 树能够自我平衡,我们需要它,因为不平衡的树的查找时间复杂度最差,为 O(n),这与简单的链表相同!

    如果你需要禁止 ip 范围而不是单个和唯一的 ip,可能你需要一个 Patricia Trie,也称为 Radix Tree,它们是为单词字典和 ip 字典发明的。 但是,如果写得不好\平衡,这些树可能会变慢。 哈希表总是更适合简单的查找!它们太快了,不真实:)

    现在关于同步:

    如果您只在应用程序启动时填写一次黑名单,您可以使用一个没有多线程和锁定问题的纯只读哈希表(或基数树)。

    如果你不需要经常更新,我建议你使用读写锁。

    如果您需要非常频繁的更新,我建议您使用并发哈希表。 警告:不要自己编写,它们非常复杂且容易出错,请在网络上找到实现! 他们大量使用新处理器的(相对)新的原子 CAS 操作(CAS 表示比较和交换)。这些是一组特殊的指令或指令序列,允许在单个原子操作中比较和交换内存上的 32 位或 64 位字段,而无需锁定。 使用它们可能很复杂,因为您必须非常了解您的处理器、操作系统、编译器和算法本身是违反直觉的。 有关 CAS 的更多信息,请参阅http://en.wikipedia.org/wiki/Compare-and-swap

    并发AVL树被发明了,但它太复杂了,我真的不知道该说些什么:)例如http://hal.inria.fr/docs/00/07/39/31/PDF/RR-2761.pdf

    我刚刚发现并发基数树存在: ftp://82.96.64.7/pub/linux/kernel/people/npiggin/patches/lockless/2.6.16-rc5/radix-intro.pdf 不过也挺复杂的。

    并发排序数组当然不存在,更新需要读写锁。

    还要考虑处理非并发哈希表所需的内存量可能非常少:对于每个 IP,您需要 4 个字节的 IP 和一个指针。 您还需要一个大的指针数组(或带有一些技巧的 32 位整数),其大小应该是大于应该存储的项目数的素数。 如果您愿意,哈希表当然也可以在需要时自行调整大小,但它们也可以存储比素数更多的项目,但代价是查找时间变慢。

    对于树和哈希表,空间复杂度都是线性的。

    我希望这是一个多线程应用程序而不是多进程应用程序(fork)。 如果不是多线程,则无法以快速可靠的方式共享部分内存。

    【讨论】:

      猜你喜欢
      • 2019-03-25
      • 2017-11-05
      • 1970-01-01
      • 1970-01-01
      • 2012-08-30
      • 1970-01-01
      • 2017-05-17
      • 2012-05-22
      • 1970-01-01
      相关资源
      最近更新 更多